Check CMS items before Astro renders them
@evanion/astro-widget 0.3.0 and its patch releases up to 0.3.4, the version on npm. Values on this page were produced by running the source at 2ee3e7e, a later commit than the 0.3.0 tag, and are not re-executed.
validateItems checks the items a CMS saved against the widget types you render. By running this check as part of your build process, you can stop astro build on any save that would otherwise publish a page with missing or broken items.
The Problem: The “Silent Gap” in Production
Widgets.astro follows a “fail-soft” strategy: if it encounters a widget type it doesn’t recognize, it simply skips that item and renders the rest of the region.
While this prevents the entire page from crashing, it results in a “silent gap” in your production content. A content editor might accidentally use a stale widget type or misspell a type name, and you wouldn’t know the page is broken until a user reports it.
Solution: A Build-Time Quality Gate
You can use validateItems to scan your CMS payload before the build process starts. This turns a silent runtime failure into a loud build-time error.
import { } from '@evanion/astro-widget';
const = [
{
: 'header',
: 'listing-header',
: { : 'Brass: Birmingham' },
},
{ : 'price', : 'price-box', : {} },
{ : 'questions', : 'answer-wall', : {} },
];
const = { 'listing-header': ['title'], 'price-box': ['price'] };
const = (
,
['listing-header', 'price-box'],
,
);
; // -> [{ index: 1, id: 'price', type: 'price-box', message: 'missing field price' }, { index: 2, id: 'questions', type: 'answer-wall', message: 'unknown widget type' }]Each problem returned is a WidgetProblem object, containing the item’s index, id, type, and a message. In the example above, answer-wall is an unknown type, and the price-box item is missing its required price prop.
Integrating into the Build Process
To ensure no broken content ever ships, create a validation script (e.g., scripts/check-content.ts) and call it as part of your build command:
import { validateItems } from '@evanion/astro-widget';
import { items } from '../src/data/brass-birmingham';
const known = ['listing-header', 'price-box'];
const required = { 'listing-header': ['title'], 'price-box': ['price'] };
const report = validateItems(items, known, required).map(
(problem) =>
`item ${problem.index} (${problem.id}, ${problem.type}): ${problem.message}`,
);
if (report.length > 0) throw new Error(report.join('\n'));Update your package.json to run this check before astro build:
npx tsx scripts/check-content.ts && astro buildIf validateItems finds any problems, the script will throw an error and exit with a non-zero code, stopping the build immediately.
Enforcing Required Props
Since .astro components do not have the same compile-time prop inference as React, validateItems is the only way to ensure that an item carries the data it needs to render.
By passing a required map, you can specify which props must be present and not blank (where “blank” means undefined, null, or a string of only whitespace).
import { } from '@evanion/astro-widget';
const = [
{ : 'header', : 'listing-header', : { : ' ' } },
];
(, ['listing-header'], { 'listing-header': ['title'] }); // -> [{ index: 0, id: 'header', type: 'listing-header', message: 'missing field title' }]validateItems only checks required props for items whose type is already known. If a type is unknown, it is reported once as an UNKNOWN_TYPE fault, and its props are ignored.
Registry vs. List of Names
validateItems can accept either a full component registry or a simple list of type names as the known argument.
- List of Names: Ideal for Node scripts (like a CI build) where you cannot import
.astrocomponents. - Registry: Ideal for code running within the Astro/Vite pipeline (like a page’s frontmatter), as it ensures the validation and the rendering use a single source of truth for supported types.
import { validateItems } from '@evanion/astro-widget';
import { items } from '../examples/src/data/brass-birmingham';
import { registry } from '../examples/src/registry';
Object.keys(registry); // -> ['listing-header', 'price-box']
validateItems(items, registry); // -> []
validateItems(items, ['listing-header', 'price-box']); // -> []Next Steps: Page Layout
Your build now stops on a missing type or a blank required prop. Page data, wrappers and nested items shows how to provide page-level data to your widgets, wrap them in shared markup, and create nested layouts.