SEO Payload¶
build_seo_payload() returns a SEOPayload dataclass.
It behaves like a dict for most use cases.
The payload contains every tag your <head> needs.
Full payload structure¶
{
"title": "My Post - My Blog",
"description": "A brief description of the post.",
"canonical": "https://blog.example.com/posts/my-post",
"robots": "index,follow",
"og": {
"type": "article",
"title": "My Post - My Blog",
"description": "A brief description of the post.",
"url": "https://blog.example.com/posts/my-post",
"image": "https://cdn.example.com/hero.jpg",
"image:width": 1200,
"image:height": 630,
"image:alt": "Hero image description",
"site_name": "My Blog",
"locale": "en_US",
"locale:alternate": ["es_ES", "fr_FR"],
"audio": "https://example.com/audio.mp3",
"video": "https://example.com/video.mp4",
},
"twitter": {
"card": "summary_large_image",
"title": "My Post - My Blog",
"description": "A brief description of the post.",
"image": "https://cdn.example.com/hero.jpg",
"image:alt": "Hero image description",
"site": "@mysite",
"creator": "@janedoe",
},
"schema_jsonld": {
"@context": "https://schema.org",
"@type": "Article",
"name": "My Post - My Blog",
"url": "https://blog.example.com/posts/my-post",
"description": "A brief description of the post.",
"image": "https://cdn.example.com/hero.jpg",
},
}
Top-level keys¶
| Key | Type | Description |
|---|---|---|
title |
str |
Page title, with title template applied |
description |
str |
Meta description from fallback chain |
canonical |
str |
Fully normalized canonical URL |
robots |
str |
Robots meta content string |
og |
OGPayload (dict-like) |
Open Graph tags |
twitter |
TwitterPayload (dict-like) |
Twitter Card tags |
schema_jsonld |
dict \| list[dict] \| None |
Schema.org JSON-LD |
title¶
Fallback chain: SEOOverrides.meta_title > SEOEntity.title > "Untitled".
After resolution the title template from config is applied.
description¶
Fallback chain: SEOOverrides.meta_description > SEOEntity.excerpt > body snippet > "".
The HTML body is only parsed when neither the override nor the excerpt is available.
If you provide an explicit excerpt or meta_description, the body_html is never touched.
This avoids expensive HTML parsing when the description is already determined.
The body snippet converts HTML to plain text and truncates at 160 characters.
canonical¶
Fallback chain: SEOOverrides.canonical_url > normalized route path.
The route path runs through the full URL normalization pipeline.
robots¶
Robots dataclass field |
Type | Default |
|---|---|---|
index |
bool |
True |
follow |
bool |
True |
max_snippet |
int \| None |
None |
max_image_preview |
str \| None |
None |
max_video_preview |
int \| None |
None |
from seoslug import Robots
# Structured
config = SEOConfig(
...,
default_robots=Robots(index=False, follow=False),
search_robots=Robots(index=False, follow=True, max_snippet=-1),
)
# Or string
overrides = SEOOverrides(robots="noindex,nofollow")
The output is always a serialized string like "index,follow" or "noindex,follow,max-snippet:-1".
og (Open Graph)¶
| Key | Type | Source |
|---|---|---|
type |
str |
"article" for post/video, "website" otherwise |
title |
str \| None |
Override > resolved title |
description |
str \| None |
Override > resolved description |
url |
str \| None |
Canonical URL |
image |
str \| None |
Override > entity.featured_image > config.default_og_image |
image:width |
int \| None |
From OGImage.width |
image:height |
int \| None |
From OGImage.height |
image:alt |
str \| None |
From OGImage.alt |
site_name |
str \| None |
From config.site_name |
locale |
str \| None |
From config.locale |
locale:alternate |
list[str] \| None |
From config.locale_alternate |
audio |
str \| None |
Override og_audio |
video |
str \| None |
Override og_video |
OGImage dataclass¶
from seoslug import OGImage
img = OGImage(
url="https://cdn.example.com/hero.jpg",
width=1200,
height=630,
alt="Hero image",
)
Accepted anywhere an image is: entity.featured_image, overrides.og_image, overrides.twitter_image, config.default_og_image.
twitter (Twitter Card)¶
| Key | Type | Source |
|---|---|---|
card |
str |
Override > "summary_large_image" |
title |
str \| None |
Override > resolved og:title |
description |
str \| None |
Override > resolved og:description |
image |
str \| None |
Override > resolved og:image |
image:alt |
str \| None |
From OGImage.alt |
site |
str \| None |
From config.twitter_site |
creator |
str \| None |
From override.twitter_creator |
schema_jsonld¶
Auto-generated from entity type. See Schema JSON-LD.
Single schema is a dict. Multiple schemas (e.g. with breadcrumbs) is a list. None when omitted.
Social metadata fields¶
locale and locale:alternate are set at config level and flow into the OG payload:
Accessible in the payload as:
og:audio and og:video come from overrides:
overrides = SEOOverrides(
og_audio="https://example.com/audio.mp3",
og_video="https://example.com/video.mp4",
)
twitter:site is a config-level field:
twitter:creator is a per-entity override: