The headless CMS built for Astro
Composable block-based pages, reusable content, hierarchical menus, and a visual editor — all self-hosted and open source. Install in minutes, deploy anywhere.
$ npx create-wolly my-site
Creating project in ./my-site...
$ cd my-site && npm run migrate && npm run seed
$ npm run dev
WollyCMS running at http://localhost:4321
Admin UI at http://localhost:4321/admin
Features
Everything you need to build content-driven sites
A complete CMS with the composition power of Drupal, the editing experience of Storyblok, and the simplicity you actually want.
Block Composition
Build pages from typed, reusable blocks arranged in named regions. Hero, content, sidebar, features — you define the layout, editors fill in the blocks.
Astro-Native
First-class @wollycms/astro package with typed client, BlockRenderer component, menu helpers, image optimization, and SEO utilities.
Reusable Block Library
Create a block once, use it on any page. Edit once, updates everywhere. Usage tracking prevents accidental deletion.
Visual Page Builder
Drag-and-drop blocks between regions with live preview. Click any block in the preview to edit inline. Mobile, tablet, and desktop preview modes.
Hierarchical Menus
Deep-nesting support, multiple independent menus, container items, and helper functions for navigation, breadcrumbs, and active states.
Clean REST API
Structured JSON content API with batch fetching, full-text search, ETag caching, and rate limiting. Every response is typed.
Deploy Anywhere
Docker, Cloudflare Workers, bare Node.js — your choice. SQLite or PostgreSQL for the database. S3/R2 for media.
AI Coding Friendly
Clean architecture, typed APIs, and convention-over-configuration make WollyCMS ideal for building with Claude Code or Copilot.
Custom Content Types
Define your own content types with custom fields, regions, and block restrictions. Match any content model — Drupal, WordPress, or your own.
Developer Experience
Fetch content in 3 lines
The @wollycms/astro package gives you a typed client, component rendering, menu helpers, SEO utilities, and responsive images out of the box.
- Full TypeScript types — no casting, no guessing
- BlockRenderer maps CMS blocks to your Astro components
- Menu helpers for nav, breadcrumbs, and active states
- WollyImage component with responsive srcset and lazy loading
class="text-slate-500">---
class=class="text-amber-400">"text-sky-400">import class="text-slate-300">{ wolly } class=class="text-amber-400">"text-sky-400">from class="text-amber-400">'../lib/wolly';
class=class="text-amber-400">"text-sky-400">import * as blocks class=class="text-amber-400">"text-sky-400">from class="text-amber-400">'../lib/blocks';
class=class="text-amber-400">"text-sky-400">import class="text-slate-300">{ BlockRenderer } class=class="text-amber-400">"text-sky-400">from class="text-amber-400">'@wollycms/astro';
class=class="text-amber-400">"text-sky-400">import Layout class=class="text-amber-400">"text-sky-400">from class="text-amber-400">'../layouts/Default.astro';
class=class="text-amber-400">"text-sky-400">const slug = Astro.params.slug || class="text-amber-400">'home';
class=class="text-amber-400">"text-sky-400">const page = class=class="text-amber-400">"text-sky-400">await wolly.pages.getBySlug(slug);
class=class="text-amber-400">"text-sky-400">const menu = class=class="text-amber-400">"text-sky-400">await wolly.menus.get(class="text-amber-400">'main');
class="text-slate-500">---
class="text-sky-400"><Layout title=class="text-slate-300">{page.title} menu=class="text-slate-300">{menu}>
class="text-sky-400"><BlockRenderer
blocks=class="text-slate-300">{page.regions.hero}
components=class="text-slate-300">{blocks}
/>
class="text-sky-400"><BlockRenderer
blocks=class="text-slate-300">{page.regions.content}
components=class="text-slate-300">{blocks}
/>
class="text-sky-400"></Layout> Infinite Page Types
Define any content model
Blog posts, landing pages, product pages, case studies — define the page type, assign regions and blocks, and WollyCMS handles the rest. Every page type gets its own fields, regions, and block restrictions.
- Custom fields per page type — dates, tags, authors, anything
- Named regions — hero, sidebar, content, footer — you decide
- Block restrictions per region control what editors can use
- Full taxonomy system for categories, tags, and custom vocabularies
class="text-slate-500">// Register page types in WollyCMS admin
class=class="text-amber-400">"text-sky-400">const blogPost = {
type: class="text-amber-400">"blog_post",
fields: [
{ name: class="text-amber-400">"author", type: class="text-amber-400">"text" },
{ name: class="text-amber-400">"date", type: class="text-amber-400">"date" },
{ name: class="text-amber-400">"tags", type: class="text-amber-400">"taxonomy", vocab: class="text-amber-400">"tags" }
],
regions: {
hero: { allowed: [class="text-amber-400">"hero"] },
content: { allowed: [class="text-amber-400">"rich_text", class="text-amber-400">"image", class="text-amber-400">"code_example"] },
sidebar: { allowed: [class="text-amber-400">"author_card", class="text-amber-400">"related_posts"] }
}
}
class="text-slate-500">// Fetch typed content in Astro
class=class="text-amber-400">"text-sky-400">const post = class=class="text-amber-400">"text-sky-400">await wolly.pages.getBySlug(class="text-amber-400">"my-post");
class="text-slate-500">// post.fields.author -> class="text-amber-400">"Chad"
class="text-slate-500">// post.regions.hero -> [{ block_type: class="text-amber-400">"hero", ... }]
class="text-slate-500">// post.regions.sidebar -> [{ block_type: class="text-amber-400">"author_card", ... }] Hierarchical Menus
Navigation that just works
Build unlimited independent menus with deep nesting, container items, and external links. The @wollycms/astro package gives you helpers for nav rendering, breadcrumbs, and active-state detection.
- Multiple independent menus — main nav, footer, sidebar, mobile
- Deep nesting with parent/child relationships
- Container items for mega-menu style dropdowns
- Active state and breadcrumb helpers out of the box
class="text-slate-500">---
class=class="text-amber-400">"text-sky-400">import class="text-slate-300">{ getWolly } class=class="text-amber-400">"text-sky-400">from class="text-amber-400">"../lib/wolly";
class=class="text-amber-400">"text-sky-400">const wolly = getWolly();
class="text-slate-500">// Fetch any menu by name
class=class="text-amber-400">"text-sky-400">const main = class=class="text-amber-400">"text-sky-400">await wolly.menus.get(class="text-amber-400">"main");
class=class="text-amber-400">"text-sky-400">const footer = class=class="text-amber-400">"text-sky-400">await wolly.menus.get(class="text-amber-400">"footer");
class="text-slate-500">---
<nav>
class="text-slate-300">{main.items.map((item) => (
<a href={item.url} class:list={[
class="text-amber-400">"nav-link",
{ active: Astro.url.pathname === item.url }
]}>
class="text-slate-300">{item.label}
{item.children?.length > 0 && (
<ul class=class="text-amber-400">"dropdown">
{item.children.map((child) => (
<li><a href={child.url}>class="text-slate-300">{child.label}</a></li>
))}
</ul>
)}
</a>
))}
</nav> Comparison
The features that set WollyCMS apart
Drupal's content model power, modern developer experience, no vendor lock-in.
| Feature | WollyCMS | Strapi | Payload | Storyblok | Keystatic |
|---|---|---|---|---|---|
| Block composition with regions | — | — | — | ||
| Reusable block instances | — | — | — | — | |
| Hierarchical menus built-in | — | — | — | — | |
| Visual page builder | — | — | — | ||
| Astro integration package | — | — | |||
| Self-hosted | — | ||||
| Open source (MIT) | — | ||||
| OG image auto-generation | — | — | — | — | |
| Deploy anywhere (Docker/Workers/Node) | — |