Content from a CMS or API
Build collections from Sanity, Contentful, Supabase, any GraphQL API, or any JSON endpoint, including functions on AWS, Google Cloud, and Azure.
A collection can come from where your content already lives. Name a source in the collection’s schema, and mira build fetches it, turns each item into an entry, and checks it against the schema like a Markdown file in content/. Routes give entries URLs, templates render them, and agents read them over MCP, the same as local content.
A collection can have a source and files in content/<name>/ at once; their entries are sorted together. Without a schema, every top level field of each item is kept.
Fields, slugs, and bodies
Each schema field is read from the item field with the same name. map reads it from somewhere else, with a dotted path such as author.name or tags.0:
Key in map | What it sets |
|---|---|
slug | The entry’s URL segment. Defaults to slug; a Sanity slug object works as is |
body | The entry’s content. Defaults to body. Markdown text, Sanity Portable Text, and Contentful Rich Text are all converted to Markdown |
| any field | That field, read from the given path |
Images in rich text are downloaded at build time and processed like local media: sized, encoded to AVIF, and served from your site rather than the CMS’s servers.
Sanity
| Key | What it is |
|---|---|
project | The project ID |
dataset | The dataset, such as production |
query | A GROQ query that returns a list of documents |
token_env | Optional. An environment variable holding a read token, for private datasets |
Only published documents are read. Without a token, Mira reads from Sanity’s CDN.
Contentful
"source":
| Key | What it is |
|---|---|
space | The space ID |
content_type | The content type ID |
token_env | The environment variable holding a Content Delivery API token |
environment | Optional. Defaults to master |
locale | Optional. Defaults to the space’s default locale |
Every entry of the type is read, a thousand at a time. Linked assets become objects with their url, title, and description, and linked entries become their fields, two levels deep. Each item also has id, created, and updated from Contentful, so "map": { "slug": "id" } works for types without a slug field.
Supabase
"source":
| Key | What it is |
|---|---|
url | The project URL |
table | The table or view |
select | Optional. Columns, in PostgREST syntax. Defaults to * |
filter | Optional. Column conditions, in PostgREST syntax, such as "eq.true" or "gte.2026-01-01" |
key_env | The environment variable holding the key. The anon key reads what row level security allows |
Rows are read a thousand at a time, so tables of any size load.
GraphQL
Any GraphQL API: Hygraph, Shopify’s Storefront API, WordPress with WPGraphQL, Strapi, Payload, and others.
"source":
items is the path to the list in the response. token_env sends a bearer token, and headers sends other headers, each naming the environment variable that holds its value. A GraphQL error in the response fails the build with its message.
Any JSON endpoint
Any HTTPS endpoint that returns a list: a REST API such as Strapi, Directus, WordPress, or Ghost, or a function you write on AWS Lambda, Google Cloud Functions, Azure Functions, Supabase Edge Functions, or Cloudflare Workers that reads from DynamoDB, Firestore, Cosmos DB, or anything else.
"source":
| Key | What it is |
|---|---|
url | An https:// URL. Query strings are allowed; credentials are not |
items | Optional. The path to the list in the response, when it is not the response itself |
token_env | Optional. An environment variable holding a bearer token |
headers | Optional. Headers, each naming the environment variable that holds its value |
Tokens and secrets
Tokens are never written in mira.config.json. Each source names an environment variable, and the build stops with the variable’s name if it is not set. Set them in your shell for local builds and in your host’s settings for builds there. Tokens never reach the cache, the build output, or error messages.
Builds, caching, and failures
mira buildfetches every source on every build, so a deploy publishes current content. Deploy again when content changes, for example with your CMS’s webhook and your host’s deploy hook.mira devreuses what it fetched for 10 minutes, so saving a file does not refetch.- Responses are cached in
.mira/cache/, separately for each set of credentials, so a copy fetched with one token is never used with another. If a source cannot be reached and a cached copy exists, the build uses it and warns, with the copy’s age. Without a cached copy, the build fails with the reason. - A source that refuses the token (HTTP 401 or 403) fails the build and its cached copy is deleted, so revoking a token stops its content from being published.
- Requests go over HTTPS only, time out after 30 seconds, and are limited to 32 MB.
- An item that does not match the schema fails the build, naming the collection and the item, such as
collections.posts.source item 4: missing required field date.