How django CMS is composed

A django CMS site is assembled from three building blocks: content objects (of which the page is the most prominent), plugins, and apphooks. Each has a single, well-defined job. Most decisions you make while building a site are decisions about which of the three to reach for.

This page describes the three building blocks side by side, the rules that govern how they fit together, and the most common decision — plugin vs apphook — that beginners ask about.

For the internals of each piece, follow the cross-links:

The three building blocks

Block

What it is

What it owns

Content object

A thing that holds editable content and exposes placeholders for editors to fill. Pages are the most common (and most powerful) kind — they live in the page tree, carry URLs, drive menus, and carry permissions. Other kinds include aliases (reusable content embedded via the Alias plugin) and content objects defined by add-on apps (e.g. blog posts, catalogue items).

Its placeholders, its translations, and — for pages — its slug, its template, and its position in the page tree.

Plugin

A reusable content component an editor can drop into a placeholder.

The data the editor fills in (via its model) plus the template that renders it.

Apphook

The standard way to mount a Django application onto the page tree. The application’s URLs (and any content objects it defines) take their prefix from the CMS page that anchors them.

All URLs at and below the page it is attached to.

The content object is the unit of editable content. The plugin is the unit of composition inside it. The apphook is how other content joins the page tree.

How they fit together

The relationships are simple, but easy to confuse if you only read about each in isolation:

Page tree
 │
 └── Page  (a content object: owns placeholders, lives in the tree)
      │
      ├── Placeholder  (declared by the page's template)
      │    ├── Plugin
      │    │    └── Plugin (nested, if the parent allows it)
      │    └── Plugin
      │
      └── Apphook  (optional — attaches a Django app at this URL)
           │
           └── App-defined content objects
                │   (e.g. blog posts, products, polls)
                └── Placeholders
                     └── Plugins

Outside the page tree:

Alias  (a reusable content object embedded via the Alias plugin)
 └── Placeholders
      └── Plugins

A few rules govern the picture:

  • Content objects own placeholders. Placeholders contain plugins. Plugins compose. This is true for every kind of content object — pages, aliases, and app-defined objects alike.

  • The page tree is what gives pages URLs, what makes them appear in menus, and what carries the per-page permission model. Content objects that are not pages get their URLs by being mounted on the tree through an apphook, or by providing their URLs through their own get_absolute_url() method.

  • Plugins live in placeholders, never standalone. Even when the same plugin appears on many content objects, each appearance is a separate plugin instance with its own configuration.

  • Plugins can contain other plugins when the parent plugin declares allow_children = True. This is how composite layouts (rows, columns, accordions) are built.

  • An apphook is meaningless without a page to attach it to. It is not a setting; it is a binding made on a CMS page in Advanced settings. The page provides the URL prefix; the app provides everything below.

Plugin or apphook? A decision aid

Reframed: the question is really where does this content live? If it fits inside an existing placeholder on someone else’s page, it is a plugin. If it is its own kind of thing — with its own list view, detail view, and URL — it is an app, mounted via an apphook.

You want…

Reach for…

Why

A reusable content component the editor drops into any placeholder.

Plugin

Plugins are the unit of composition inside a placeholder.

A whole sub-application (blog, catalogue, polls, search) with list and detail views.

Apphook

Apphooks own a URL prefix and bring their own content objects.

A page where everything is editor-composed.

Page + plugins

The default flow for a page content object.

A page whose body is driven by Django views (list, detail, search results).

Page + apphook

The page provides the URL, the app provides the views and (if it has them) its own content objects.

Editors to choose which records show up in a list embedded on a page.

Plugin (with a model that references your records)

The component lives on a page; only its data lives in your app’s models.

Editors to move a sub-site to a different URL by moving a page.

Apphook

The views and content objects are yours; the URL belongs to the page.

A teaser of upcoming events on the homepage and the full events sub-site at /events/.

Both — plugin for the teaser, apphook for the sub-site

Common combination. The plugin renders a small summary; the apphook owns /events/ and below.

When the line blurs

A few real cases trip people up. None of them break the model; they just look like edge cases.

A plugin that needs its own detail URL. A “product card” plugin that links to /products/42/ is still a plugin (it lives in a placeholder), but the detail URL must come from somewhere. The idiomatic answer is to put the detail view in an apphook mounted on a CMS page, so the URL is editor-controllable. If you wire the view into the project’s root urls.py instead, the URL works but is fixed in code and not controlled by the editor.

Many copies of “the same” plugin on one page. Each is an independent plugin instance with its own configuration. They share the same model and template; they do not share data.

A page that has both placeholders and an apphook. The page’s own URL (/events/) renders its placeholders normally; URLs below (/events/2026-summit/) are handed to the apphook. Child CMS pages under an apphooked page are not reliably reachable, because the apphook owns that URL space. The URLs might resolve to the app hook instead of a child page, and the child page might not be reached at all.

The same Django app mounted on several pages. Each mount point is independent. If editors should configure each mount differently (different category, different feed), the app needs an apphook configuration.

Reusing the same content across pages without copying it. That’s what an alias is for: a content object that lives outside the page tree and is embedded in placeholders via the Alias plugin. A footer block, a promotional banner, or a “current opening hours” strip are typical examples.

Where to go next

  • Plugins — what a plugin really is, what files it consists of, and how to think about plugin models.

  • Application hooks (“apphooks”) — what an apphook is, what it changes about URL handling, and the apphook configuration story.

  • Publishing — how the publishing model interacts with every content object on the page (an unpublished page hides its plugins and takes its apphook offline).

  • How the menu system works — how pages, apphooks, and custom menu code combine to produce the navigation editors see.