Skip to main content

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

CI PyPI

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 pass api=). 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

starcms-0.1.0.tar.gz (16.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

starcms-0.1.0-py3-none-any.whl (20.7 kB view details)

Uploaded Python 3

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

Hashes for starcms-0.1.0.tar.gz
Algorithm Hash digest
SHA256 9c71de672631d820a692c92099ae1a8e18f9e15405dfdc338d045a96ec6939ba
MD5 052185ca494cab879b373f8bc83f6334
BLAKE2b-256 859fae8b7c599494f72356cde69b0d3442738f86665c1a044bbd6679ba5528a2

See more details on using hashes here.

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

Hashes for starcms-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 328b3dbf50151c12e4d55e96bec0fcca235d43e13bda5d077c1d1800f01a70f9
MD5 8c04ca322d4e56570ec3fe9ca4686546
BLAKE2b-256 8e73937b2226a1f83b6b325269a9fce0f092db74903b5bc717566d970d5a361b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page