Models
A model defines the structure and behavior of your content in Foir. Every piece of data — pages, blog posts, bookings, product records, configurations — is stored as a record that belongs to a model.
Overview
A model is a schema definition that controls what data your records can hold and how they behave. Each model has:
- Key — A unique identifier used in APIs and configuration (e.g.,
page,blog-post,booking) - Name — A display name shown in the admin dashboard
- Fields — The schema that defines the shape of each record (see Field Types)
- Config — Capability flags that control versioning, publishing, variants, and more
Model Capabilities
Every model has a config object that enables or disables features:
| Capability | Description | Default |
|---|---|---|
versioning | Track change history; each update creates a new version | Off |
publishing | Draft/publish workflow; records are not live until explicitly published | Off |
variants | Multiple content versions for different audiences (device, segment, market) | Off |
Record ownership is configured separately from these capability flags, via the model’s top-level access field (ownerPlane: customer | admin | project) — see Defining Models → Model Access.
These capabilities are mix-and-match, with one dependency chain: publishing requires versioning, and variants requires both versioning and publishing. The admin enables the prerequisites automatically when you turn on a higher-level capability. So a model can have versioning alone, versioning + publishing, or versioning + publishing + variants — but not publishing without versioning.
Whether a model is record-bearing or an embedded (inline) Block is not a capability flag — it is the model’s kind, chosen once when you create the model and locked thereafter. Record-bearing models are automatically available on the public API as standalone types; inline models are usable as a field type inside other models rather than queried on their own (see Inline Schemas). There is no separate “public API” flag.
Use Case Categories
Simple Data (no capabilities) — For application data that needs direct read/write without overhead. Records are updated in place with no drafts, versions, or publishing step. Examples: form submissions, bookings, app configuration, feature flags, counters.
Published Content (versioning + publishing) — For content that needs a review workflow. Each edit creates a new version, and content stays in draft until explicitly published. Examples: blog posts, announcements, documentation, policies.
Personalized Content (versioning + publishing + variants) — For content that changes based on audience. Variants target different devices, markets, or customer segments, and each variant has its own version history. Examples: landing pages, navigation menus, promotional banners, product descriptions.
Reusable Components (inline kind) — For models used as field types inside other models. When a model is created with the inline kind, its key becomes available as a field type. Examples: hero banners, CTAs, testimonials, product cards.
Quick Reference
| Use Case | Versioning | Publishing | Variants |
|---|---|---|---|
| Blog posts | Yes | Yes | No |
| Landing pages | Yes | Yes | Yes |
| Navigation menus | Yes | Yes | Yes |
| Form submissions | No | No | No |
| App configuration | No | No | No |
| Event bookings | No | No | No |
| Hero banners (inline) | No | No | No |
In the Admin
- Go to Settings > Models
- Click Create Model
- Fill in:
- Name: e.g., “Blog Post”
- Key: auto-generated from the name (e.g.,
blog-post), or set manually
- Choose the model’s kind — record-bearing or embedded (inline) Block. This is set at creation and locked afterward.
- Configure capabilities (versioning, publishing, variants)
- Add fields to the schema
- Click Save
Model schemas track changes with version history. When you edit a model’s fields, a new schema version is created, and you can restore a previous schema version if needed.
Via the CLI
List all models
foir models listGet a model by key
foir models get blog-postCreate a model
foir models create --data '{
"key": "blog-post",
"name": "Blog Post",
"pluralName": "Blog Posts",
"fields": [
{ "key": "title", "name": "Title", "type": "text", "required": true },
{ "key": "slug", "name": "Slug", "type": "text", "required": true },
{ "key": "body", "name": "Body", "type": "richtext" }
],
"config": {
"versioning": true,
"publishing": true
}
}'foir models create takes a single model definition via --data/-d (inline JSON) or --file/-f (a JSON file). For bulk or declarative model management — defining your models in code and syncing them all at once — use foir push instead (see Config System).
Update a model
The model is identified by its key (positional argument); pass the changed fields with --data:
foir models update blog-post --data '{
"name": "Blog Post",
"fields": [...]
}'Delete a model
foir models delete blog-postList schema versions
foir models versions blog-postVia the API
List models
The public API exposes a single models query that returns the project’s models as a plain list. It takes an optional category filter and requires the schemas:read scope.
query {
models(category: "content") {
key
name
description
pluralName
pluralKey
category
capabilities {
isVersioned
isPublishable
hasVariants
}
fields {
key
label
type
required
}
}
}There is no id field, no raw config JSON, and no pagination/search/filter arguments — capabilities are surfaced through the capabilities object:
| Field | Meaning |
|---|---|
isVersioned | The model tracks version history |
isPublishable | The model has a draft/publish workflow |
hasVariants | The model supports content variants |
The models query lists every model in the project, inline models included. The difference is in the typed query layer: only record-bearing models get their own typed root queries; inline models have no standalone records, so they surface only as field types embedded in other models.
Config System
Models can also be defined in code using defineModel in your foir.config.ts file. This is useful for version-controlling your model schemas alongside your application code.
See Model Configuration for the full reference.
Best Practices
- Choose the minimal set of capabilities each model needs. Simpler models are faster and easier to work with. Remember the dependency chain: publishing implies versioning, and variants implies both.
- Use clear, descriptive keys. The key is permanent and used throughout your APIs and configuration.
- Enable
inlinefor models meant to be embedded as field types rather than queried as standalone records. - Use
foir pushto sync model definitions declaratively from your codebase, rather than creating them one at a time. - Track schema changes with meaningful descriptions so you can review and roll back if needed.