Your content model,
written by an agent
Declare a content type, fill it with records, and every record becomes a real page with its own URL and SEO. No migration, no redeploy, and no hand-built template per row.
Nine field types, no migration
A collection is a schema you describe once: text, number, bool, select, date, richtext, media, relationship, blocks. The schema lives in a JSON column, so adding a field never touches the rows you already have.
{
"slug": "case-studies",
"route_base": "case-studies",
"fields": [
{ "id": "client_name", "type": "text", "required": true },
{ "id": "industry", "type": "select" },
{ "id": "body", "type": "richtext" }
]
}
A hundred records, one transaction
Push up to 100 records in a single call. It is one transaction: every record lands, or none do. A half-imported catalogue is the worst outcome, so it is the one outcome that cannot happen.
All or nothing
AtomicOne invalid row rejects the whole batch, and the error names every field the schema declares.
Rejected, not dropped
Fail loudA field your schema never declared fails the write. It is not quietly discarded — you find out now, not in a month.
Every record is a page, not a row
Set a route_base and each record gets a real URL, its own SEO title and description, and its own Open Graph image. Nobody writes a template per record.
# one collection, one route_base
/case-studies/acme-migration
/case-studies/globex-rollout
/case-studies/initech-replatform
# each carries its own title, description and OG image
Nothing goes public by accident
Editing a draft applies straight away. Making a record public does not. A status change is a two-step: ask once and you get a preview, a single-use token and a hash of exactly what is about to change; call again with the token to commit.
Preview first
dry_runReturns what would change, plus a token that expires. Nothing is written.
Then commit
confirmDeleting a collection works the same way, and tells you how many records go with it.
Links with a query budget you can state
Link a case study to its author, a product to its category. Cardinality is many-to-one or one-to-many, and the cost is fixed rather than vague: one lookup to resolve the URL, one joined query for the page of records, then one batched fetch per relationship — two for a reverse walk. Never one query per record.
A page of fifty records with two relationships costs four queries, not a hundred and four. Ask for depth zero and you get raw keys with no extra queries at all.
The content the blog module was never for
Most client sites need a type the blog cannot hold: case studies, the team page, a product range, testimonials, a changelog for the client's own product. The usual answers are a hand-built page per item, or bending posts into a shape they resist.
One shape per client
Per siteEach tenant defines its own types. A law firm's case notes and a studio's project list are different collections, not the same compromise.
On their domain
One deployRecords render on the client's own site through the same deploy as everything else. No second system to explain at handover.
Schema and content in one session
A typed CMS is not new. What is new is who holds the pen: elsewhere a person defines the model and an agent fills it in afterwards. Here both arrive together, through one API, behind the same confirm step.
spideriq content collections create \
--slug case-studies --label "Case Studies" \
--route-base case-studies --public --schema-file ./schema.json
spideriq content collections records bulk case-studies \
--file ./records.json
spideriq content collections records update case-studies $ID \
--status published --dry-run
spideriq content deploy