The SEO builder¶
goatseo.SEO (defined in goatseo.seo) collects everything about one page and renders its
head. It is framework-independent and cheap to create: build one per request.
Construction¶
from goatseo import SEO, Metadata
seo = SEO(
title="My article",
description="An interesting article",
canonical="https://example.com/articles/hello",
defaults=Metadata(site_name="Example"), # SITE layer
)
SEO(**metadata) accepts every Metadata field as a keyword (typed through the
MetadataFields TypedDict) plus defaults, open_graph, twitter and schema.
Setters¶
Every setter writes the request layer and returns the same object, so calls chain:
(
seo.title("My article")
.description("An interesting article")
.canonical("/articles/hello")
.language("en")
.locale("en_US")
.site_name("Example")
.author("Jane Doe")
.publisher("Example Media")
.keywords("python", "seo")
.theme_color("#0f172a")
)
| Method | Effect |
|---|---|
update(**fields) |
Merge several metadata fields at once |
robots(robots=None, /, **directives) |
seo.robots(index=False) or seo.robots(Robots(...)) |
hreflang(language, href) / hreflang(mapping) |
Append hreflang alternates |
alternate(href, *, type=None, title=None, media=None) |
Append a rel="alternate" link |
open_graph(graph=None) |
Enable Open Graph, optionally with an OpenGraph model |
twitter(card=None) |
Enable a Twitter/X card, optionally with a TwitterCard model |
schema(*items) |
Append Schema.org models rendered as JSON-LD |
defaults(metadata, precedence=Precedence.SITE) |
Merge a lower-precedence layer |
from_object(source, precedence=Precedence.MODEL) |
Use source.seo_metadata() as a layer |
copy() |
Independent copy, useful to derive a page from a template |
hreflang is overloaded: Pyright accepts either two strings or one mapping.
seo.hreflang("fr", "/fr/articles/hello")
seo.hreflang({"en": "/articles/hello", "x-default": "/articles/hello"})
Reading¶
| Member | Returns |
|---|---|
metadata |
The resolved Metadata after merging every layer |
graph |
The OpenGraph model with fallbacks applied, or None |
card |
The TwitterCard with fallbacks applied, or None |
schemas |
The Schema.org models, as a tuple |
audit() |
SEO issues found in the resolved metadata |
Fallbacks¶
Open Graph and Twitter fields left empty are derived from the resolved metadata when rendering:
og:title,og:description,og:url,og:site_name,og:localecome fromtitle,description,canonical,site_nameandlocale.twitter:title,twitter:descriptioncome from the metadata,twitter:imagefrom the first Open Graph image.
Explicit values always win.
Rendering¶
seo.render() # SafeHtml
seo.render(context, separator="", json_indent=2)
list(seo.elements(context)) # HeadElement objects
context is any RequestContext (an object with a url string property); relative URLs are
resolved against it. The rendering order is: metadata, Open Graph, Twitter, JSON-LD.
Auditing¶
seo.audit() (or goatseo.core.audit(metadata)) reports problems that are valid HTML but
hurt search visibility:
| Code | Severity |
|---|---|
missing-title |
error |
title-too-long (over 60 characters) |
warning |
missing-description |
warning |
description-length (outside 50 to 160 characters) |
info |
missing-canonical, relative-canonical |
info |
duplicate-hreflang |
error |
missing-x-default |
info |