#Publishing Stories
A few ways to get a story onto a live site: the CLI (full control, including narration), the admin portal (browser upload for new stories, or editing an existing chapter in place), or a push from your own publishing system through the content API.
#Format first
Stories are plain markdown — see authoring-stories.md
for the full format. No custom parser needed for the common case.
#CLI publish (full pipeline: text + audio + timings)
node packages/pipeline/publish.mjs --brand <id> --source <path-to-your-content>
This is the complete pipeline: parses your markdown, synthesizes word-synced
narration (28 free on-device voices, or Azure premium voices with your own
key), uploads everything, and updates the manifest. Add --no-audio if you
don't have TTS set up yet — the app falls back to on-device Web Speech for
listen mode. Add --storage azure-blob if you're deployed on Azure. Full
flag reference: content-pipeline.md.
Re-publishing is cheap: only chapters whose content actually changed are re-processed (content-hash diffing), and re-narration is per block, not per chapter — editing one paragraph re-synthesizes just that paragraph, not the whole book.
Because the deployment stores its own copy of your source, a portal edit and
a CLI publish can drift out of sync. --pull fetches the deployment's
current source into your working tree before parsing, and every publish
checks the live content against what it's about to write — a real conflict
(exit code 2) names the chapter and refuses rather than silently overwriting
someone's portal edit; --force publishes anyway.
#Editing an existing chapter (admin portal)
Open your site's /admin, sign in with your operator account, and open any
book to edit its chapters directly — a markdown editor with live preview,
upload/download of the .md, image insertion, five-version history with
one-click revert, and Up/Down reordering. See
admin-guide.md for the full picture, including the
"managed externally" case for content synced in from elsewhere. A portal
save publishes text instantly; narration catches up through the bulk
narration queue,
automatically.
If a chapter was already published from a CLI run, its contentHash at
save time is checked against the live version — editing a stale copy (say,
two browser tabs open at once) is refused with an offer to reload the live
text rather than silently clobbering it.
#New story upload (admin portal, text-only)
Open your site's /admin, sign in with your operator account, and use the
"Publish a story" form: book id, title, author, and the markdown text. This
is the single-chapter shorthand format — one story, no chapters to split.
Under the hood this doesn't reimplement the pipeline in the browser or the
Worker — it commits your markdown to the site's own repo (via the GitHub
API) and triggers publish.yml, which runs the exact same
packages/pipeline/publish.mjs the CLI uses. There's only ever one publish
pipeline; the portal is just a front door to it.
Narration depends on what the publish workflow has configured. If
AZURE_SPEECH_KEY/AZURE_SPEECH_REGION are set as repo secrets, the
uploaded story gets full narration. If not, it publishes text-only — the
portal's confirmation message says which happened, honestly, every time.
Requires GITHUB_REPO and GITHUB_DEPLOY_TOKEN configured on the
deployment (see admin-guide.md); without them, upload
is disabled and the portal tells you why.
#Pushing from your own publishing system
If you already have a CMS or a release step of your own, /api/content/v1
is a documented, versioned HTTP contract it can call directly — single
chapters, whole books, or a zip/batch import for onboarding a catalogue at
once. Content pushed this way shows in the portal as read-only ("managed
externally"), naming your system, so nobody accidentally edits a copy your
system is about to overwrite on its next push. Full reference:
content-api.md in the engine repo.
#Removing or changing a story
Edit the markdown and re-run publish.mjs (CLI), edit it directly in the
portal's content manager, or DELETE it through the content API — there's
no delete button in the portal UI yet, only edit and reorder. See
content-pipeline.md for how the manifest and
content-hash state track what's live.
Found a gap? StoryLark is open source — improve these docs on GitHub.