Guide: Site-wide defaults

The SEO_SUITE["DEFAULTS"] and SEO_SUITE["SITE_DEFAULTS"] settings are a baseline layer of metadata that every page inherits. Higher-priority providers (model fields, views, path rules) override them field by field.


Global defaults

Add SEO_SUITE to settings.py. Any key you set is deep-merged with the built-in defaults, so you only need to specify what you want to change:

SEO_SUITE = {
    "DEFAULTS": {
        "title_suffix": " | My Blog",
        "robots": "index,follow",
        "og": {
            "type": "website",
            "site_name": "My Blog",
        },
    },
}

The built-in global defaults (applied unless you override them):

Field Built-in value
title_suffix "" (no suffix)
robots "index,follow"
og.type "website"

CANONICAL_DOMAIN and HTTPS

By default, canonical URLs are built from the request's Host header. Use CANONICAL_DOMAIN to enforce a specific domain regardless of the request:

SEO_SUITE = {
    "CANONICAL_DOMAIN": "www.example.com",
    "FORCE_HTTPS_CANONICAL": True,  # default: True
}

With CANONICAL_DOMAIN set, all canonical URLs use https://www.example.com even in local development or behind a reverse proxy that doesn't pass the correct Host header.

FORCE_HTTPS_CANONICAL upgrades any http:// canonical to https:// even when CANONICAL_DOMAIN is not set.


Per-site defaults (multi-site projects)

When django.contrib.sites is installed (or SITE_ID is set), you can provide different defaults per site:

SEO_SUITE = {
    "DEFAULTS": {
        "title_suffix": " | My Network",
        "og": {"site_name": "My Network"},
    },
    "SITE_DEFAULTS": {
        1: {
            "title_suffix": " | My Blog",
            "og": {"site_name": "My Blog"},
        },
        2: {
            "title_suffix": " | My Shop",
            "og": {"site_name": "My Shop"},
        },
    },
}

The site-specific block (priority 20) merges on top of the global defaults (priority 10) using the same field-by-field logic. You can also use None as a key in SITE_DEFAULTS to apply a default to all sites that don't have a specific entry:

"SITE_DEFAULTS": {
    None: {"robots": "noindex"},   # applies when no site-specific match
    1:    {"robots": "index,follow"},
},

How site ID is resolved

The current site ID is determined by the following fallback chain:

  1. SEO_SUITE["SITE_ID_RESOLVER"] — a callable that receives the request and returns an integer site ID. Use this for custom logic (subdomain routing, request-header-based selection, etc.).
  2. django.contrib.sites.shortcuts.get_current_site(request) — if django.contrib.sites is installed.
  3. settings.SITE_ID — if defined.
  4. None — single-site, no per-site defaults apply.

Custom resolver example:

# myapp/utils.py

def my_site_id_resolver(request):
    if request.get_host().startswith("shop."):
        return 2
    return 1
# settings.py
SEO_SUITE = {
    "SITE_ID_RESOLVER": "myapp.utils.my_site_id_resolver",
}

DEFAULT_SCHEMA_PROFILES

Add JSON-LD schema profiles to every page site-wide:

SEO_SUITE = {
    "DEFAULT_SCHEMA_PROFILES": ["WebSite", "Organization"],
}

These profiles run at the global defaults layer (priority 10). Per-model and per-view profiles accumulate alongside them. JSON-LD blocks are never replaced by higher-priority layers.


Caching resolved metadata

For high-traffic sites, enable resolved-payload caching. The cache key is per-object (model + PK + generation counter), so stale values are invalidated automatically on save and delete:

SEO_SUITE = {
    "CACHE_TTL": 300,           # seconds; 0 = disabled (default)
    "CACHE_BACKEND": "default", # any Django cache alias
    "CACHE_KEY_PREFIX": "seosuite:v1",
}

Only resolutions with a stable object identity (model instance with a PK) are cached. List pages and model-less views are not cached here.