Dynamic collections
Dynamic collections let authorized clients define content types through the API at runtime. Use them when collection names and fields are not known at build time, or when editors must add collections without changing the application.
When to use them
Section titled “When to use them”| Use case | Best fit |
|---|---|
| Adding collections to Studio without redeploying | Dynamic |
| Letting clients add their own content types | Dynamic |
| Stable schema known at build time | Registered |
| Truly ad-hoc data, schema unknown | Inferred |
Creating a collection does not create a page or route on your public site. Your Astro templates must render it.
You can mix all three in the same site. Registered collections take priority; dynamic ones appear alongside them.
Creating a collection
Section titled “Creating a collection”Send a create_collection mutation through the API:
curl -X POST http://localhost:4321/api/cms/mutate \ -H 'Content-Type: application/json' \ -H 'x-caret-request: 1' \ -H 'Cookie: caret_session=<your-token>' \ -d '{ "type": "create_collection", "id": "products", "label": "Products", "icon": "📦", "creatable": true, "orderable": true, "schema": { "type": "object", "properties": { "name": { "type": "string", "title": "Product name" }, "price": { "type": "number", "minimum": 0, "title": "Price (USD)" } }, "required": ["name", "price"] } }'The example uses these collection options:
- id: starts with a lowercase letter, followed by lowercase letters, digits, hyphens or underscores. Becomes the collection name in code.
- label: human-readable name shown in Studio.
- icon: optional emoji.
- creatable: whether editors can add new entries (default:
true). - orderable: whether entries can be drag-reordered (default:
false). - schema: JSON Schema. See Schemas — JSON Schema features supported for what’s understood.
The adapter writes the metadata, creates an empty collection directory (filesystem) or initializes an empty index (KV), and the new collection appears on /admin/cms.
Editing entries
Section titled “Editing entries”Once created, the collection behaves identically to a registered one in Studio. The schema you defined drives form rendering, validation, and the template for new entries.
Deleting a collection
Section titled “Deleting a collection”Send a delete_collection mutation through the API (no UI button yet — destructive, kept gated behind the API):
curl -X POST http://localhost:4321/api/cms/mutate \ -H 'Content-Type: application/json' \ -H 'x-caret-request: 1' \ -H 'Cookie: caret_session=<your-token>' \ -d '{ "type": "delete_collection", "id": "products" }'Reading dynamic collections from code
Section titled “Reading dynamic collections from code”Dynamic collections work the same as any other collection through the live loader:
import { defineLiveCollection } from 'astro:content';import { caretLoader } from '@caretcms/core';
const products = defineLiveCollection({ loader: caretLoader('products'),});
export const collections = { products };Then use getLiveEntry('products', '...') from astro:content.
const res = await fetch('/api/cms/entries?collection=products');if (!res.ok) throw new Error('Could not load entries');const { entries } = await res.json();This is an authenticated editor endpoint, not a public content API. It is
paginated; follow the response pagination to load further entries. For public
server-rendered pages, use loadCollection() from @caretcms/core/runtime.
const res = await fetch('/api/cms/collections-metadata', { credentials: 'same-origin' });const { collections } = await res.json();// collections: CollectionMetadata[]Returns metadata for dynamic collections only. Registered ones aren’t included here.
Storage layout
Section titled “Storage layout”Where the metadata lives depends on your storage adapter.
Directory.caret/data/
Directoryproducts/
- widget-001.json entries
Directory.caretcms/
Directorycollections/
- products.json CollectionMetadata
meta::products → CollectionMetadata JSONcollections → ["pages", "site", "products", ...]products::widget-001 → entry JSONindex::products → ["widget-001", ...]Held in JS maps. Lost on process restart (intended for tests).
Schema fields supported
Section titled “Schema fields supported”Dynamic schemas use the same supported JSON Schema subset as registered schemas. See Schemas — JSON Schema features supported for the full table.
Migrating dynamic → registered
Section titled “Migrating dynamic → registered”Dynamic collections are stored as JSON Schema. Once a collection’s shape stabilizes, you can promote it to a registered schema:
- Read the metadata:
GET /api/cms/collections-metadata. - Copy the
schemaobject into your config:caret({schemas: {products: {/* the JSON Schema you copied */},},}) - Keep the dynamic metadata and entries in place. Do not call
delete_collectionto clean up metadata: it also deletes content.