Settings
Seven stored preferences, four SEO readouts and one row that writes a commit — every setting on the screen, what reads it, and what it costs to get wrong.
/admin/settings/ is admin only, because every row on it changes the deployment for everybody. It saves with JavaScript disabled, which is worth knowing if you ever debug it.
Seven values are stored, in one record in the same store your accounts live in — wherever your database URL puts it. Everything else on the screen is a readout of something the build already decided.
Project
Language — the dashboard’s language, for everyone who signs in. A searchable select over the nineteen shipped locales. The filter matches endonyms only, which is the honest limit: type “Korean” and you will not find 한국어. Labels stay in their own language, because a picker lost in the wrong language must still be readable.
Folder — a readout of the repository the CMS is editing.
Git remote — a readout, derived from the checkout with git remote get-url rather than restated in config. In a development tree it prints your real origin, which is deliberately the safety line for anybody about to press Publish.
Publish
Branch — where Publish pushes. A combo box: known branches are suggestions, and a name nothing lists yet is legal. Typed names are validated by git check-ref-format, not by a regex, stored immediately, and created on the remote by your next publish. The list’s last row is a New branch action.
This overrides git.branch in src/admin.config.ts, which is only the default. Publish reads the store, never the config.
Commit messages — the template each save names its commit with. <collection> and <ids> fill from the entry. The default reproduces content(blog): update "my-post" exactly. Also a combo, so the house templates are suggestions and your own wording is a value rather than a feature request.
Push after commit — off leaves commits local until you push. It defaults off in a development tree, so a save on your laptop cannot reach the real remote by surprise; the row’s note says which way it landed.
Preview and build
Staging preview — where your staging branch’s deploy lives. Empty hides the Preview your changes affordance on the review screen and in the editor, because a branch deploy’s URL is platform-specific and only you know it.
Build command — run before every publish, and it is the gate. A failing build refuses the push.
Build timeout — a hanging build must not hold a publish open.
General SEO
Four readouts, zero stored fields. These files regenerate on every build, so there is no state a running server could change — a stored “enabled” flag here would be a switch wired to nothing.
| Row | What it is |
|---|---|
| Sitemap | /sitemap-index.xml — regenerated from your published pages. Submit this URL to Search Console |
| robots.txt | Points crawlers at the sitemap and keeps them out of the CMS. The staging build serves Disallow: / instead |
| llms.txt | Your site’s index for AI assistants, rebuilt from published entries |
| RSS feed | Published entries newest first, linked from every page’s head |
Site — the row that writes a commit
Domain is the one control on this screen with no store key, and the reason is worth understanding.
Your site’s URL is baked in at build time, so the host cannot be a runtime value — it has to be a committed file. Pressing Connect validates the host, rewrites src/site.domain.ts, and lands that as a commit. Your next build is what makes the new domain true.
Consequences:
- The row reads the value off disk, not out of the running server’s bundle. A running server keeps its build’s host until the next deploy, and the note names both during that window.
- The validation is stricter than the config check: it refuses
localhost, address literals and LAN suffixes, because that value becomes a server-side fetch as well as a TypeScript string literal. - The write is surgical — one regex over the export line — so a reformatted
site.domain.tsthrows at save rather than saving nothing quietly.
The Site group is not a settings row: Save changes does not write it, it posts to its own endpoint and answers with its own sentence.
Licence
Paste your key, press Activate. Covered in Licence and activation.
Disconnect
Also not a settings row — an action with its own confirmation.
What is deliberately not here
Two rows describing a managed preview server — a dev-server port and a stop-when-idle timeout — were deleted because nothing read them. A settings screen promising behaviour no code has is the same drift defect this product exists to avoid, arriving through a form. The dev server you already run plus your staging deploy cover both questions.
Troubleshooting
Save changes did nothing for the Domain row. Correct — it has its own button. That is by design, since it writes a commit rather than a preference.
A branch name is refused. git check-ref-format rejected it. The message is git’s own rule, not ours.
Preview your changes is missing. The staging preview URL is empty.
The domain note says a change is pending after a deploy. The committed file and the running server’s bundle disagree until the server is rebuilt from that commit. That is the window the note exists to describe.
Settings look reset after an update. The stored record merges per field, so a record written by an older release cannot reset every setting. Genuinely lost settings mean a lost store — see Bring a database for making that survivable.