Static sites via Pull-API (Starlight, Astro, Hugo …)
Orimora can be the content source for a static site without a Git mirror in between. The site pulls the published documents of a shared folder from Orimora’s token-secured Pull-API at build time and renders them — Orimora stays the single source of truth, and there is no separate content repository to maintain.
This works with any generator that reads Markdown + frontmatter (Astro/Starlight, Hugo, Jekyll, Eleventy, …). The example uses Starlight.
How it works
Section titled “How it works”- You create a folder share for a collection in Orimora. It mints a read-only, published-only token scoped to exactly that folder — only its published documents are ever exposed (no draft leak).
- A small pull script runs before each site build, fetches the published set from the Pull-API as Markdown, and writes one Markdown file per document.
- The generator builds as usual from those files.
First: SSG or SSR? Two very different paths
Section titled “First: SSG or SSR? Two very different paths”This recipe is for static generators (SSG: Starlight, Hugo, Jekyll, Eleventy …) — they only pick up content at build time, which is why a pull script and a rebuild trigger are needed.
Server-rendered apps (SSR/ISR — Next.js, Nuxt, SvelteKit …) don’t need this recipe.
They simply call the Pull API at request time (with a cache in front) — no build,
no hook, publishing is live immediately. The API supports updatedSince for
incremental fetches.
1. Create the folder share in Orimora
Section titled “1. Create the folder share in Orimora”- In the sidebar, open a collection’s menu (kebab or right-click) and choose
“Connect to a service…”. (Requires the
collection.sharecapability — Admin-only by default.) - Pick the target type Static site (SSG) and create the share. Orimora mints a read token and shows it once — copy it now and store it like a password. The dialog also shows the API endpoint.
- Note the collection id (it’s in the collection URL) — the pull script filters on it.
You now have three values for the script: the Orimora base URL, the collection id, and the token.
Verify immediately
Section titled “Verify immediately”Before building anything, test the chain with curl:
curl -H "Authorization: Bearer <token>" \ "https://wiki.example.com/api/v1/documents?collectionId=<collection-id>&format=markdown"You must see the folder’s published documents as JSON (data: [...]), each with a
markdownText body and a slug. A 401 means the token is wrong; a 403 means the
token isn’t scoped to this collection; an empty data means the folder has no
published documents yet (drafts never appear).
2. The pull script
Section titled “2. The pull script”Drop this into your site repo as scripts/pull-orimora.mjs. It pages through the
Pull-API (offset-based) and writes src/content/docs/orimora/<slug>.md:
import { mkdir, rm, writeFile } from 'node:fs/promises';import { join } from 'node:path';
const BASE = process.env.ORIMORA_URL; // e.g. https://wiki.example.comconst COLLECTION = process.env.ORIMORA_COLLECTION; // collection idconst TOKEN = process.env.ORIMORA_TOKEN; // folder-share token (kb_…)const OUT = 'src/content/docs/orimora'; // target directoryconst PAGE = 200;
if (!BASE || !COLLECTION || !TOKEN) { throw new Error('Set ORIMORA_URL, ORIMORA_COLLECTION and ORIMORA_TOKEN');}
async function pull() { const docs = []; let offset = 0; let total = Infinity; do { const url = new URL(`${BASE}/api/v1/documents`); url.searchParams.set('collectionId', COLLECTION); url.searchParams.set('format', 'markdown'); url.searchParams.set('limit', String(PAGE)); url.searchParams.set('offset', String(offset)); const res = await fetch(url, { headers: { Authorization: `Bearer ${TOKEN}` } }); if (!res.ok) throw new Error(`Pull failed: ${res.status} ${res.statusText}`); const { data, total: t } = await res.json(); docs.push(...data); total = typeof t === 'number' ? t : docs.length; offset += PAGE; } while (docs.length < total && offset < total); return docs;}
function toMarkdownFile(doc) { // `markdownText` is the serialized body (?format=markdown). Only the title // travels as frontmatter — Starlight's docsSchema requires it. const fm = { title: doc.title }; const yaml = Object.entries(fm) .filter(([, v]) => v !== null && v !== undefined) .map(([k, v]) => `${k}: ${JSON.stringify(v)}`) .join('\n'); return `---\n${yaml}\n---\n\n${doc.markdownText ?? ''}`;}
const docs = await pull();await rm(OUT, { recursive: true, force: true });await mkdir(OUT, { recursive: true });for (const doc of docs) { await writeFile(join(OUT, `${doc.slug}.md`), toMarkdownFile(doc), 'utf-8');}console.log(`Pulled ${docs.length} document(s) from Orimora into ${OUT}`);3. Run it before the build
Section titled “3. Run it before the build”Wire it as a prebuild step so every build refreshes content:
{ "scripts": { "prebuild": "node scripts/pull-orimora.mjs", "build": "astro build" }}npm run build now pulls first, then builds. On a host like Netlify/Vercel/Cloudflare
Pages, set ORIMORA_URL, ORIMORA_COLLECTION, ORIMORA_TOKEN as build environment
variables.
4. Trigger the rebuild
Section titled “4. Trigger the rebuild”Publishing only writes to Orimora — a static site shows the content after the next build. Trigger it from your host: a scheduled build, a manual deploy, or a git push. (Automatic rebuild-on-publish for folder shares is on the roadmap.) For SSR/ISR apps the question doesn’t arise — they read the Pull-API live.
Pull-API reference
Section titled “Pull-API reference”GET /api/v1/documents — Authorization: Bearer <token>.
| Query | Meaning |
|---|---|
collectionId | Filter to one folder (the share’s collection) |
format | markdown → add a markdownText body + a derived slug |
limit | 1–100 (default 25) |
offset | Offset pagination; the response carries total |
updatedSince | ISO timestamp — only documents changed after this point |
Each item: id, title, emoji, collectionId, collectionName,
collectionSlug, updatedAt, publishedAt, tags, and — with
?format=markdown — slug and markdownText. A published-only share token
returns only published documents regardless of any status parameter.
Case study: these docs dogfood themselves
Section titled “Case study: these docs dogfood themselves”The Praxistipps section of these very docs is pulled live from Orimora using exactly this recipe — it’s the feature’s reality check:
- The tips live in a Praxistipps collection in Orimora, exposed via a folder share (read-only, published-only).
docs/scripts/pull-orimora.mjsruns before everyastro buildand fetches the set via the Pull API. Env vars:ORIMORA_URL,ORIMORA_PRAXISTIPPS_COLLECTION,ORIMORA_PRAXISTIPPS_TOKEN— set in Coolify on the docs app and passed through as Docker build args (see the caution above).- Deliberately robust: if the variables are missing or Orimora is unreachable, the script skips with exit 0 — an Orimora outage can never break the docs build; the committed placeholder index stays in place.
- Verification: the build log prints
[pull-orimora] Pulled N Praxistipp(s)(or the skip reason).
See also
Section titled “See also”- REST API overview — auth, rate limits, pagination
- WordPress — import the same folder into WordPress instead
- Kirby — the same pull pattern for Kirby
- Git mirror — push a folder to a Git repo (vs. the site pulling)