#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:
- Forgot-password email — the standard reset flow, since your admin
account is a regular account. Needs
RESEND_API_KEYandMAIL_FROMconfigured on the deployment. - 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.
- 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
- "not_configured" errors — the GitHub secrets above aren't set on this deployment. They affect story upload only.
- Update card shows the command, not a button — you haven't enabled one-click updates for this deployment; the command always works either way.
- Status shows
—for book/chapter counts — the manifest was unreachable when the page loaded; try refreshing. - A chapter says "audio out of date" — narration hasn't caught up yet; run a narration worker or check the Narration card for a failure.
- Story upload says "committed but publishing failed to start" — the
markdown file made it into your repo, but the GitHub Actions dispatch
didn't fire. Check your repo's Actions tab and re-run
publish.ymlmanually if needed.
Found a gap? StoryLark is open source — improve these docs on GitHub.