Skip to content

Live collections

Astro 6 introduced stable live content collections, which use a runtime loader instead of static files. CaretCMS provides caretLoader to expose CMS data through astro:content on Astro 6 and 7.

Use this guide after configuring server delivery and creating content in storage. A live collection does not create entries or Astro routes. It tells Astro how to read an existing CMS collection.

getLiveEntry('pages', 'home') then returns the latest CMS data on each request.

  • Astro 6.0+ stable
  • Server delivery — output: 'server' (live collections are dynamic by definition)
  1. Create src/live.config.ts

    src/live.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, error: homeError } = await getLiveEntry('pages', 'home');
    if (homeError) throw homeError;
    const { entries: allProducts, error: productsError } = await getLiveCollection('products');
    if (productsError) throw productsError;
    ---
    <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, error: homeError } = await getLiveEntry('pages', 'home');
    if (homeError) throw homeError;
    ---
    <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 page reads content during the server request, and the explicit bindings connect the displayed fields to the editor. Both use the same storage.

For authenticated editors, the loader hides binding metadata inside string values. Text rendered in the page body uses that metadata for automatic click-to-edit. Before using a live string in a non-editable HTML attribute, remove the metadata with stegaClean:

---
import { stegaClean } from '@caretcms/core';
import { getLiveEntry } from 'astro:content';
const { entry: home, error } = await getLiveEntry('pages', 'home');
if (error) throw error;
const href = stegaClean(home?.data.ctaHref ?? '/');
const description = stegaClean(home?.data.description ?? '');
---
<meta name="description" content={description} />
<a href={href}>{home?.data.ctaLabel}</a>

This applies to values used in href, src, alt, <title>, metadata, and other attributes. Keep visible text encoded so the editor can discover its binding.

Caret’s loader loads the collection; it does not implement query filters. Filter the returned array in your page:

---
import { getLiveCollection } from 'astro:content';
const { entries, error } = await getLiveCollection('products');
if (error) throw error;
const inStock = entries.filter((entry) => entry.data.in_stock === true);
---

This loads all entries into memory. For large collections, use getLiveEntry for known IDs or implement a dedicated query layer.

Check the returned error before using entry or entries:

---
import { getLiveEntry } from 'astro:content';
const { entry: home, error } = await getLiveEntry('pages', 'home');
if (error) throw error;
const headline = home?.data.headline ?? 'Welcome';
---

The loader returns a CaretLoaderError for storage failures and invalid single entries. Collection reads skip invalid entries and log validation details while returning valid entries. A missing or unpublished entry is unavailable to a public read; handle that case with a fallback or your own 404 response.

The config filename and result shape follow Astro’s live content loader API.

On Astro 7, published results include cache tags caret:<collection> and caret:<collection>::<id>. Editor and private-preview results omit these hints. Tags are metadata for a compatible cache; they do not install a CDN or configure its invalidation for you. Astro 6 does not use the Astro 7 tags.

The loader reads storage on every render. If you add page, adapter, or CDN caching, keep authenticated and draft responses private and uncached, and invalidate public responses after publication. Missing cache hints alone do not prevent an independently configured proxy from caching a page.

Add a schema to the Astro live collection to type and validate its returned data. This is separate from registering a schema with CaretCMS for Studio:

src/live.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,
});
export const collections = { pages };

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