Deleting features
Remove the bundled modules you don't need — credits, files, image generation, 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.tsand the schema barrelsrc/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/endcomments (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:
- Run
bun run verify:deletion <name>first. If it fails, the recipe has drifted from the code — fix the recipe before touching your tree. - Read the recipe in
scripts/verify-deletion.tsand apply the same deletions and edits. - Clean up what the recipe leaves alone:
- unused keys in
messages/en.jsonandmessages/zh.json(recipes only drop a few prefixes, such as the module'ssetting_*keys) - the module's cases in
e2e/*.spec.ts - the recipe itself, and this page if you keep your own docs
- unused keys in
- 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.
| Module | Deletes | Registries | Also edits |
|---|---|---|---|
credits | src/features/credits/, /credits, /admin/credits, /api/v1/admin/credits*, /api/v1/me/credits, /api/v1/me/run, src/core/payment/money-path.test.ts | schema, payment, account, jobs, stats, user-detail, settings, authz | sidebar 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 |
files | src/features/files/, /files, /api/files, /api/files/$fileId | schema, account, user-detail, authz | sidebar row, dashboard card and recent-files panel |
generations | src/features/generations/, /generate, /admin/models, /api/generations/$id/$index, /api/generations/inputs*, /api/webhooks/generations/$provider, src/config/models.ts | schema, jobs, account, authz, settings | sidebar row and the console's AI section, src/config/private-paths.ts, the line that keeps the provider keys off Admin → Settings (they move there), audit labels, generations_* / admin_ai_* messages |
content (blog and docs) | src/features/content/, content/blog/, content/docs/, public/covers/, /blog, /docs | none | blog/docs links in _marketing.tsx, docs links in the landing hero.tsx and cta.tsx blocks |
audit | src/features/audit/, /admin/audit, /api/v1/admin/audit | schema, audit, stats, user-detail, payment, account, jobs, settings, authz | console nav row, seed |
email-log | src/features/email-log/, /admin/email-log, /api/v1/admin/emails | schema, email, stats, user-detail, account, jobs, settings | console nav row, seed |
affiliate | src/features/affiliate/, /affiliate, /admin/affiliate, the /r/<code> referral link | schema, payment, account, jobs, stats, settings, authz | sidebar 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 |
feedback | src/features/feedback/, /admin/feedback, /api/v1/admin/feedback, /api/feedback-image*, feedback-reply email template | schema, stats, user-detail, account, settings, authz | Help 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 |
waitlist | src/features/waitlist/, /waitlist, /admin/waitlist, /api/v1/admin/waitlist, waitlist-approved email template | schema, stats, user-detail, account, authz | the 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 |
analytics | src/features/analytics/, /api/ph (the PostHog proxy) | audit, payment, account, analytics | <PostHog /> in __root.tsx, the analytics block in app-config.ts |
demo | src/features/demo/ | jobs | marked blocks in auth.ts, env.ts, _app.tsx, _auth/login.tsx, _marketing.tsx; demo_* messages |
notifications | src/features/notifications/ | schema, notify, account | the 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.jsoncbucket 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. - Model providers without generations.
src/core/ai/(the provider adapters) and its token settings stay, for the next feature that calls a model. Images already generated are not removed from the bucket. - Legal pages without content. The MDX pipeline,
src/core/content/andcontent/legal/stay, because the terms and privacy pages are core. See Content. - Sending without the email log.
sendEmailkeeps working; deliveries are just not recorded. The email preview page and itsemail.readpermission are core. - No migration for three.
analytics,contentanddemoown no tables. You can also switch analytics off without deleting it by leaving the PostHog key empty, and demo mode is off unlessDEMO_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.
ShipKit