Skip to content

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.

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.

Send a create_collection mutation through the API:

curl
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.

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.

Send a delete_collection mutation through the API (no UI button yet — destructive, kept gated behind the API):

curl
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" }'

Dynamic collections work the same as any other collection through the live loader:

src/live.config.ts
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.

Where the metadata lives depends on your storage adapter.

  • Directory.caret/data/
    • Directoryproducts/
      • widget-001.json entries
  • Directory.caretcms/
    • Directorycollections/
      • products.json CollectionMetadata

Dynamic schemas use the same supported JSON Schema subset as registered schemas. See Schemas — JSON Schema features supported for the full table.

Dynamic collections are stored as JSON Schema. Once a collection’s shape stabilizes, you can promote it to a registered schema:

  1. Read the metadata: GET /api/cms/collections-metadata.
  2. Copy the schema object into your config:
    caret({
    schemas: {
    products: {/* the JSON Schema you copied */},
    },
    })
  3. Keep the dynamic metadata and entries in place. Do not call delete_collection to clean up metadata: it also deletes content.