Code-first CMS for Python: define Pydantic models, get an admin UI and content API, mounted into your FastAPI or FastHTML app.
Project description
starcms
A code-first CMS for Python. Define your content models as Pydantic classes and get a login-protected admin UI plus a content API, mounted into your existing FastAPI or FastHTML app — pure Python, no Node, no build step, no separate service.
from fastapi import FastAPI # or: from fasthtml.common import FastHTML
from pydantic import BaseModel
from starcms import StarCMS
class BlogPost(BaseModel):
title: str
body: str | None = None
published: bool = False
app = FastAPI()
cms = StarCMS(db="sqlite+aiosqlite:///content.db", models=[BlogPost])
cms.mount(app, admin="/admin", api="/api/cms")
That's the whole integration. From one model class you get:
-
/admin— a server-rendered admin: list, create, edit, delete, behind a login, with forms generated from your model's fields and validated by Pydantic. -
/api/cms/blogpost— a read-only JSON API serving your content to frontends and mobile apps (opt-in: mounted only if you passapi=). Collections come enveloped as{"items": [...]}and paged (?limit=default 50, max 200, plus?offset=); single records live at/api/cms/blogpost/{id}. -
In-process queries — server-rendered hosts read their own content directly, no HTTP round-trip to yourself. Records come back as plain dicts (
{"id": ..., **fields}):posts = await cms.find(BlogPost, published=True, limit=10) post = await cms.get(BlogPost, 42)
Change the class and the admin forms, table, database schema, and API all follow. The model is the single source of truth.
Why
Python is underserved for modern, code-first content management. Wagtail and Django CMS are mature but Django-coupled; the popular headless CMSes (Payload, Strapi, Sanity) mean running a Node service next to your Python app — two runtimes, two deploys, and your content schema living outside your codebase. Admin generators like SQLAdmin solve the screens but stop there: no content delivery, and your models must be SQLAlchemy ORM classes.
starcms is a library, not a service: pip install, describe content in the
Pydantic vocabulary you already use, mount, done. It works in both FastAPI
and FastHTML because it's built one layer down, on Starlette — the
foundation they share.
Install & run
uv add starcms # or: pip install starcms
Set the admin credentials and (in production) a session secret:
| Variable | Purpose |
|---|---|
STARCMS_ADMIN_USER / STARCMS_ADMIN_PASSWORD |
The single admin login. Unset = admin locked. |
STARCMS_SECRET |
Signs the session cookie. Unset = random per process (dev-only; restarts log you out). Set it before cms.mount(). |
Then run your app as usual (uvicorn myapp:app). The database tables are
created automatically on first use — no migration step. SQLite
(sqlite+aiosqlite:///...) for development, PostgreSQL for production
(postgresql+asyncpg://... — install via pip install "starcms[postgres]").
Supported field types
str, int, float, bool, datetime.datetime — each optionally
| None, with defaults and default_factory respected (a
default_factory=datetime.now prefills forms with now, not server start).
Labels come from field names or Field(title=...). Unsupported types are
rejected loudly at startup, not deep in a request. Models must not declare
an id field — starcms manages the primary key.
The shape of it
your Pydantic models
│ introspection (one pass, cached)
▼
FieldSpec IR ──────────┬───────────────┬──────────────┐
│ │ │ │
▼ ▼ ▼ ▼
database tables admin forms admin tables JSON API
(SQLAlchemy Core) (htpy, no JS) (read-only)
One introspection layer reads your models; everything else is generated from its output. The admin is a self-contained Starlette sub-app with its own session (cookie named and path-scoped so it never collides with your app's). The API is a second sub-app with a separate perimeter: no session, no login — front it with your own auth if your content isn't public.
Honest scope
This is the wedge, deliberately small: CRUD admin + content API. Not yet here: rich text, media uploads, drafts/versioning, multi-user auth & roles, migrations (schema changes during dev: drop and recreate), localization, htmx interactivity. These arrive based on real demand, not speculation.
Development
git clone https://github.com/admsftpge/starcms && cd starcms
uv sync
uv run poe check # lint + tests
uv run poe demo-fastapi # admin at http://localhost:8000/admin (admin/admin)
uv run poe demo-fasthtml # same CMS, FastHTML host, content on the homepage
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file starcms-0.1.0.tar.gz.
File metadata
- Download URL: starcms-0.1.0.tar.gz
- Upload date:
- Size: 16.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9c71de672631d820a692c92099ae1a8e18f9e15405dfdc338d045a96ec6939ba
|
|
| MD5 |
052185ca494cab879b373f8bc83f6334
|
|
| BLAKE2b-256 |
859fae8b7c599494f72356cde69b0d3442738f86665c1a044bbd6679ba5528a2
|
File details
Details for the file starcms-0.1.0-py3-none-any.whl.
File metadata
- Download URL: starcms-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
328b3dbf50151c12e4d55e96bec0fcca235d43e13bda5d077c1d1800f01a70f9
|
|
| MD5 |
8c04ca322d4e56570ec3fe9ca4686546
|
|
| BLAKE2b-256 |
8e73937b2226a1f83b6b325269a9fce0f092db74903b5bc717566d970d5a361b
|