Skip to main content
/admin/prompts shows you which prompts your app has registered, what the latest version of each is, and when it was last updated. It’s intentionally read-only — publishing a new version is a deliberate act that should go through code review or a server action, not a freeform admin panel. This page is the audit trail; the editing happens elsewhere.

Prerequisites

  • Your user record has role = 'admin'. The /admin/* layout calls requireAdmin() and 403s otherwise.
  • Prompts registered in the database — usually seeded by your app at startup via the prompt registry pattern (see /docs/ai/prompts).
  • The prompt and prompt_version tables exist (created by pnpm db:push from src/db/ai.schema.ts).

Step-by-step

  1. Sign in as an admin at /login, then visit /admin/prompts.
  2. Scan the table:
    ColumnWhat it shows
    slugStable identifier the app uses to fetch the prompt
    name + descHuman label and one-liner about what it does
    latest versionvN from a join to prompt_version
    updatedprompt.updatedAt, formatted via date-fns
  3. Sort & filter — currently neither is implemented. Rows come back sorted by updatedAt desc with a hard limit(100). Either rebuild the page with sortable columns or paginate (see pitfall 1).
  4. To publish a new version, write SQL or a server action that inserts into prompt_version and updates prompt.latestVersionId. The full registry pattern is documented at /docs/ai/prompts.

How the join works

The page does one query for prompts, then resolves each latestVersionId in a follow-up loop:
const rows = await db.select().from(prompt)
  .orderBy(desc(prompt.updatedAt)).limit(100);

const versionMap = new Map<string, number>();
for (const r of rows) {
  if (!r.latestVersionId) continue;
  const v = await db.select({ version: promptVersion.version })
    .from(promptVersion)
    .where(eq(promptVersion.id, r.latestVersionId)).limit(1);
  if (v[0]) versionMap.set(r.id, v[0].version);
}
This is a textbook N+1 — fine for the 100-row admin volume, but rewrite to a join the moment that limit becomes a problem.

Verify it works

  1. Register a new prompt via your registry’s register() call (or insert directly into the prompt table for testing).
  2. Refresh /admin/prompts — the new row should appear at the top (sorted by updatedAt desc).
  3. Insert a prompt_version row pointing back to the prompt and update prompt.latestVersionId — refresh, the version column should now show v1.
  4. Delete the test rows when done; there’s no UI delete (yet).

Common pitfalls

  1. Pagination missing. The hard limit(100) silently truncates large registries. Add a query-string ?page=N or switch to keyset pagination before you cross 100 prompts.
  2. N+1 query under load. Acceptable at admin volume, painful past a few hundred prompts. Rewrite as a LEFT JOIN promptVersion ON promptVersion.id = prompt.latestVersionId in one query.
  3. Latest version shows . Means prompt.latestVersionId is null — the prompt has no published version yet. Insert a prompt_version row and set latestVersionId on the parent.
  4. Edits don’t show up. Updates to prompt_version.text don’t bump prompt.updatedAt. Either touch the parent row in the same transaction or sort by the version’s createdAt instead.
  5. No edit UI. This is the design — read-only on purpose. Don’t add a freeform editor unless you also add audit logging and a review workflow.

Official docs