These pages document @evanion/astro-widget 0.3.4, the version on npm.
Getting started
By the end of this page, you will have a functional Astro page that renders components dynamically based on a JSON payload from a CMS. You’ll learn how to define a widget registry and use the Widgets.astro component to render your content.
Prerequisites
To follow this guide, ensure you have the following:
- An Astro project: If you don’t have one, run
npm create astro@latest. - Astro 7, from 7.3.4: This is the package’s only peer dependency.
- Node 22.12 or newer and an ESM project: Astro 7 needs Node 22.12, and
@evanion/astro-widgetis an ESM-only package.
Install astro-widget
Run the command for your preferred package manager:
npm install @evanion/astro-widget@evanion/widget (the core logic) is installed automatically. You can import all the core tools—like defineWidgets and validateItems—directly from @evanion/astro-widget.
Step 1: Write Your Widgets
An astro-widget widget is just an ordinary .astro component. It receives the item’s props as its own props.
Example: Listing Header (src/widgets/ListingHeader.astro)
---
interface Props {
title: string;
players: string;
}
const { title, players } = Astro.props;
---
<header>
<h1>{title}</h1>
<p>{players} players</p>
</header>Example: Price Box (src/widgets/PriceBox.astro)
---
interface Props {
price: string;
}
const { price } = Astro.props;
---
<p class="price">{price}</p>Step 2: Map Each Type to Its Widget
To tell the renderer which component to use for each item type, you create a registry. Use the defineWidgets helper to ensure that your type names are treated as literal types, which gives you autocomplete in your editor.
Save this as src/registry.ts:
import { defineWidgets } from '@evanion/astro-widget';
import ListingHeader from './widgets/ListingHeader.astro';
import PriceBox from './widgets/PriceBox.astro';
export const registry = defineWidgets({
'listing-header': ListingHeader,
'price-box': PriceBox,
});Step 3: Import Your CMS Data
Astro allows you to import JSON files directly. In a real project, this data would come from a CMS API, but for this example, we’ll use a local file.
Save your items as src/data/brass-birmingham.json. Then, create a TypeScript wrapper (src/data/brass-birmingham.ts) to give the import a proper type:
import type { AnyWidgetItem } from '@evanion/astro-widget';
import saved from './brass-birmingham.json';
export const items: AnyWidgetItem[] = saved;Step 4: Render the Items on a Page
Now, create a page in src/pages/brass-birmingham.astro. Use the Widgets.astro component, passing it the items and the registry.
---
import Widgets from '@evanion/astro-widget/components/Widgets.astro';
import { items } from '../data/brass-birmingham';
import { registry } from '../registry';
---
<main>
<Widgets items={items} registry={registry} />
</main>Astro serves this file at /brass-birmingham.
Open the Listing Page
Start the development server:
npm run devOpen http://localhost:4321/brass-birmingham. You will see the header and the price box rendered in the order they appear in the JSON. Note that Widgets.astro adds no extra HTML elements of its own inside the <main> tag.
The package keeps the same files under its examples/src and renders the page in its own tests, with the container API the Overview used. The code below is that test, and its last line holds the <main> element your page renders:
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
import BrassBirmingham from '../examples/src/pages/brass-birmingham.astro';
const container = await AstroContainer.create();
const html = await container.renderToString(BrassBirmingham);
html; // -> '<main><header><h1>Brass: Birmingham</h1><p>2-4 players</p></header><p class="price">649 kr</p></main>'Catch a Stale Type Before it Ships
Widgets.astro follows a “fail-soft” strategy: it simply skips any item whose type is not in the registry. While this prevents the page from crashing, it can leave gaps in your content.
To prevent this, you should run validateItems as a build-time check. Check CMS items before Astro renders them shows how to stop astro build if your CMS payload contains an unknown widget type.