You write content in StaticOwl as drafts. Nothing you type is on the web until you build — the build compiles your content + templates into plain HTML and deploys it to a host. This guide covers the mental model, the environments every site ships with, how to run a build (dashboard, API, and MCP), how to validate before you publish, how to check status and cancel, and how a custom domain takes over from the preview host at cutover.


The mental model

edit content (drafts)  →  publish to an environment  →  build  →  live output at a host

A build is always scoped to one environment. You build dev, you build prod — each is a separate compile + deploy to a separate host.


Environments and their preview hosts

Every site is created with three environments:

id name auto-builds on publish who can publish
dev Dev yes admin, editor, publisher
staging Staging no admin, publisher
prod Production no admin, publisher

Each environment serves at its own preview host, one per environment:

https://<slug>-<env>.preview.staticowl.com

So a site with slug nightowl gets nightowl-dev.preview.staticowl.com and nightowl-prod.preview.staticowl.com. The preview hosts are always live — no DNS or TLS setup on your side. Building an environment updates that environment's preview host and nothing else.

Tip: build dev freely while you iterate. Promote to prod only when it looks right — a prod build requires that your content is published to prod first.


Running a build

Dashboard

Open Site → Builds → Build now, pick the environment, and choose a mode. When the build finishes, the panel links you to the published publicUrl.

API

# Trigger a build for an environment (default mode: incremental, default env: dev)
curl -X POST https://app.staticowl.com/api/build \
  -H "Authorization: Bearer $STATICOWL_API_KEY" \
  -H "X-Site-Id: site:mysite" \
  -H "Content-Type: application/json" \
  -d '{"mode":"full","env":"dev"}'

# Long build? Run it async — returns 202 + a statusUrl immediately, build runs in the background
curl -X POST https://app.staticowl.com/api/build \
  -H "Authorization: Bearer $STATICOWL_API_KEY" \
  -H "X-Site-Id: site:mysite" \
  -H "Content-Type: application/json" \
  -d '{"mode":"full","env":"prod","async":true}'

A successful synchronous build responds with the deploy result and every page it emitted:

{
  "ok": true,
  "envId": "dev",
  "buildId": "build:1699999999999:ab12cd",
  "artifacts": 42,
  "totalBytes": 1048576,
  "durationMs": 5123,
  "publicUrl": "https://mysite-dev.preview.staticowl.com",
  "deploy": { "ok": true, "bucket": "staticowl-sites", "filesUploaded": 42, "serveCheck": { "ok": true, "status": 200 } },
  "pages": [ { "path": "/index.html", "type": "page", "title": "Home", "bytes": 8123 } ],
  "transformErrors": [],
  "error": null
}

An async build responds 202 with { ok, buildId, envId, status: "running", statusUrl } — poll statusUrl until the status flips.

Build modes:

mode what it does
incremental rebuild only artifacts impacted by recent changes (default)
full rebuild every page
auto pick full vs. incremental based on the change surface
validate dry-run — compile in memory, return the artifact list + transform errors, deploy nothing, record no build

MCP (AI agents)

build_run { mode: "full", env: "dev" }
build_run { mode: "full", env: "prod", async: true }

build_run returns publicUrl and (in a real build) pages[]. Related build tools:

Tool Required What it does
build_run Trigger a build (mode, env, async)
build_status buildId Poll one build by id — runningok/error
build_last Status + stats of the single most recent build
build_history List recent builds (filter with env, cap with limit)
build_impact nodeIds Dry-run blast-radius: which pages would rebuild
build_rollback buildId Revert the live site to a prior build (destructive)

Agents need a write role (admin, editor, or publisher) on the site.


Validate before you publish

validate mode is the cheap sanity check. It runs the full compile in memory and returns the artifact list plus any template/transform errors, but deploys nothing and records no build — so it's the fast way to catch a broken template before spending a real build slot.

curl -X POST https://app.staticowl.com/api/build \
  -H "Authorization: Bearer $STATICOWL_API_KEY" \
  -H "X-Site-Id: site:mysite" \
  -H "Content-Type: application/json" \
  -d '{"mode":"validate","env":"dev"}'
