StoryLark
← All guides

Admin Guide

Running your deployed StoryLark site from /admin — the operator's portal.

Getting in

The admin portal uses a normal account — the same email and password any reader on your site would have, just flagged as an operator. There's no shared key to type in and no separate login system to remember.

First time: a successful deploy prints a one-time setup link (https://<your-app-origin>/admin?setup=…) and ten recovery codes. Open the link to choose the email and password you'll sign in with from now on, and put the codes in your password manager — they're shown only once. From then on, /admin is an ordinary email-and-password sign-in with a normal, httpOnly session cookie.

Locked out? Three doors back in:

  1. Forgot-password email — the standard reset flow, since your admin account is a regular account. Needs RESEND_API_KEY and MAIL_FROM configured on the deployment.
  2. A recovery code — no runtime dependencies at all, so it's the door that still works when everything else is having a bad day. Each code works once.
  3. The deployment's ADMIN_KEY — last resort. Anyone with access to the deployment's own configuration can use it to mint a fresh setup link and a fresh batch of recovery codes:
    curl -X POST https://<your-app-origin>/api/admin/setup/reset \
      -H "x-admin-key: <your ADMIN_KEY>"
    

ADMIN_KEY is no longer a login by itself — its remaining jobs are minting setup links and authenticating the publish pipeline's headless calls (which can't hold a session cookie).

What you'll see

Status — your brand id, the running engine version, how many books and chapters are published, and how many devices are subscribed to push notifications.

Platform update — current version vs. latest, a link to release notes, and the exact command to run to update, ready to copy. If you've enabled one-click updates, an Install update button appears instead. See updating.md for exactly what each path does.

Stories (or Books, depending on your library's shape) — the content manager. Browse what's published, open any chapter, and edit it as plain markdown with a live preview — type, paste, or upload a .md file; download the current text; insert an image at your cursor. The last five versions of each chapter are kept with one-click revert, and a "This is a correction" toggle (on by default for existing content) controls whether readers get notified. Chapters can be reordered with Up/Down controls. Content synced in from a git repo, a JSON feed, or the content API shows as managed externally — viewable, not editable, with a link to where it actually lives.

Text saved here publishes immediately, but a Worker or Node deployment can't run the text-to-speech model — so an edited chapter is marked audio out of date until a narration worker processes it (automatically queued; see Narration below).

Brand & themes — what your site looks like, changed on a live deployment with no rebuild. Either edit the details form (name, tagline, colors, fonts) or install a .storylark-theme.zip package — from the gallery, from another of your deployments, or one you built with npm run package-theme. Both write to the same version history: the last five installs are kept, "Roll back to this" restores exactly what was installed, and "Revert to the built-in brand" undoes every override at once. A package is fully validated before anything changes, so a bad upload is a no-op, not a half-applied site.

Publish a story — book id, title, author, and markdown text. See publishing-stories.md for the full picture, including why this is text-only today and how narration gets added.

Narration

Portal saves, reverts, and anything pushed through the content API are queued for narration automatically — no deployment can run the TTS model itself, Cloudflare or Azure, so the work is tracked rather than run inline. A Narration card in the portal shows what's pending, a measured time estimate once anything has completed, and per-job failures with a Retry button. Drain the queue from wherever you already publish from:

node packages/pipeline/narrate.mjs --brand <id> --watch

Only the blocks whose text actually changed get re-synthesized — a typo fix doesn't re-narrate a whole chapter.

Turning features on

Story upload — and only story upload — needs two secrets, because it commits the markdown to your site's repo:

Secret What it's for
GITHUB_REPO owner/repo — your site's own GitHub repo
GITHUB_DEPLOY_TOKEN A fine-grained PAT scoped to just that repo, with Contents:write (the commit) and Actions:write (to start publish.yml)

Content editing needs writable content storage. On Cloudflare you already have it — the R2 bucket declared in wrangler.jsonc is what the portal writes through. On Azure / a Node host, set AZURE_STORAGE_CONNECTION_STRING (or STORYLARK_LOCAL_CONTENT for a local directory). Without either, the portal still loads and explains what isn't configured rather than failing silently.

Platform updates need none of this. The update command and the optional one-click button authenticate with your own platform login, not a stored deployment secret — see updating.md.

For proactive email notifications when a new release exists, set ADMIN_EMAIL and RESEND_API_KEY — see updating.md.

Under the hood, briefly

Nothing in the admin portal reimplements logic that lives elsewhere. The publish form commits to your repo and dispatches publish.yml, which runs the exact same packages/pipeline/publish.mjs the CLI uses. Content edits go through the same save/parse path the CLI's markdown parser is tested against byte-for-byte. The update card hands you the exact installer command the CLI documents. The portal is a front door to the real mechanisms, not a second copy of them — so there's never a question of which one is "really" correct.

If something's not working


Found a gap? StoryLark is open source — improve these docs on GitHub.