Skip to main content

django-seo-suite

PyPI Python Django License Docs

A lightweight, extensible SEO metadata layer for Django. It gives any project one consistent way to attach and render the full set of SEO attributes — <title>, <h1>, meta description/keywords/robots, canonical URL, hreflang alternates, JSON-LD schema and Open Graph / Twitter cards — across models, class-based views (including listing and model-less views), and even third-party models you can't edit.

  • No required apps. The core ships a single migration (the versioned robots.txt table) and works without django.contrib.sites (detected at runtime).
  • One dependency: Django (4.2+). Python 3.10+.
  • Multi-provider resolver. Metadata is merged from several sources by a clear precedence, then rendered by template tags.
  • Built for extension. A separate package can add capability through stable, documented hooks — no forking, no monkeypatching.

Install

pip install django-seo-suite
# settings.py
INSTALLED_APPS = [
    # ...
    "seo_suite",
    # optional, admin-editable overrides:
    "seo_suite.contrib.seopath",     # SEO keyed by URL path
    "seo_suite.contrib.seoobject",   # SEO for third-party models (uses ContentTypes)
]

TEMPLATES = [{
    # ...
    "OPTIONS": {"context_processors": [
        "django.template.context_processors.request",
        "seo_suite.context.seo",     # enables {% seo_head %} everywhere
    ]},
}]

Then run migrations (the core app ships one migration for the robots.txt table):

python manage.py migrate

Quickstart

On a model you own:

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

class Article(SeoModelMixin, models.Model):
    title = models.CharField(max_length=200)
    summary = models.TextField()
    cover = models.ImageField(upload_to="covers/")
    slug = models.SlugField()

    SEO_FIELD_MAP = {"meta_description": "summary", "og_image": "cover"}
    SEO_SCHEMA_PROFILES = ["Article", "BreadcrumbList"]

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

title/description/image fall back to conventional field names automatically; SEO_FIELD_MAP overrides them; get_absolute_url becomes the canonical URL.

On a view:

from django.views.generic import DetailView, ListView
from seo_suite.mixins import SeoViewMixin, SeoListViewMixin

class ArticleDetail(SeoViewMixin, DetailView):
    model = Article
    # the object's metadata is used automatically; view attrs override it

class ArticleList(SeoListViewMixin, ListView):     # model-less views work too
    model = Article
    seo_title = "All Articles"                     # becomes "All Articles – Page 2" when paginated

In your base template:

