
Security News
PHP Foundation Says Chainguard Broke Security Embargo Ahead of Emergency Patches
The PHP Foundation says Chainguard broke a security embargo ahead of critical RCE patches, as leaks disrupted the coordinated release.
π» A little CMS with big heart β schema-driven, runs on Cloudflare's free tier
A little CMS with big heart β schema-driven content management that runs entirely on Cloudflare's free tier.
Koguma gives you a headless CMS with a beautiful admin dashboard, all powered by Cloudflare Workers + D1 + R2. Define your content types in code, and Koguma handles the rest β API routes, admin UI, media storage, and a file-based content/ directory for version-controlled content.
bun add koguma
# or
npm install koguma
Create a site.config.ts in your project root:
import { defineConfig, contentType, field } from 'koguma';
export default defineConfig({
siteName: 'My Site',
contentTypes: [
contentType({
id: 'post',
name: 'Blog Post',
displayField: 'title',
fields: {
title: field.text('Title').required(),
slug: field.text('Slug').required(),
body: field.markdown('Body'),
heroImage: field.image('Hero Image'),
published: field.boolean('Published').default(false),
date: field.date('Published Date')
}
})
]
});
Create a worker.ts:
import { createWorker } from 'koguma/worker';
import config from './site.config';
export default createWorker(config);
Create koguma.toml:
name = "my-site"
database_id = "" # filled by `koguma init`
That's it β two lines. Everything else is derived by convention:
my-site-dbmy-site-mediamy-sitekoguma init # Scaffold project, create D1 + R2 on Cloudflare
koguma push # Build + deploy + sync content
Your admin dashboard is at /admin. Your API is at /api/content/:type.
| Field | Usage | Stored As |
|---|---|---|
field.text(label) | Titles, slugs, URLs | TEXT |
field.longText(label) | Descriptions, bios | TEXT |
field.markdown(label) | Rich formatted content | TEXT (markdown string) |
field.number(label) | Counts, order | number in JSON |
field.boolean(label) | Toggles | boolean in JSON |
field.date(label) | Timestamps | string (ISO 8601) |
field.select(label, {options}) | Dropdowns | string |
field.url(label) | URLs | string |
field.email(label) | Email addresses | string |
field.phone(label) | Phone numbers | string |
field.color(label) | Hex colours | string |
field.youtube(label) | YouTube video IDs | string |
field.instagram(label) | Instagram handles | string |
field.image(label) | Image from R2 media | string (asset ID) |
field.images(label) | Array of images | string[] (asset IDs) |
field.ref(typeId, label) | Link to another entry | string (entry ID) |
field.refs(typeId, label) | Array of entry links | string[] (entry IDs) |
All fields stored inside a JSON data blob in the entries table β no per-field columns, no migrations.
contentType({
id: "page",
name: "Page",
displayField: "title",
singleton: true, // Only one entry allowed (e.g. site settings)
fields: { ... },
});
All commands auto-detect your project root by looking for koguma.toml.
| Command | Description |
|---|---|
koguma init | Interactive project setup β scaffold, create D1 + R2, set secret |
koguma dev | Auto-sync content/ β local D1, generate types, start dev server |
koguma push | Build + deploy + sync content to remote |
koguma pull | Download remote content + media β local content/ files |
koguma gen-types | Generate koguma.d.ts typed interfaces |
koguma tidy | Validate content/ against config, sync dirs, check fields |
Koguma uses a content/ directory for file-based content that lives in your git repo:
content/
βββ post/ # collection β one file per entry
β βββ hello-world.md # slug = "hello-world"
β βββ our-mission.md
β βββ our-mission.heroBody.md # sibling markdown field
βββ landingPage/ # singleton β always index.md
β βββ index.md # slug = content type ID
β βββ heroBody.md # sibling markdown field
β βββ ctaBody.md # sibling markdown field
βββ media/ # local images (synced to R2)
βββ hero-banner.jpg
Every content file uses YAML frontmatter + markdown body:
---
title: Our Mission
slug: our-mission
heroImage: media-hero-banner
published: true
date: 2026-03-01
---
This body content maps to the **first** `field.markdown()` in the content type.
{slug}.mdsingleton: true): One entry, always index.md. The slug is auto-set to the content type ID.When a content type has multiple field.markdown() fields, the first one maps to the file body. Additional markdown fields are stored as sibling files (pure markdown, no frontmatter):
| Collections | Singletons | |
|---|---|---|
| Main file | {slug}.md | index.md |
| Sibling pattern | {slug}.{fieldId}.md | {fieldId}.md |
Example singleton with 3 markdown fields:
content/landingPage/
βββ index.md # frontmatter + 1st markdown field body
βββ heroBody.md # 2nd markdown field (pure markdown)
βββ ctaBody.md # 3rd markdown field (pure markdown)
Git is your version history. No custom versioning table needed.
| Method | Route | Description |
|---|---|---|
GET | /api/content/:type | List all entries of a content type |
GET | /api/content/:type/:id | Get a single entry (with resolved refs) |
GET | /api/media/:key | Serve a media file from R2 |
| Method | Route | Description |
|---|---|---|
POST | /api/auth/login | Login with { password } |
POST | /api/auth/logout | Clear session |
GET | /api/auth/me | Check authentication |
GET | /api/admin/schema | Content type schemas (for admin UI) |
GET | /api/admin/:type | List entries |
GET | /api/admin/:type/:id | Get entry |
POST | /api/admin/:type | Create entry |
PUT | /api/admin/:type/:id | Update entry |
DELETE | /api/admin/:type/:id | Delete entry |
GET | /api/admin/media | List media assets |
POST | /api/admin/media | Upload media (multipart form) |
DELETE | /api/admin/media/:id | Delete media |
References and images are automatically resolved to nested objects in the public API (up to 2 levels deep).
import { defineConfig, contentType, group, field, type Infer } from 'koguma';
import { createWorker } from 'koguma/worker';
import { createClient } from 'koguma/client';
import { Markdown, useEntry, useEntries } from 'koguma/react';
import type { KogumaAsset, EntryReference } from 'koguma/types';
# Create .dev.vars with your local admin password
echo "KOGUMA_SECRET=your-password" > .dev.vars
# Start dev server (auto-syncs content/, generates types)
koguma dev
# Admin dashboard is at http://localhost:8787/admin
# API is at http://localhost:8787/api/content/:type
Your Project
βββ site.config.ts β Content type definitions
βββ worker.ts β Entry point (imports koguma/worker)
βββ koguma.toml β Project config (name + database_id)
βββ .dev.vars β Local secrets (KOGUMA_SECRET)
βββ content/ β Version-controlled content files
βββ post/
β βββ hello-world.md
βββ siteSettings/
βββ index.md
Koguma (this package)
βββ src/
β βββ config/ β Schema definitions (defineConfig, field types)
β βββ api/ β Hono router (CRUD + media + auth)
β βββ db/ β D1 queries (JSON document store)
β βββ auth/ β HMAC-signed cookie sessions
β βββ media/ β R2 upload/serve/delete
β βββ admin/ β Dashboard HTML shell + JS/CSS bundle
β βββ client/ β Fetch client for consuming the API
β βββ react/ β React hooks + <Markdown> component
βββ admin/ β Vite + React admin dashboard source
βββ cli/ β CLI commands (init, dev, push, pull, gen-types, tidy)
MIT
FAQs
π» A little CMS with big heart β schema-driven, runs on Cloudflare's free tier
The npm package koguma receives a total of 0 weekly downloads. As such, koguma popularity was classified as not popular.
We found that koguma demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Β It has 1 open source maintainer collaborating on the project.

Security News
The PHP Foundation says Chainguard broke a security embargo ahead of critical RCE patches, as leaks disrupted the coordinated release.

Security News
PHP releases emergency patches for two critical RCE vulnerabilities as agencies urge organizations to take unpatched sites offline.

Threat Research
/Security News
A supply chain attack compromised LiteLLM 1.107.0 on PyPI and Docker, spreading malware that steals cloud credentials, developer secrets, and AI provider keys.