Deleting features

Remove the bundled modules you don't need — credits, files, blog and docs, waitlist and more — with recipes that are checked by build, typecheck and tests.

ShipKit ships with more than most products need. Every optional module is a vertical slice (see Adding features), and every one has a deletion recipe that is run and checked, not just written down. Remove what you will not use early: less code to read, fewer tables, fewer console pages.

How it works

A module lives in src/features/<name>/. Outside that folder it touches the rest of the app in three kinds of places:

  • Its own routes — user pages, console pages, API routes. These are deleted.
  • Registry lines — one import line per registry in src/core/*/handlers.ts and the schema barrel src/core/db/schema.ts. These are removed.
  • Marked blocks in shared files — a sidebar row, a dashboard card, a menu item — fenced by // [feature: <name>] start / end comments (or {/* … */} in JSX), or a single line tagged // [feature: <name>]. These are cut out.

The core dispatchers stay behind. audit(), notify(), track() and the email event emitter all live in src/core/; with no feature registered to receive their events, calling them does nothing. That is why deleting the audit log does not mean editing every server function that records an audit entry.

The recipes are code

scripts/verify-deletion.ts holds one recipe per module. Each is a function that deletes files and edits shared ones — the exact steps, not a summary.

bun run verify:deletion credits      # one module
bun run verify:deletion              # every recipe, each on its own copy
bun run verify:deletion all          # every recipe applied to ONE copy
bun scripts/verify-deletion.ts --list  # the recipe names

For each target the script copies the repo to /tmp/verify-deletion-<name>, applies the recipe, regenerates routes, then requires bun run build, bun run typecheck and bun run test to pass. It never touches your working tree. Each run is a full production build, so the full sweep takes a while. CI runs every recipe on pull requests, so a change that breaks one is caught before it merges.

all is the case for a buyer who wants none of the optional modules: a recipe that patches a file another recipe already deleted only fails there.

Deleting a module for real

The easiest path is the /delete-feature skill in Claude Code (see AI agents): it dry-runs the recipe, applies the same steps to your tree and does the cleanup below. By hand:

  1. Run bun run verify:deletion <name> first. If it fails, the recipe has drifted from the code — fix the recipe before touching your tree.
  2. Read the recipe in scripts/verify-deletion.ts and apply the same deletions and edits.
  3. Clean up what the recipe leaves alone:
    • unused keys in messages/en.json and messages/zh.json (recipes only drop a few prefixes, such as the module's setting_* keys)
    • the module's cases in e2e/*.spec.ts
    • the recipe itself, and this page if you keep your own docs
  4. Generate the drop migration and check everything:
bun run db:generate && bun run db:migrate:local
bun run generate-routes && bun run typecheck && bun run build && bun run e2e

Apply the migration to production with bun run db:migrate:remote before you deploy (see Deploy). The drop migration deletes the module's tables and their data, locally and in production.

Module by module

"Registries" lists the src/core/<registry>/handlers.ts files that lose a line; schema is the barrel src/core/db/schema.ts. Pages are given by URL.

ModuleDeletesRegistriesAlso edits
creditssrc/features/credits/, /credits, /admin/credits, /api/v1/admin/credits*, /api/v1/me/credits, /api/v1/me/run, src/core/payment/money-path.test.tsschema, payment, account, jobs, stats, user-detail, settings, authzsidebar rows, balance chip in _app.tsx, dashboard card and ledger panel, account-dialog tabs, billing section, credit packs in src/config/plans.ts and its test, notification bell, seed
filessrc/features/files/, /files, /api/files, /api/files/$fileIdschema, account, user-detail, authzsidebar row, dashboard card and recent-files panel
content (blog and docs)src/features/content/, content/blog/, content/docs/, public/covers/, /blog, /docsnoneblog/docs links in _marketing.tsx, docs links in the landing hero.tsx and cta.tsx blocks
auditsrc/features/audit/, /admin/audit, /api/v1/admin/auditschema, audit, stats, user-detail, payment, account, jobs, settings, authzconsole nav row, seed
email-logsrc/features/email-log/, /admin/email-log, /api/v1/admin/emailsschema, email, stats, user-detail, account, jobs, settingsconsole nav row, seed
affiliatesrc/features/affiliate/, /affiliate, /admin/affiliate, the /r/<code> referral linkschema, payment, account, jobs, stats, settings, authzsidebar rows, notification bell, overview labels, deletion-guard message in account-section.tsx, console settings page, src/config/private-paths.ts, the guard code union in src/core/account/events.ts
feedbacksrc/features/feedback/, /admin/feedback, /api/v1/admin/feedback, /api/feedback-image*, feedback-reply email templateschema, stats, user-detail, account, settings, authzHelp and feedback menu item and dialog in _app.tsx, console nav row, overview labels, notification bell, "My feedback" in the account dialog, email preview samples, seed
waitlistsrc/features/waitlist/, /waitlist, /admin/waitlist, /api/v1/admin/waitlist, waitlist-approved email templateschema, stats, user-detail, account, authzthe gate in _app.tsx, the waitlist flag in app-config.ts, the landing and header calls to action, console nav row, overview labels, notification bell, email preview samples, seed, e2e setup
analyticssrc/features/analytics/, /api/ph (the PostHog proxy)audit, payment, account, analytics<PostHog /> in __root.tsx, the analytics block in app-config.ts
demosrc/features/demo/jobsmarked blocks in auth.ts, env.ts, _app.tsx, _auth/login.tsx, _marketing.tsx; demo_* messages
notificationssrc/features/notifications/schema, notify, accountthe bell in _app.tsx

What stays, and why

  • Billing without credits. Subscriptions and one-time orders keep working; billing and credits only share the payment event stream. See Payments.
  • R2 without files. The wrangler.jsonc bucket binding stays: profile photos are core and use the same bucket. Objects already uploaded by the files or feedback modules are not removed from the bucket.
  • Legal pages without content. The MDX pipeline, src/core/content/ and content/legal/ stay, because the terms and privacy pages are core. See Content.
  • Sending without the email log. sendEmail keeps working; deliveries are just not recorded. The email preview page and its email.read permission are core.
  • No migration for three. analytics, content and demo own no tables. You can also switch analytics off without deleting it by leaving the PostHog key empty, and demo mode is off unless DEMO_MODE=true. See More modules.

Modules without a recipe

Billing, authentication and the admin console are not optional modules; the rest of the app is built on them, and there is no recipe to remove them. For a module you added yourself, write the recipe when you build it — Adding features shows how.