Singletons
Some content is not a list. A homepage, a settings record, a contact page — there is one of each, forever. Trokky calls these singletons.
Declaring one
Section titled “Declaring one”A singleton is a schema that says so:
export const homepageSchema = { name: 'homepage', title: 'Homepage', type: 'document', singleton: true, fields: { heroTitle: { type: 'string', title: 'Hero title' }, heroImage: { type: 'media', title: 'Hero image' }, intro: { type: 'richtext', title: 'Introduction' }, },}That one line changes four behaviours:
- Creating a second document in the collection is rejected.
PUTto a document id becomes an upsert, so the document can be created at a known id.- Backups restore the document under its original id instead of a new one.
- The Studio offers to edit the document rather than to create a list of them.
The schema decides, the structure presents
Section titled “The schema decides, the structure presents”If you use a custom structure.ts, you will also write something like this:
{ type: 'singleton', title: 'Homepage', schemaType: 'homepage', documentId: 'home',}These two declarations look redundant. They are not, and understanding the difference is worth two minutes.
The schema owns the invariant. “This collection holds exactly one document” is a fact about your content model. It is what the create guard, the upsert path, and trokky restore all read.
The structure owns the presentation. documentId says which document this navigation entry opens and auto-creates. It is a routing detail, and it is why documentId does not have to match the schema name — a schema called homepage can perfectly well store its document as home.
So: the structure entry is optional. The schema flag is not.
What happens if you only declare it in the structure
Section titled “What happens if you only declare it in the structure”Nothing, at first. This is what makes it dangerous.
The Studio renders the singleton view because the structure told it to. The site reads the document and finds it. Every request behaves correctly, and the mistake stays invisible for as long as you never restore a backup.
Then you restore one. trokky restore decides between an id-preserving upsert and a plain create by reading the schema flag, which is absent — so the document is created with a freshly generated id. Your structure entry still points at home, which no longer exists. The Studio’s singleton view breaks, any frontend query by id breaks, and if the collection held a second document it is rejected outright.
This has happened to real sites. On one, it cost two documents out of fifty and every singleton id in the project.
Since 2.0.1, Trokky refuses to start when a structure entry claims a singleton its schema does not declare, and names each offending collection:
Structure and schemas disagree about singletons: - 'homepage' is presented as a singleton but its schema does not set 'singleton: true'Before you deploy, trokky migrate --dry-run reports the same divergence without starting anything.
isSingleton is not the flag
Section titled “isSingleton is not the flag”// Silently does nothing on a schema.isSingleton: trueisSingleton is a key on Studio structure items. Schemas are validated with unknown keys stripped, so writing it on a schema produces no error, no warning, and no effect — the collection is simply not a singleton, with all the consequences above.
The key is singleton. This one has bitten a production site.
Auto-creation
Section titled “Auto-creation”A singleton document is created on first read, using the schema’s defaults, so the Studio has something to open before anyone has saved anything.
You can turn that off in the structure entry:
{ type: 'singleton', schemaType: 'homepage', documentId: 'home', options: { autoCreate: false },}With auto-creation disabled, reading a singleton that does not exist returns 404 rather than conjuring an empty document.
Auto-creation only ever creates the singleton’s own document id, and only when the collection is empty. It will not create a second document in a collection that already has one, whatever id you request.
Querying a singleton
Section titled “Querying a singleton”From the client SDK, ask for the collection rather than a specific id:
const homepage = await client.singleton<homepageDocument>('homepage').fetch()That queries the collection and takes the single document, so it keeps working regardless of what the document’s id happens to be. Prefer it over fetching a hardcoded id — it is the form that survives a restore.