Schema Publishing
The Foir GraphQL schema served by the public API is versioned. Editing a model, operation, context dimension, auth provider, or customer profile schema in the admin app puts the change into a draft state — public API consumers continue to see the previously published schema until you explicitly Publish it.
This protects production traffic from breaking the moment a schema-shaping change is saved. You stage your schema and content changes together, then promote them in one step when both sides are ready.
How it works
Every schema-surface entity carries two states:
- Draft — the live state in the admin app. Edits land here immediately.
- Published — the state served to public API keys (
pk_*).
The two move independently. Saving a draft in the admin app does not change the published state; clicking Publish does.
Channels
Public API requests choose between the two states via the X-Foir-Schema-Channel header:
| Channel | Default for | Header |
|---|---|---|
| Published | Public keys (pk_*), every caller without the header | (omit, or published) |
| Draft | Secret keys (sk_*) with the drafts:read scope | X-Foir-Schema-Channel: draft |
Public keys cannot opt into the draft channel — the header is silently ignored. Secret keys without the drafts:read scope receive a 403 if they request draft.
# Production: served the published schema
curl -X POST https://api.foir.io/graphql \
-H "x-api-key: pk_live_abc123..." \
-d '{"query": "{ pages { id title } }"}'
# Staging preview: served the draft schema
curl -X POST https://api.foir.io/graphql \
-H "x-api-key: sk_live_xyz789..." \
-H "X-Foir-Schema-Channel: draft" \
-d '{"query": "{ pages { id title subtitle } }"}'The draft response is stamped with Cache-Control: no-store so CDNs don’t cache an in-flight schema.
What gets versioned
The publish lifecycle covers everything that shapes the public GraphQL surface:
- Models — type names, field shapes, configuration flags
- Operations — custom mutation signatures, input/output schemas, async lifecycle
- Context dimensions — variant resolution inputs
- Auth providers — login mutations exposed in the schema
- Customer profile schema — fields available on the customer profile type
Records are unaffected by this lifecycle — they have their own per-record draft/publish flow controlled by the preview: true query argument.
Recommended workflow
- Edit in admin. Add fields, rename, change configuration. Save the draft.
- Update your application. Bump types, write content, deploy your code.
- Preview from staging. Point your staging deployment at the production API with a secret key +
X-Foir-Schema-Channel: draft. Verify your queries succeed against the new schema. - Publish. When staging looks good, click Publish in the admin app. The public schema flips for every consumer within seconds.
The Publish action shows a diff of every change about to go live, with breaking changes (renames, removed fields, narrowed types) flagged. Additive changes (new optional fields, new types) are safe to publish at any time.
Publishing many changes at once
If you’ve edited several models in service of one feature, use Publish all pending changes from the project-level pending-changes view. This promotes every dirty entity in a single transaction so the schema flips coherently rather than entity-by-entity.
Newly created entities
A brand-new model, operation, context dimension, or auth provider is auto-published on create — adding a new GraphQL type or mutation can’t break any existing query. Subsequent edits stay in draft until you Publish them.
Unpublishing
Unpublish removes an entity from the public schema entirely. Any client query that selected the type starts failing on the next schema rebuild. Use it deliberately — for entities you’re retiring, not for transient configuration changes.
Failure modes
- Public-key client calls a field that doesn’t exist on the published schema. The query is rejected at validation time. To get this field for that consumer, publish the change.
- Customer code wants to read a draft field before publish. Use a secret key with
drafts:readand theX-Foir-Schema-Channel: draftheader. Don’t use this in production traffic — only in staging or preview environments. - Published schema references a model that’s been deleted. The published snapshot persists. Republish to remove it from the public schema (or unpublish that specific model).