Django-based CMS

Wagtail, from zero to first published page

A short, plain-language guide to what Wagtail is, how to spin one up in a browser without installing anything, and how to publish your first StreamField page. A live public demo is embedded below.

Your private Wagtail demo sandbox

Every visitor gets their own isolated Wagtail instance. The public demo is a read-only shared site; for a private, editable sandbox, launch the bakerydemo repo in Gitpod. Paste the URL back below and this page will reload your sandbox on your next visit.

Launch on Wagtail demo
Preparing your session...

If nothing loads here, the sandbox host does not allow being framed by other sites. That is a common, sensible security setting for live admin interfaces — use the "Open in new tab" button above.

Launch the sandbox

How to put it into the sandbox

A sandbox is a temporary Wagtail site hosted for you. The official demo is read-only, so for editing you launch the bakerydemo repo in Gitpod (or GitHub Codespaces) — it gives you a full Wagtail + sample content in about a minute.

  1. 1. Open the sandbox provider

    Click gitpod.io on the bakerydemo repo. Sign in with GitHub if asked. Gitpod opens a full VS Code in the browser and starts a container with Python, PostgreSQL and Wagtail configured.

  2. 2. Wait for the workspace to build

    The first boot runs pip install and Wagtail's migrations, then loads sample content (recipes, blog posts, locations). Watch the terminal — when it prints Starting development server at 0.0.0.0:8000 the site is ready.

  3. 3. Open your live URL

    Gitpod shows a preview pane on the right. Click Open in new tab to get a public URL like 8000-yourworkspace.ws-eu.gitpod.io. That is your sandbox. Paste it below so this page can reload it next time.

  4. 4. Log into Wagtail admin

    Bakerydemo creates a default superuser: admin / changeme. Visit /admin/ at your live URL and log in. You land on the Wagtail dashboard with sample pages, images and snippets.

  5. 5. Explore the page tree

    Click Pages in the left sidebar. Wagtail shows the whole site as a tree. Click Home, then Blog, then Add child page, then pick Blog page. Fill in a title, hero image and a couple of StreamField blocks.

  6. 6. Publish and preview

    Click the up-arrow menu at the bottom, choose Publish. Refresh the front-end URL — your post appears on the Blog index and at its own slug. Use the Preview button while editing to see a draft render without publishing.

  7. 7. Try Snippets and Images

    Under Snippets add an Author, upload a portrait under Images, and edit an existing recipe page to attach the author snippet. Snippets are the way to share small pieces of data across many pages.

  8. 8. Save what you want to keep

    Gitpod workspaces are ephemeral. Commit your changes to a fork of the bakerydemo repo, or run python manage.py dumpdata in the terminal and download the JSON. Media files are in the media/ folder — right-click, download.

Step-by-step guide

  1. 1. What Wagtail is

    Wagtail is a free, open-source content management system built on top of Django. It gives you Django's power (Python, ORM, admin, permissions) plus a modern editor experience aimed squarely at editorial teams — a clean page tree, a drag-and-drop rich-text field called StreamField, a media library, workflows and previews. NASA, the NHS, Google Blog, and Mozilla all run Wagtail sites.

    Was this useful?
  2. 2. Try Wagtail without installing anything

    The Wagtail team maintains a public demo you can log into as an editor at demo.wagtail.io — the underlying project is bakerydemo. To get your own private, disposable instance, open the bakerydemo repo in Gitpod or GitHub Codespaces — it boots a full Wagtail site with sample content in about a minute.

    Was this useful?
  3. 3. What you need to run it yourself

    Python 3.10 or newer, and pip (bundled with Python). Optionally venv to isolate the project. Wagtail uses SQLite out of the box for development and PostgreSQL for production. On the front end it needs no build step by default — templates render server-side — but it plays well with any JS framework if you want to go headless.

    Was this useful?
  4. 4. Install Wagtail locally in one minute

    Open a terminal and run python -m venv .venv, source .venv/bin/activate, pip install wagtail, wagtail start mysite, cd mysite, pip install -r requirements.txt, python manage.py migrate, python manage.py createsuperuser, python manage.py runserver. Open http://127.0.0.1:8000/admin and log in — you get the Wagtail dashboard.

    Was this useful?
  5. 5. First run — the Wagtail admin

    The admin has: Pages (the site tree), Documents, Images, Snippets, Reports, Settings. Pages is where content lives; every URL on your site is a page in the tree. Images and Documents are the media library. Snippets are reusable data models (footer text, author bios, promotional banners) not tied to a single URL. Reports covers workflow and audit logs.

    Was this useful?
  6. 6. Page models and the tree — the core idea

    Every page type is a Python class that inherits from wagtail.models.Page. Give it fields (title, body, feature image), declare which page types can live under it, and Wagtail generates a full editor UI in the admin. Editors add pages by clicking Add child page under a parent in the tree. URLs are derived from the tree, so structure equals sitemap.

    Was this useful?
  7. 7. StreamField — flexible content blocks

    Instead of one giant WYSIWYG textarea, Wagtail encourages a StreamField: an ordered list of typed blocks. You define block types in Python (Heading, Paragraph, Image, Quote, TwoColumn, Callout, EmbedBlock, HTMLBlock) and editors compose a page by adding, ordering and configuring blocks. It is the same idea as WordPress Gutenberg but Python-first and much easier to constrain.

    Was this useful?
  8. 8. Users, roles, workflows and previews

    Wagtail extends Django's user system with per-page permissions (edit, publish, lock) and a Workflow feature you enable at Settings, Workflows. A page can require review by one or more groups before it goes live. Every page has a Preview button that renders it exactly as the public site will, and a full revision history you can revert to.

    Was this useful?
  9. 9. Add features with Django and Wagtail packages

    Because Wagtail is Django, every Django package works. The Wagtail-specific ecosystem adds: wagtail-localize (multilingual), wagtail-2fa (two-factor auth), wagtail-seo, wagtail-menus, wagtail-transfer (move content between environments), djangorestframework + wagtail.api.v2 for a headless JSON API. Install with pip, add to INSTALLED_APPS, run migrations.

    Was this useful?
  10. 10. Publishing your site

    Deploy like any Django app: PostgreSQL, gunicorn behind nginx, run collectstatic, set DEBUG=False. Wagtail-friendly hosts: Divio, Fly.io, Heroku, Render, Railway, or a plain VPS. For images at scale, put media on S3 with django-storages. Back up: pg_dump the database and mirror the media/ folder. Wagtail follows Django's LTS pattern — the current LTS is a safe target for production.

    Was this useful?

One-page cheat sheet

Create a new Wagtail project

python -m venv .venv
source .venv/bin/activate
pip install wagtail
wagtail start mysite
cd mysite
pip install -r requirements.txt
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver

Add a page model (home/models.py)

from wagtail.models import Page
from wagtail.fields import StreamField
from wagtail import blocks

class BlogPage(Page):
    intro = models.CharField(max_length=250)
    body = StreamField([
        ("heading", blocks.CharBlock()),
        ("paragraph", blocks.RichTextBlock()),
        ("image", ImageChooserBlock()),
    ], use_json_field=True)

    content_panels = Page.content_panels + [
        FieldPanel("intro"),
        FieldPanel("body"),
    ]

Migrations

python manage.py makemigrations
python manage.py migrate
python manage.py collectstatic --no-input

Deploy checklist

# settings/production.py
DEBUG = False
ALLOWED_HOSTS = ["example.com"]
DATABASES = {"default": dj_database_url.config()}
# then
python manage.py migrate
python manage.py collectstatic
gunicorn mysite.wsgi --bind 0.0.0.0:8000