CMS for Django: When a Headless Content Layer Beats Wagtail
Keep your Django project lean, give writers a real editor
Django ships with an admin, so "does Django need a CMS?" is a fair question. The Django admin is great for your app's data models. It's not a content editor. It won't give a marketing writer a clean place to draft a blog post, schedule it, drop in images, and preview SEO. And the Python CMS options — Wagtail, Django CMS — mean adopting a whole framework opinion inside your project.
Sometimes the cleaner answer is a headless CMS that lives outside Django and feeds it content over an API. This post covers when that makes sense, how to wire it up, and the tradeoffs.
Two roads for content in Django
In-framework CMS (Wagtail, Django CMS, Mezzanine): these bolt page models, a tree, and an editor onto your Django project. Powerful, deeply integrated — and a big commitment. Your content model now lives in your app's database and migrations, tied to your Django version and deploy.
Headless CMS: content lives in a separate service with its own admin and API. Django fetches it and renders it (or your front end does). Looser coupling, less to maintain inside your Django codebase, and non-developers get a purpose-built editor.
If your content is mostly a blog, docs, and marketing pages — not deeply woven into app logic — headless keeps your Django project lean.
Fetching CMS content in a Django view
The integration is a plain HTTP call. Use requests (or httpx) in a view, hand the data to a template:
# views.py
import requests
def blog_index(request):
resp = requests.get(
"https://cms.yoursite.com/api/v1/posts",
params={"per_page": 20},
)
posts = resp.json()["data"]
return render(request, "blog/index.html", {"posts": posts})
def blog_detail(request, slug):
resp = requests.get(f"https://cms.yoursite.com/api/v1/posts/{slug}")
if resp.status_code == 404:
raise Http404("Post not found")
post = resp.json()["data"]
return render(request, "blog/detail.html", {"post": post})
Cache the responses (Django's cache framework, or a short-TTL layer) so you're not hitting the API on every request. The CMS returns a consistent { success, message, data } envelope, and drafts return 404 — so unpublished content never leaks.
Why a self-hosted headless CMS suits Django teams
Django developers usually run their own infrastructure and value control. A SaaS CMS with metered API calls and vendor-owned data cuts against that. A self-hosted CMS keeps the ownership Django people expect.
UnfoldCMS is one option. It's a self-hosted CMS built on Laravel, with a REST API at /api/v1/* and a React + shadcn/ui admin. Yes, it's PHP, not Python — but that's the point of headless: the CMS is a black box that speaks HTTP. Your Django code never touches PHP; it just calls an API and gets JSON. You run the CMS on your own server next to (or separate from) your Django app.
What matters here:
- REST + JSON — exactly what
requestsand Django templates want. No GraphQL client to learn. - No SDK — nothing to add to
requirements.txt. The integration is standard-library-level HTTP. - Self-hosted, pay once — no per-request billing, no data hostage situation.
Keeping content and cache in sync
If you cache CMS responses (you should), you need to bust that cache when content changes. UnfoldCMS fires outgoing, HMAC-signed webhooks on publish/update/delete. Point one at a Django endpoint that clears the relevant cache key:
# A webhook receiver that verifies HMAC and clears cache
def cms_webhook(request):
if not verify_hmac(request): # check the signature header
return HttpResponseForbidden()
cache.delete_pattern("blog:*") # bust cached posts
return JsonResponse({"ok": True})
Now an editor publishing in the CMS immediately clears Django's stale cache — no waiting for a TTL to expire. The HMAC signature ensures only your CMS can trigger it.
Django content options compared
| Option | Language | Editor for non-devs | Coupling to Django |
|---|---|---|---|
| Django admin | Python | ⚠️ Data-shaped, not content | In-app |
| Wagtail | Python | ✅ Good page editor | Deep — it's a framework |
| UnfoldCMS (headless) | PHP (API) | ✅ Full admin UI | Loose — HTTP only |
| Contentful | SaaS | ✅ Full admin UI | Loose — but metered |
Wagtail is the right answer when content and app logic are tightly intertwined and you want it all in Python. A headless CMS is the right answer when content is a separable concern and you'd rather keep your Django project focused on the app.
Tradeoffs to weigh
- A network hop. Django now depends on an API call for content. Caching hides the latency, but it's a moving part Wagtail wouldn't add.
- Different language to operate. The CMS is a PHP app you host. You don't code in it, but you do run and update it. If your team refuses to operate anything non-Python, that's a real objection.
- No revision history. UnfoldCMS doesn't store past versions of posts.
- Preview needs wiring. Drafts 404 on the public API, so previewing unpublished content means a token-authed route you build.
FAQ
Does Django need a separate CMS? The Django admin manages your data models but isn't a content editor for writers. If you have a blog, docs, or marketing pages edited by non-developers, a dedicated CMS — in-framework like Wagtail, or headless — gives them a proper editing experience.
Can I use a PHP-based CMS with a Python app?
Yes. In a headless setup the CMS only exposes an HTTP API. Django calls it with requests and never touches PHP. The language of the CMS is irrelevant to your Django code.
Wagtail or headless for Django? Wagtail if content is deeply tied to app logic and you want everything in Python. Headless if content is a separable concern and you'd rather keep Django lean and let non-developers use a standalone editor.
How do I avoid hitting the API on every request? Cache responses with Django's cache framework and bust the cache via a webhook receiver when the CMS fires a publish event.
Bottom line
Django's admin isn't a content editor, and in-framework CMSes like Wagtail are a big commitment. A self-hosted headless CMS lets you keep your Django project focused on the app while non-developers get a real editor — connected by a plain REST call and kept fresh with a webhook. It's PHP under the hood, but your Django code only ever sees JSON.
Explore the API in the demo or read the headless vs traditional CMS breakdown.
Related: CMS for PHP developers · Headless vs traditional CMS · 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: