CMS for Flask: Add a Real Editor Without Building One
Skip building publishing, media, and SEO by hand
Flask is deliberately minimal — a microframework that gives you routing and templating and stays out of your way. That's the appeal and the gap: Flask ships no CMS, no admin, no content model. For a Flask app that needs a blog, docs, or marketing pages edited by non-developers, you either build a content backend yourself or connect a headless CMS. This post covers the headless route: how to wire a CMS to Flask, what to fetch in a view, and why a self-hosted content layer fits.
Build it in Flask, or connect a headless CMS?
Build your own: a Post model with SQLAlchemy, a Flask-Admin interface, and templates. Full control, all Python — but you own every feature: media handling, SEO fields, scheduling, draft workflow. That's a lot to build and maintain for something that isn't your app's core.
Headless CMS: content lives in a dedicated service with a ready-made admin and API. Flask fetches it and renders it. You skip building the editor; non-developers get a real tool; Flask stays focused on your application.
If your content is a blog or marketing pages edited by writers, headless saves weeks. If it's tightly woven into app logic, a built-in model may fit better.
Fetching CMS content in a Flask view
The integration is a plain HTTP call. Use requests:
import requests
from flask import Flask, render_template, abort
app = Flask(__name__)
CMS = "https://cms.yoursite.com/api/v1"
@app.route("/blog")
def blog_index():
resp = requests.get(f"{CMS}/posts", params={"per_page": 20})
posts = resp.json()["data"]
return render_template("blog/index.html", posts=posts)
@app.route("/blog/<slug>")
def blog_detail(slug):
resp = requests.get(f"{CMS}/posts/{slug}")
if resp.status_code == 404:
abort(404)
post = resp.json()["data"]
return render_template("blog/detail.html", post=post)
Wrap responses in a cache (Flask-Caching) so you're not hitting the API every request. The CMS returns a { success, message, data } envelope, and drafts return 404 — so unpublished content never leaks.
Why self-hosted headless fits Flask
Flask developers value control and running their own infrastructure. A SaaS CMS with metered API calls and vendor-owned data goes against that. A self-hosted CMS keeps content under your control.
UnfoldCMS is one option. It's a self-hosted CMS on Laravel with a REST API at /api/v1/*. It's PHP, not Python — but that's the point of headless: the CMS is a service that speaks HTTP, so your Flask code only ever sees JSON. Run it on your own server next to (or separate from) your Flask app.
Key fits:
- REST + JSON — exactly what
requestsand Jinja templates want. No GraphQL client to learn. - No SDK — nothing added to
requirements.txt. Standard-library-level HTTP. - Self-hosted, pay once — no per-request billing, no data hostage.
Cache busting with webhooks
Cache CMS responses and clear them when content changes. UnfoldCMS fires outgoing, HMAC-signed webhooks on publish/update/delete. Point one at a Flask endpoint:
@app.route("/webhooks/cms", methods=["POST"])
def cms_webhook():
if not verify_hmac(request): # check the signature header
abort(403)
cache.clear() # bust cached posts
return {"ok": True}
An editor publishes, the CMS pings Flask, the stale cache clears — no waiting for a TTL. The HMAC signature ensures only your CMS can trigger it.
Flask content options compared
| Option | Language | Editor for non-devs | You maintain |
|---|---|---|---|
| Flask-Admin + models | Python | ⚠️ Data-shaped | Everything |
| Headless (UnfoldCMS) | PHP (API) | ✅ Full admin UI | Just the fetch |
| Contentful | SaaS | ✅ Full admin UI | The bill |
A Python-native build keeps everything in one language — good if you never want a second runtime. Headless trades that for zero content-feature maintenance and a real editor.
Tradeoffs to weigh
- A network hop. Content comes from an API call. Caching hides the latency, but it's a moving part.
- A second runtime. UnfoldCMS is a PHP app you host. You don't code in it, but you run it. If your team won't operate anything but Python, that's a real objection.
- No revision history. UnfoldCMS doesn't keep past versions of posts.
- Preview needs wiring. Drafts 404 on the public API, so previewing takes a token-authed route you build.
FAQ
Does Flask have a built-in CMS? No. Flask is a microframework with no CMS, admin, or content model. You either build one (with Flask-Admin and SQLAlchemy) or connect a headless CMS via its API.
Can Flask use a CMS written in another language?
Yes. A headless CMS exposes an HTTP API. Flask calls it with requests and parses JSON — the CMS's language is irrelevant to your app.
How do I avoid hitting the API on every request? Cache responses with Flask-Caching and bust the cache via a webhook when the CMS fires a publish event.
Build my own or go headless? Build in Flask if content is simple and developer-edited. Go headless once you need a real editor for non-developers plus media, scheduling, and SEO — features you'd otherwise reimplement.
Bottom line
Flask ships no CMS by design, so you either build a content backend or connect a headless one. A self-hosted headless CMS gives non-developers a real editor and hands Flask clean JSON over a cached requests call. It's another runtime to operate, but you skip building publishing, media, and SEO yourself. For a Flask app past the "blog is a model" stage, that's the better trade.
See the API in the demo or read CMS vs custom build.
Related: CMS for Django · CMS vs custom build · What is a content API
Free & Open Source
Own your CMS. No subscriptions.
Unfold CMS is free to download and self-host. Built on Laravel + React, full source code included.
Share this post: