Map it before you ship it. Course correct after deploy.
Auto-discover routes, APIs, middleware, and external services in your web project, then generate an interactive HTML service map.
npm install --save-dev shipmapshipmap scans your project and produces a self-contained HTML report showing:
- Page routes with rendering strategy detection (SSR, SSG, ISR, Edge, Static, Client)
- API routes with HTTP method detection from exports (
GET,POST, etc.) - Middleware including
matcherpatterns, auth provider detection, and redirect targets - External services detected from env var prefixes and import patterns (Stripe, Supabase, Firebase, AWS, OpenAI, Clerk, Prisma, and more)
- Connectors showing relationships between middleware coverage, route-to-external dependencies
The report is a single .html file with zero external dependencies. Open it in any browser.
| Framework | Status | Routes | API Routes | Middleware | Externals |
|---|---|---|---|---|---|
| Next.js (App + Pages Router) | Full support | Yes | Yes | Yes | Yes |
| Remix | Beta | Yes | Yes (loader/action) | ✗ | Yes |
| SvelteKit | Beta | Yes | Yes (+server) | Yes (hooks) | Yes |
| Astro | Beta | Yes | Yes (API routes) | Yes | Yes |
| Nuxt | Beta | Yes | Yes (server/api) | Yes | Yes |
| Vite + React | Beta | Yes | Yes (fetch detect) | ✗ | Yes |
| React Router SPA | Basic | Yes | ✗ | ✗ | Yes |
| Other | Basic | Yes | Yes | ✗ | Yes |
- Full support: production-tested, all features working
- Beta: functional discovery with route normalization; edge cases may exist
- Basic: limited discovery (externals, simple route patterns)
Framework is auto-detected from package.json dependencies. No configuration needed.
npm install --save-dev shipmapOr run directly without installing:
npx shipmapA sample Next.js project is included in the demo/ folder (i GitHUb repo; not npm package). Run shipmap against it to see a full service map without setting up your own project:
node bin/shipmap.js demo --verboseAdd a script to your package.json:
{
"scripts": {
"shipmap": "shipmap"
}
}Then run:
# Scan current directory, output shipmap-report.html
npm run shipmap
# Scan a specific project
npm run shipmap -- ./my-project
# Custom output path
npm run shipmap -- -o service-map.html
# JSON output instead of HTML
npm run shipmap -- --json
# Don't auto-open in browser
npm run shipmap -- --no-open
# Quiet mode (errors only)
npm run shipmap -- -q
# Verbose discovery details
npm run shipmap -- --verbose
# Compare to previous run
npm run shipmap -- --diff
# Output as Markdown table
npm run shipmap -- --markdownTest discovered routes against a running server:
# Probe routes against localhost:3000
npm run shipmap -- --probe
# Probe against a custom URL
npm run shipmap -- --probe --probe-url https://preview.example.com
# Include auth headers derived from .env secrets (NextAuth, Supabase, Basic Auth)
npm run shipmap -- --probe --probe-with-auth
# Allow probing private/internal IPs (10.x, 172.16.x, 192.168.x, CGNAT)
npm run shipmap -- --probe --allow-internal
# Allow probing non-localhost URLs over plain HTTP
npm run shipmap -- --probe --allow-httpSecurity note:
--probedoes not send credentials by default. The--probe-with-authflag must be passed explicitly to read secrets from.envfiles and include them as request headers. Secrets are never sent over plain HTTP to non-localhost URLs.By default, probe mode blocks requests to private/internal IP ranges (RFC 1918, CGNAT, link-local) and requires HTTPS for non-localhost targets. Cloud metadata endpoints (169.254.169.254) are always blocked. Use
--allow-internaland--allow-httpto relax these restrictions when needed.
Use shipmap in continuous integration to catch service map regressions:
# Exit non-zero if errors found
npm run shipmap -- --ci
# Fail on specific conditions
npm run shipmap -- --ci --ci-fail-on errors,slow,unprotected
# CI + diff + JSON output
npm run shipmap -- --ci --diff --json -o report.jsonFailure rules for --ci-fail-on:
| Rule | Triggers when |
|---|---|
errors |
Any route returns HTTP error (probe mode) |
slow |
Any route exceeds timeout threshold (probe mode) |
unprotected |
Routes lack middleware coverage |
unreachable |
External service hosts are unreachable (probe mode) |
new-unreviewed |
New routes added since last run (with --diff) |
Exit codes: 0 = pass, 1 = failures found, 2 = execution error.
shipmap scans source files directly, so it does not depend on a build step. Add it anywhere in your workflow:
# Static check (no running server needed)
- name: Service map scan
run: npx shipmap --ci --diff --json > shipmap-report.json
# Optional: with probe against preview deployment
# - name: Service map probe
# run: npx shipmap --ci --probe --probe-url ${{ env.PREVIEW_URL }}
- name: Upload service map report
if: always()
uses: actions/upload-artifact@v4
with:
name: shipmap-report
path: shipmap-report.html
# Optional: Comment on PR with service map changes
- name: Comment service map diff
if: always()
run: |
npx shipmap --diff --markdown > /tmp/service-map-diff.md
gh pr comment ${{ github.event.pull_request.number }} --body-file /tmp/service-map-diff.mdshipmap works out of the box with zero configuration. To customize behavior, create a shipmap.config.js in your project root:
export default {
probe: {
baseUrl: "http://localhost:3000",
timeout: 10000,
concurrency: 5,
headers: { Authorization: "Bearer ..." },
exclude: ["/health", "/internal/*"],
},
discovery: {
exclude: ["**/test/**", "**/storybook/**"],
include: ["src/pages/**", "app/**"],
},
externals: [
{ name: "Plaid", envPrefixes: ["PLAID_"], importPatterns: ["plaid"] },
],
groups: {
admin: "/admin/*",
auth: ["/login", "/register", "/forgot-password"],
},
ci: {
failOn: ["errors", "unprotected"],
slowThreshold: 5000,
allowUnprotected: ["/login", "/register", "/api/health"],
},
};The HTML report features a dark canvas visualization:
- Pan: drag the canvas background
- Zoom: scroll wheel
- Drag nodes: reposition any node
- Click a node: opens detail panel with file path, rendering strategy, methods, connections
- F: fit all nodes to view
- Escape: close detail panel
- +/-: zoom in/out
Nodes are color-coded by type:
| Type | Color |
|---|---|
| Page | Blue |
| API | Green |
| Middleware | Amber |
| External | Purple |
Rendering strategies show as badges: SSR (red), SSG (green), ISR (amber), Edge (cyan), Static (grey), Client (pink).
| Signal | Strategy |
|---|---|
'use client' |
Client |
runtime = 'edge' |
Edge |
dynamic = 'force-dynamic' |
SSR |
dynamic = 'force-static' |
SSG |
revalidate = N |
ISR |
generateStaticParams |
SSG |
getServerSideProps |
SSR |
getStaticProps |
SSG |
cookies() / headers() |
SSR |
searchParams |
SSR |
| Default | Static |
Detected from all .env* files and package imports. 50+ built-in services including:
- Payments: Stripe, PayPal, Square, Lemon Squeezy
- Auth: Clerk, NextAuth, Auth0, Lucia, Kinde
- Databases: Supabase, Firebase, Prisma, Drizzle, MongoDB, PlanetScale, Turso, Neon, Convex, FaunaDB, Redis
- AI: OpenAI, Anthropic, Replicate, Cohere, HuggingFace, Groq, Mistral, Pinecone
- Email: Resend, SendGrid, Mailgun, Postmark
- Messaging: Twilio, Pusher, Ably
- Cloud: AWS, Cloudinary, Uploadthing, Vercel
- Monitoring: Sentry, Datadog, LogRocket, PostHog
- Search: Algolia, Meilisearch
- CMS: Sanity, Contentful, Strapi
- APIs: GitHub, Slack, Discord
DATABASE_URL is parsed by protocol: postgresql:// → PostgreSQL, mysql:// → MySQL, mongodb:// → MongoDB.
Env var values are never included in the report. Only service names.
To add custom or missing services, use the externals config:
// shipmap.config.js
export default {
externals: [
{ name: "Plaid", envPrefixes: ["PLAID_"], importPatterns: ["plaid"] },
],
};matcherpattern extraction fromexport const config- Auth provider detection (NextAuth, Clerk, Supabase, Kinde, custom)
- Redirect target detection
- Route coverage connectors
The CLI is built on top of a fully exported TypeScript API. Every function the CLI uses is available to your own scripts, so you can build custom dashboards, enforce project-specific rules in CI, generate reports in any format, or integrate service map data into other tools.
The main entry point. Scans a project directory and returns a TopologyReport with all discovered pages, API routes, middleware, external services, and the connections between them.
import { discover } from "shipmap";
const report = await discover("./my-project", {
// Register services that aren't in the built-in list
customExternals: [
{ name: "Plaid", envPrefixes: ["PLAID_"], importPatterns: ["plaid"] },
],
});The returned TopologyReport contains everything shipmap knows about your project:
report.meta; // framework, version, project name, generation timestamp
report.nodes; // every page, API route, middleware, and external service
report.connectors; // relationships: middleware coverage, external dependencies
report.groups; // logical groupings (pages, api, external, etc.)
report.summary; // totals and rendering strategy breakdownEach node in report.nodes is typed by its role:
| Node type | Key fields |
|---|---|
RouteNode (page or api) |
path, filePath, methods, rendering, isProtected, probe |
MiddlewareNode |
filePath, matcherPatterns, authProvider, redirectTarget |
ExternalNode |
name, detectedFrom (env, import, or both), referencedBy, host |
Turns a TopologyReport into a self-contained HTML file with the interactive canvas visualization. Optionally pass a DiffResult to highlight added, removed, and changed nodes.
import { discover, generateReport, compareTopology } from "shipmap";
import { writeFile, readFile } from "node:fs/promises";
const current = await discover("./my-project");
const previous = JSON.parse(await readFile("./last-report.json", "utf-8"));
const diff = compareTopology(current, previous);
// HTML report with changes highlighted
const html = generateReport(current, diff);
await writeFile("service-map.html", html);Compare any two reports to get a structured diff. This is what powers --diff on the CLI, but you can use it to build your own change notifications, PR comments, or audit logs.
import { discover, compareTopology, generateMarkdown } from "shipmap";
const current = await discover("./my-project");
const previous = /* load from file, database, S3, etc. */;
const diff = compareTopology(current, previous);
diff.added; // new routes, services, or middleware since last run
diff.removed; // routes or services that no longer exist
diff.changed; // nodes with altered rendering strategy, methods, etc.
// Render as a Markdown table (useful for PR comments)
const md = generateMarkdown(current);Probe functions let you test discovered routes against a running server and check external service reachability. Combine with discover() to build health checks or deployment verification scripts.
Security note:
detectAuth()reads real secret values from.envfiles and returns them as HTTP headers. Only call it when you intend to send credentials, and only probe over HTTPS for non-localhost targets.
import { discover, probeRoutes, probeExternals, detectAuth } from "shipmap";
const report = await discover("./my-project");
// detectAuth() reads .env secrets (NEXTAUTH_SECRET, SUPABASE_SERVICE_ROLE_KEY, etc.)
// Only use when you explicitly need authenticated probing
const auth = await detectAuth("./my-project");
// Probe all page and API routes
const routes = report.nodes.filter(
(n) => n.type === "page" || n.type === "api",
);
const probed = await probeRoutes(routes, {
baseUrl: "https://staging.example.com",
timeout: 10000,
concurrency: 5,
headers: auth?.headers ?? {},
// allowInternal: true, // permit private/internal IPs
// allowHttp: true, // permit plain HTTP to non-localhost
});
// Check which external services are reachable
const externals = report.nodes.filter((n) => n.type === "external");
const probedExternals = await probeExternals(externals, { timeout: 10000 });
// Build your own alerting logic
const broken = probed.filter((r) => r.probe?.status === "error");
const unreachable = probedExternals.filter((e) => !e.probe?.reachable);The CI runner is also exported, so you can evaluate pass/fail rules programmatically and integrate the results into any workflow.
import { discover, compareTopology } from "shipmap";
import { evaluateCi } from "shipmap";
const report = await discover("./my-project");
const diff = compareTopology(report, previousReport);
const result = evaluateCi(report, ["errors", "unprotected", "new-unreviewed"], {
slowThreshold: 5000,
diffResult: diff,
});
if (!result.passed) {
for (const failure of result.failures) {
console.error(`[${failure.rule}] ${failure.message}`);
}
process.exit(result.exitCode); // 1 = failures found, 2 = execution error
}| Export | Description |
|---|---|
discover(dir, options?) |
Scans a project and returns a TopologyReport |
generateReport(report, diff?) |
Renders an interactive HTML report |
detectFramework(dir) |
Returns { type, version } for the detected framework |
loadConfig(dir) |
Loads and validates shipmap.config.js |
compareTopology(current, previous) |
Diffs two reports, returns added/removed/changed nodes |
generateMarkdown(report) |
Renders service map as a Markdown table |
probeRoutes(nodes, options) |
HTTP-probes route nodes against a running server |
probeExternals(nodes, options) |
Checks reachability of external service hosts |
validateProbeUrl(url, options?) |
Validates a URL against SSRF and protocol safety rules |
detectAuth(dir) |
Auto-detects auth provider and generates probe headers |
archiveReport(dir, report) |
Saves a timestamped snapshot for future diffs |
readReportFromPath(path) |
Reads a previously saved JSON report |
All types are exported for use in custom integrations:
import type {
TopologyReport, // the full report object
TopologyNode, // union of all node types
RouteNode, // page or API route
MiddlewareNode, // middleware with matcher patterns
ExternalNode, // detected external service
Connector, // relationship between two nodes
RenderingStrategy, // SSR, SSG, ISR, Edge, Static, Client
FrameworkType, // nextjs, remix, sveltekit, astro, nuxt, etc.
} from "shipmap";- Node.js 18+ (uses
fetch,crypto.randomUUID, and other Node 18 APIs) - A web project with
package.jsonin the root (used for framework detection and project naming) - A supported framework (see Framework support above) for full route and middleware discovery. Projects without a recognized framework still get basic route and external service detection.
- File-based routing conventions must be in place (e.g.,
app/orpages/for Next.js,src/routes/for SvelteKit). shipmap scans source files directly and does not require a build step. - Probe mode (
--probe) requires a running dev or preview server at the target URL .envfiles are optional but recommended. shipmap reads them to detect external service integrations. Env var values are never included in reports.
MIT