build_run { mode: "validate", env: "dev" }

The response is distinct from a real build — note validate: true, a null buildId, and a per-artifact list:

{
  "ok": true,
  "validate": true,
  "buildId": null,
  "envId": "dev",
  "artifactCount": 42,
  "artifacts": [ { "path": "/index.html", "type": "page", "id": "page:home", "title": "Home", "bytes": 8123 } ],
  "totalBytes": 1048576,
  "durationMs": 3200,
  "transformErrors": [],
  "note": "Validate mode — nothing deployed, no BuildEvent recorded."
}

If transformErrors is non-empty, fix your templates before running the real build. (?dryRun=1 on the query string is an equivalent trigger.)


Checking status and cancelling

Status

Every real build gets a buildId. Poll it:

curl https://app.staticowl.com/api/build/status/build:1699999999999:ab12cd \
  -H "Authorization: Bearer $STATICOWL_API_KEY" -H "X-Site-Id: site:mysite"
build_status { buildId: "build:1699999999999:ab12cd" }

The status endpoint returns status (running | ok | error | cancelled) along with phase, artifacts, bytes, durationMs, publicUrl, and error. Keep polling while status is running. To see the latest build or a list, use GET /api/build/last and GET /api/build/history?env=prod&limit=20.

Cancelling

Only one full/incremental build can run per site + environment at a time — a second POST /api/build for the same pair bounces with 409 build_in_progress and points you at the running build. If a build gets stuck, release its lock:

# Either verb works — both hit the same handler
curl -X DELETE https://app.staticowl.com/api/build/build:1699999999999:ab12cd \
  -H "Authorization: Bearer $STATICOWL_API_KEY" -H "X-Site-Id: site:mysite"

curl -X POST https://app.staticowl.com/api/build/build:1699999999999:ab12cd/cancel \
  -H "Authorization: Bearer $STATICOWL_API_KEY" -H "X-Site-Id: site:mysite"

Cancel releases the lock (so a fresh build can start) and marks the build cancelled. It does not kill compile work already in flight — that finishes on the box on its own. It's a way to unstick the queue, not an abort button.


Custom domains

Preview hosts are enough to launch and share. When you want your own domain, that lives in the site's publishConfig:

field meaning
customDomain your domain (e.g. www.example.com) — drives publicUrl
distributionId the CloudFront distribution serving the domain (null = shared preview)
bucket a BYO S3 bucket (null = shared staticowl-sites)
bucketRegion region of a BYO bucket
prefix key prefix inside the bucket (defaults to the site slug)
assumeRoleArn IAM role to assume for a BYO bucket

Who gets one: custom domains are a Pro / Business feature. The Free plan is limited to one site on a preview subdomain, with no custom domain — attempting to attach one returns a plan-gate error.

How cutover works: once your domain is configured, StaticOwl provisions its CloudFront distribution and attaches the edge redirect/rewrite function to it. From that point a build sets publicUrl to https://<customDomain> instead of the preview host, and your redirects fire on the live domain automatically — the redirect table is keyed on the custom-domain host the moment the domain is set, so a rule you create applies to both the preview hosts and the live domain. No per-redirect action is needed at cutover.


Deploy targets

Where a build's artifacts go is a separate, operator-level choice controlled by STATICOWL_DEPLOY_TARGET: plain static paths on S3 (static-paths, the default "host anywhere" mode), a managed atomic-release CDN (manifest-pointer), a mirror to a Git repo you own (github), or both. Most sites never need to touch this — the default serves the preview hosts and custom domains described above.

See Deploy targets for the full comparison and the env vars each mode needs.


Gotchas & tips


See also

Related docs

Resolved at build time via {% similar %} — cosine similarity over embeddings, not tag overlap. Zero arguments.

Deploy targets — S3, GitHub, and CDN options
62% match
Product positioning — StaticOwl Docs
61% match
StaticOwl documentation — StaticOwl Docs
61% match