{% load seo_suite %}
<head>
  {% seo_head %}            {# title, meta, canonical, hreflang, OG, Twitter, JSON-LD #}
</head>

Need finer control? Use the granular tags: {% seo_title %}, {% seo_meta %}, {% seo_canonical %}, {% seo_hreflang %}, {% seo_opengraph %}, {% seo_twitter %}, {% seo_jsonld %}, {% seo_extra_head %}. Every partial in templates/seo_suite/ is overridable by shipping your own.

How resolution works

Metadata is merged from lowest to highest precedence (higher wins per field; JSON-LD and extra-head fragments accumulate):

Precedence Source
10 SEO_SUITE["DEFAULTS"] (global)
20 SEO_SUITE["SITE_DEFAULTS"][site_id]
30 seopath.SeoPath row matching the URL path (optional app)
40 the object's get_seo_metadata() (model mixin) or seoobject row
50 the view's get_seo_metadata() / seo_* attributes

A field left "unset" defers to lower precedence; an explicit None/"" wins and renders nothing.

JSON-LD schema library

Enable ready-made schema.org profiles declaratively — no template editing:

class FaqPage(SeoModelMixin, models.Model):
    SEO_SCHEMA_PROFILES = ["WebPage", "FAQPage"]
    def get_faqs(self):
        return [{"question": "…", "answer": "…"}]

Free profiles: WebPage, WebSite, Organization, Person, BreadcrumbList, Article, FAQPage, Product. An object can supply or override fields via get_schema_data(key, context).

Sitemaps & hreflang

from seo_suite.sitemaps import SeoSitemap

class ArticleSitemap(SeoSitemap):
    i18n = True
    alternates = True
    x_default = True
    def items(self):
        return Article.objects.all()

SeoSitemap sets each <loc> to the page's resolved canonical, so the sitemap URL always matches rel="canonical". For on-page alternates, seo_suite.hreflang.build_hreflang_alternates(path, request) produces the same LANGUAGES-driven set the sitemap uses.

Versioned robots.txt

Serve robots.txt from the database with a full version history, so a traffic regression can be traced to a specific change. Add the route to your root urlconf:

# urls.py
urlpatterns = [
    # ...
    path("", include("seo_suite.urls")),   # serves /robots.txt
]

Then manage versions in the Django admin. Adding a version (the form is pre-filled with the current live content) publishes it on save; the version it replaces is kept as a read-only history row. To roll back, run the "Roll back" action on an earlier version. Exactly one version is live per site, enforced at the database level. Every version records created_at, activated_at, and the author, so the admin changelist is a timeline you can line up against your analytics. When no version is active, the view serves SEO_SUITE["ROBOTS_TXT_FALLBACK"]; SEO_SUITE["ROBOTS_SITEMAP_URLS"] are appended as Sitemap: lines.

Settings (SEO_SUITE)

Key Default Purpose
RESOLVER_CLASS seo_suite.resolver.Resolver swap the resolver
SITE_ID_RESOLVER None custom current-site callable
DEFAULTS {robots, og.type, ...} global metadata seed
SITE_DEFAULTS {} per-site metadata seed
CANONICAL_DOMAIN / FORCE_HTTPS_CANONICAL None / True canonical absolutization
LIST_CANONICAL_INCLUDES_PAGE True paginated-list canonical policy
HREFLANG_X_DEFAULT True emit x-default
CACHE_TTL 0 cache resolved payload (0 = off)
OBJECT_MODELS [] allowlist "app.model" for seoobject
DEFAULT_SCHEMA_PROFILES [] profiles applied site-wide
ROBOTS_TXT_FALLBACK "User-agent: *\nAllow: /" served when no robots.txt version is active
ROBOTS_SITEMAP_URLS [] URLs appended to robots.txt as Sitemap: lines
ROBOTS_CACHE_TTL 0 cache served robots.txt (0 = off)

Extending the suite (public contract)

These are versioned, stable APIs an extension package may rely on:

  • Registries — provider_registry.register(provider, priority=…), renderer_registry.register(name, callable).
  • Swappable classes — SEO_SUITE["RESOLVER_CLASS"], ["JSONLD_SERIALIZER"].
  • Signals — seo_metadata_resolved (mutation allowed), seo_cache_invalidate, seo_head_rendering.
  • Schema profiles — schema_registry.register(profile) with namespaced keys.
  • Abstract models — AbstractSeoPath, AbstractSeoObject, SeoColumnsMixin.
  • Autodiscovery — ship a top-level seo_extensions.py (imported at startup) or publish an entry point in the seo_suite.extensions group.
  • Templates — override any templates/seo_suite/* partial.

Everything imports from one surface:

# my_extension/seo_extensions.py
from seo_suite.extension import PRECEDENCE_PATH, Provider, SeoMetadata, provider_registry

class MyProvider(Provider):
    priority = 35
    def provide(self, context):
        return SeoMetadata.partial(meta_description="…")

provider_registry.register(MyProvider())

Development

pip install -e ".[dev]"
pytest                                   # contrib apps on, sites off
SEO_SUITE_TEST_SITES=1 pytest            # sites framework on
SEO_SUITE_TEST_NO_CONTRIB=1 pytest       # core only
SEO_SUITE_TEST_PRO=1 pytest              # with a fake extension package
ruff check seo_suite tests
tox                                      # full Python × Django matrix

License

MIT.

Metadata

Release files for django-seo-suite 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-seo-suite 0.3.0
File Size Uploaded Provenance Actions
django_seo_suite-0.3.0.tar.gz 53.3 kB With provenance Details for django_seo_suite-0.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for django-seo-suite 0.3.0
File Interpreter ABI Platform Actions
django_seo_suite-0.3.0-py3-none-any.whl Python 3 none any Details for django_seo_suite-0.3.0-py3-none-any.whl

Total release size: 106.3 kB

Release files / django_seo_suite-0.3.0.tar.gz

Download URL django_seo_suite-0.3.0.tar.gz
Size 53.3 kB
Tags Source
SHA-256 checksum
How to use checksums
1cbe8d87451f07f91ae1917dae6c9ef9ca05ef4f9a5d4fd8188fa2331bb87028
BLAKE2b-256 checksum
How to use checksums
784bd2741274e3f14e13c873d41acfed2a858dd51c102bbaed6f0f2f1d088952
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.

Transparency log

Release files / django_seo_suite-0.3.0-py3-none-any.whl

Download URL django_seo_suite-0.3.0-py3-none-any.whl
Size 53.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c5a25624c7391199254a215caee28a1ed947b6d46e5f8654747e2bf4ebf9c81a
BLAKE2b-256 checksum
How to use checksums
00d95f1500ea39f274d7235fb4f889498eff953c3a8bfdbe4db4b53b73cd80c5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.

Transparency log

Security for django-seo-suite 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for django_seo_suite-0.3.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page