Dynamic Collections
Dynamic collections let editors define new content types from the Studio UI. No code changes, no redeploys, no caret.config.ts edits. Useful when you don’t know all your collections at build time, or when non-developers need to add content surfaces.
When to use them
Section titled “When to use them”| Use case | Best fit |
|---|---|
Adding products, team, faqs to an existing site 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 |
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"] } }'Field-level options the schema builder will support when shipped:
- id — lowercase, alphanumeric, 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');const { entries } = await res.json();Use this if editors create truly arbitrary collections you don’t know at code-time.
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 JSONproducts::index → ["widget-001", ...]Held in JS maps. Lost on process restart (intended for tests).
Schema fields supported
Section titled “Schema fields supported”The schema builder UI is intentionally focused on the JSON Schema features CaretCMS understands. 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 */},},}) - Optionally delete the dynamic metadata via
delete_collection. The entries will go too — make sure you’ve migrated them first by reading and re-writing under the registered schema.