The source for docs.postiz.com, built with Mintlify.
npx mint dev # preview at http://localhost:3000
npm run check # every check, over the whole repository
npm run check:changed # only what your branch touched, which is what CI fails on
npm run check:prose # prose only, or pass files: npm run check:prose -- general/quickstart.mdx
npx mint broken-links # Mintlify's own link checkermint dev also warns about pages that exist on disk but are missing from
docs.json.
Six tabs, three of which are audiences:
| Tab | Path prefix | For | Rule |
|---|---|---|---|
| Guide | /general/** |
Everyone | How the product works. Never mentions env vars, Docker or installation |
| Cloud | /cloud/** |
Postiz Cloud users | Plans, billing, limits. The only place a price appears |
| Self-Hosting | /self-host/** |
People running their own instance | Install, configure, provider API keys, infrastructure |
| Public API | /public-api/** |
Developers | Endpoints and per-platform settings schemas |
| Automation | /cli/**, /mcp/** |
Developers | CLI and MCP |
| Contributing | /contributing/** |
Contributors | Working on Postiz itself |
The path prefix must match the tab. It is what lets the cloud support agent
be scoped to non-self-hosting content with a single rule, so a page in the
wrong prefix is a real bug, not a cosmetic one. npm run check enforces it,
with a short, commented exception list for deliberate cross-listings.
Every page belongs to exactly one tab. Listing a page in two places breaks
breadcrumbs and the previous/next pager. Link across tabs with <Card>
instead.
When something differs between cloud and self-hosted, escalate in this order:
- A value differs: an inline
<Note>starting with the words "On self-hosted Postiz:". - Short steps differ: a
<Tabs>block with tabs named exactlyPostiz CloudandSelf-hosted, cloud first. - A whole procedure differs, or an environment variable is involved: do not
inline it. Link to the other tab with a
<Card>. - The feature does not exist on one side: a
<Warning>.
Two things a Guide or Cloud page must never contain: `.env` and a
docker or pnpm command. npm run check enforces this.
- No em-dashes. Use commas, colons, parentheses or a full stop. Enforced by
npm run check. - No generated filler. Phrases like "seamlessly", "dive into", "leverage", "unlock the power of" and "it is worth noting" are rejected. Say the thing instead.
- Start a product page with a bold Where: line giving the path through the interface, so support can link someone straight to the control.
- Say a tier name ("Team and above"), never a price, outside
cloud/plans.
npm run check runs two dependency-free scripts. The same two run on every
pull request through .github/workflows/docs-checks.yml, and a failure blocks
the merge.
A pull request is judged on what it changed. Both scripts still check every
file, but with --changed only findings on lines the branch touched can fail.
Anything else is printed under "pre-existing issues outside this change" and
ignored by the exit code, so you never inherit someone else's backlog. Run
npm run check:changed locally to see exactly what CI will see.
Four things count as yours even when the line is untouched, because the branch caused them:
- a problem with a file the branch created, deleted or edited in any way, when the rule is about the whole file (missing frontmatter, an unclosed component)
- a page deleted without a redirect
- a link that broke because the branch moved or deleted its target
- an anchor that broke because the branch renamed a heading on the target page
scripts/check-docs.mjs covers structure:
| Rule | Catches |
|---|---|
navigation |
pages in docs.json with no file, files missing from docs.json, pages listed twice, and a path prefix that does not match its tab |
redirects |
a deleted page with no redirect behind it, redirects to pages that do not exist, sources that shadow a real page, duplicate sources |
links |
internal links to missing pages, #anchors with no matching heading, .mdx suffixes, links that are not rooted at / |
audience |
self-hosting mechanics on a Guide or Cloud page |
anchors |
a heading disappearing from self-host/configuration/reference.mdx, whose anchors are linked from outside these docs |
scripts/check-prose.mjs covers the writing itself:
| Rule | Catches |
|---|---|
typography |
em and en dashes, smart quotes, ellipsis characters, non-breaking and zero-width characters |
ai-marker |
phrases that read as generated filler |
typo |
common misspellings and doubled words |
naming |
product names in the wrong casing: Github, NodeJS, Javascript, lowercase postiz |
spacing |
double spaces, trailing whitespace, stray blank lines, space before punctuation |
frontmatter |
missing title, description or icon, trailing periods, descriptions over 160 characters |
markdown |
unclosed components and inline code, unlabelled code fences, heading levels, empty or vague links, images missing alt text or missing from disk |
Prose rules ignore code fences, inline code, link targets and URLs, so a command that has to be lowercase is never flagged.
When a rule is wrong about a specific line, suppress it with a comment on the line above, naming the rule:
{/* docs-lint-ignore typo */}
- My Business Business Information API{/* docs-lint-ignore-file: ai-marker, naming */} does the same for a whole
file. Both take a comma separated list. Suppress the narrowest thing that
works, and never a whole file for a single line.
Two more jobs run on each pull request and report without blocking it, because
they can go red for reasons that have nothing to do with the branch: Mintlify's
broken-links, and a staleness check of the generated tables below against a
fresh postiz-app checkout.
Some tables are derived from the Postiz source so they cannot silently drift. They live between markers:
{/* GENERATED:pricing */}
...
{/* /GENERATED:pricing */}Regenerate with a postiz-app checkout beside this repo:
node scripts/sync-facts.mjs # rewrite the tables
node scripts/sync-facts.mjs --check # fail if they are stale
POSTIZ_APP=/path/to/postiz-app node scripts/sync-facts.mjsCurrent regions: pricing and limits (from pricing.ts), platforms (from
the provider registry), analytics (from the analytics allowlists). Edit the
source, not the table.
- Create the
.mdxfile withtitle,descriptionandicon. Pages driven by anopenapientry take their description from the spec. - Add it to
docs.jsonunder exactly one tab. - Run
npm run check.
Existing URLs are linked from Discord, YouTube videos and GitHub issues. If you
must move one, add a redirects entry in docs.json in the same change. A
pull request that deletes a page without one fails.
Wildcards work, so a whole directory is one entry:
{ "source": "/old-dir/:slug*", "destination": "/new-dir/:slug*", "permanent": true }Never rename or reorder an existing ### heading in
self-host/configuration/reference.mdx: those anchors are linked externally.
The cloud support agent must never answer from self-hosting documentation: a paying cloud customer asking about connecting a channel should not be told to edit an environment variable.
In Fin, under the website source's Advanced sync settings > URLs to exclude, that is two globs:
https://docs.postiz.com/self-host/**
https://docs.postiz.com/contributing/**
This works only while the prefixes stay honest, which is the rule above. Do
not use noindex frontmatter or a robots rule instead: that would also drop
the self-hosting pages from Google, and they should stay findable.