Skip to content

Live Collections

Astro stabilized live content collections — collections backed by a runtime loader instead of static files — in 6.0, after introducing them experimentally in 5.10. CaretCMS ships a loader (caretLoader) that wires your CMS data into astro:content on either version.

End result: getLiveEntry('pages', 'home') returns the latest CMS data on every request.

  • Astro 6.0+ stable or Astro 5.10+ experimental
  • output: 'server' (live collections are dynamic by definition)
  1. Create src/caret.config.ts

    src/caret.config.ts
    import { defineLiveCollection } from 'astro:content';
    import { caretLoader } from '@caretcms/core';
    const pages = defineLiveCollection({
    loader: caretLoader('pages'),
    });
    const site = defineLiveCollection({
    loader: caretLoader('site'),
    });
    const products = defineLiveCollection({
    loader: caretLoader('products'),
    });
    export const collections = { pages, site, products };

    One defineLiveCollection call per CMS collection you want to query.

  2. Query in page frontmatter

    src/pages/index.astro
    ---
    import { getLiveEntry, getLiveCollection } from 'astro:content';
    const { entry: home } = await getLiveEntry('pages', 'home');
    const { entries: allProducts } = await getLiveCollection('products');
    ---
    <h1>{home?.data.headline ?? 'Default headline'}</h1>
    <ul>
    {allProducts.map((p) => (
    <li>{p.data.name} — ${p.data.price}</li>
    ))}
    </ul>

    getLiveEntry and getLiveCollection come from astro:content — they’re not CaretCMS APIs. Astro routes them to the registered loader behind the scenes.

  3. Pair with inline editing (optional)

    ---
    import { getLiveEntry } from 'astro:content';
    const { entry: home } = await getLiveEntry('pages', 'home');
    ---
    <main data-caret-scope="pages::home">
    <h1 data-caret="headline">{home?.data.headline ?? 'Default headline'}</h1>
    <p data-caret="intro">{home?.data.intro ?? 'Default intro'}</p>
    </main>

    The CMS data hydrates at SSR (getLiveEntry), the response-rewriting middleware ensures the latest stored value lands in the HTML, and the inline editor lets editors click to change it. One source of truth.

getLiveCollection accepts a filter:

---
const { entries } = await getLiveCollection(
'products',
(entry) => entry.data.in_stock === true,
);
---

getLiveEntry returns { entry: undefined, error: undefined } if the entry doesn’t exist. Always nullish-coalesce:

const headline = home?.data?.headline ?? 'Welcome';

If the loader fails (storage error, unknown collection, network blip), it throws a CaretLoaderError. By default this propagates to Astro’s error boundary.

The loader has no built-in cache — every render reads from storage. For most setups (filesystem, KV) this is fast enough. If you need caching:

  • Add Astro’s Cache-Control headers on the page response
  • Wrap your storage adapter with a TTL cache
  • Use a CDN in front of your origin

If you use registered Zod schemas, you can type the data field on entries:

src/caret.config.ts
import { defineLiveCollection } from 'astro:content';
import { caretLoader } from '@caretcms/core';
import { z } from 'zod';
const PageSchema = z.object({
headline: z.string(),
intro: z.string(),
});
const pages = defineLiveCollection({
loader: caretLoader('pages'),
schema: PageSchema,
});

getLiveEntry('pages', 'home') then returns typed data.