Open Source · MIT License

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.

Terminal
$ 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
Custom Block Types
Define any block type you need — 11 included out of the box
200+
Tests Passing
Content, admin, accessibility, and SEO tests
12+
API Endpoints
Pages, menus, search, sitemap, batch, health
18
Database Tables
Full content model with indexes and migrations

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
src/pages/[...slug].astro
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
Page Type Definition
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
src/layouts/Base.astro
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 StrapiPayloadStoryblokKeystatic
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)
Get Started