Quickstart — your first SEO-enabled model

This guide takes an existing Django blog Article model and adds full SEO metadata in three steps. You should have completed Installation first.


Step 1: Add SeoModelMixin to your model

# blog/models.py

from django.db import models
from seo_suite.mixins import SeoModelMixin


class Article(SeoModelMixin, models.Model):
    title = models.CharField(max_length=200)
    description = models.TextField(blank=True)
    slug = models.SlugField(unique=True)
    published = models.BooleanField(default=False)

    def get_absolute_url(self):
        return f"/articles/{self.slug}/"

That is the only change to the model. No new columns, no migration.

SeoModelMixin reads metadata from the model's existing fields using conventional field names:

Metadata field Fields tried, in order
title title, name, headline
meta_description meta_description, description, summary, excerpt
meta_keywords meta_keywords, keywords
og_image og_image, image, cover_image, photo, thumbnail
canonical_url return value of get_absolute_url()

If your fields have different names, use SEO_FIELD_MAP to tell the mixin where to look. See Blog posts and articles for details.


Step 2: Add SeoViewMixin to your detail view

# blog/views.py

from django.views.generic import DetailView
from seo_suite.mixins import SeoViewMixin

from .models import Article


class ArticleDetail(SeoViewMixin, DetailView):
    model = Article
    template_name = "blog/article_detail.html"
    slug_field = "slug"
    slug_url_kwarg = "slug"

SeoViewMixin wires the view's context object (self.object) into the resolver. The object's get_seo_metadata() is called automatically; the resulting FinalizedSeoMetadata is placed in the template context as seo.


Step 3: Add {% seo_head %} to your base template

If you already added {% seo_head %} to your base template during installation, nothing more is needed. If you are using a per-page template:

{# blog/templates/blog/article_detail.html #}
{% extends "base.html" %}
{% load seo_suite %}

{% block extra_head %}
  {% seo_head %}
{% endblock %}

Or, in a base template that all pages share:

{# templates/base.html #}
{% load seo_suite %}
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  {% seo_head %}
</head>
<body>
  {% block content %}{% endblock %}
</body>
</html>

What gets rendered

Given an Article with title="How Django Routing Works", description="A deep dive into URL resolvers.", and slug="how-django-routing-works", and a site running at https://example.com, the <head> block renders:

<title>How Django Routing Works</title>
<meta name="description" content="A deep dive into URL resolvers.">
<meta name="robots" content="index,follow">
<link rel="canonical" href="https://example.com/articles/how-django-routing-works/">
<meta property="og:type" content="website">
<meta property="og:title" content="How Django Routing Works">
<meta property="og:description" content="A deep dive into URL resolvers.">
<meta property="og:url" content="https://example.com/articles/how-django-routing-works/">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="How Django Routing Works">
<meta name="twitter:description" content="A deep dive into URL resolvers.">

A few things to note:

  • og:title and og:description are populated from the page title and description, with no extra code needed.
  • og:url mirrors the canonical URL.
  • twitter:card defaults to "summary" when there is no image, and becomes "summary_large_image" when an image is present.
  • robots: index,follow comes from the global default in SEO_SUITE["DEFAULTS"]. You can change it per-page or site-wide; see Site-wide defaults.

Adding a title suffix

A site-wide suffix like " | My Blog" is a single settings entry:

# settings.py

SEO_SUITE = {
    "DEFAULTS": {
        "title_suffix": " | My Blog",
        "robots": "index,follow",
    },
}

The <title> tag now renders as:

<title>How Django Routing Works | My Blog</title>

Next steps