Checking a CMS payload
@evanion/react-widget 0.3.0 and its patch releases up to 0.3.3, the version on npm. Values on this page were produced by running the source at f9b1d93, a later commit than the 0.3.0 tag, and are not re-executed.
validateItems checks a payload that the TypeScript compiler never saw—such as a JSON page exported from a CMS—and returns every structural problem found. This allows you to catch “broken” content in a CI script or a publish webhook before it reaches your users.
The Problem: The “Untyped Payload” Gap
While defineItems ensures your code is type-safe, the data coming from your CMS is just plain JSON. A content editor might accidentally use a widget type that no longer exists in your code, or leave a required field blank.
If you pass this raw JSON directly to <Widgets>, the renderer will simply skip items of an unknown type and draw items with blank fields as they are. In production, this results in a “silent failure”—a gap in your page that you only discover after the page is live.
Solution: Runtime Validation in CI/CD
@evanion/react-widget re-exports validateItems from @evanion/widget. You can use this function in a build script or a webhook handler to block the publishing of broken content.
import { } from '@evanion/react-widget';
const : unknown = .(
'[{"id":"root","type":"listing","props":{"title":"Root"}},{"id":"raffle","type":"raffle","props":{}}]',
);
const = (, ['listing', 'booking']); // -> [{ index: 1, id: 'raffle', type: 'raffle', message: 'unknown widget type' }]How the Validation Works:
- Payload: The parsed JSON from your CMS, typed as
unknown. - Known Types: The list of widget types your renderer actually supports.
- Required Props: (Optional) A map that defines which props are mandatory for each widget type.
validateItems returns an array of WidgetProblem objects. If the list is not empty, your CI script can set process.exitCode = 1 to fail the build, or a webhook can send an error message back to the CMS editor.
Enforcing Mandatory Props
You can ensure that a widget is not just “known” but also “complete.” By passing a required map, you can reject items that have missing or blank props (where “blank” means undefined, null, or a string of only whitespace).
const { } = ({
: { : },
});
const : unknown = [
{ : 'root', : 'listing', : { : ' ' } },
];
const = (, { : ['title'] }); // -> [{ index: 0, id: 'root', type: 'listing', message: 'missing field title' }]validateItems reports when essential data—like a listing’s title—is missing, before the renderer even tries to draw the component.
Handling Nested Problems
Because widgets can contain other widgets, validateItems recursively scans the children array of every item. If a nested child is faulty, the WidgetProblem reports its index among its own siblings.
import { } from '@evanion/react-widget';
const : unknown = .(
'[{"id":"tonight","type":"showcase","props":{"title":"On the table tonight"},"children":[{"id":"hive","type":"listing","props":{"title":"Hive"}},{"id":"raffle","type":"raffle","props":{}}]}]',
);
const = (, ['showcase', 'listing']); // -> [{ index: 1, id: 'raffle', type: 'raffle', message: 'unknown widget type' }]To find the exact location of a nested error, you can search the children of your items for the reported id.
Rendering a Validated Payload
Once a payload has passed validation, you can safely cast it to the expected item type and render it.
import type { } from '@evanion/react-widget';
const = { : };
const { , } = ({ });
function (: unknown): <typeof >[] {
const = (, { : ['title'] });
return . === 0
? ( as <typeof >[])
: [];
}
const : unknown = .(
'[{"id":"root","type":"listing","props":{"title":"Root"}}]',
);
const : unknown = .(
'[{"id":"root","type":"listing","props":{"title":" "}}]',
);
const = (< ={()} />); // -> '<section><div data-widget-id="root" data-widget-type="listing"><h3>Root</h3></div></section>'
const = (< ={()} />); // -> '<section></section>'Important: validateItems checks for the existence of props, not their type. If a title is required and the CMS sends a number instead of a string, validateItems will pass it. If you need strict type validation for your JSON, consider using a schema validator like Zod alongside validateItems.
What Happens to Unvalidated Items?
If you skip the validation step and pass a raw payload to <Widgets>, the renderer follows a “fail-soft” strategy:
- Skip: Any item with an unknown
typeis simply not rendered. - Warn: In development (
NODE_ENV !== 'production'), the renderer logs a warning to the console:Unknown widget type "..." for widget ID "...". Skipping render. - Silence: In production, these warnings are suppressed. A stale widget type will leave a silent gap in your page.
This is why running validateItems in your CI/CD pipeline is the only way to guarantee that every item in your CMS is actually rendered on the page.