Skip to content

Customization

Metadata from your domain objects

Any object with a seo_metadata() method satisfies the SupportsMetadata protocol. No base class, no registration.

from dataclasses import dataclass
from goatseo import Metadata, SEO


@dataclass
class Article:
    title: str
    summary: str
    slug: str

    def seo_metadata(self) -> Metadata:
        return Metadata(
            title=self.title,
            description=self.summary,
            canonical=f"/articles/{self.slug}",
        )


seo = SEO().from_object(Article("Hello", "An interesting article", "hello"))

from_object writes the MODEL layer, so values set by the view or the request still win. The Django SEOMixin calls it automatically for DetailView.object.

Adding head elements

SEO.elements() yields plain HeadElement dataclasses. Add your own and render them with the same escaping:

from goatseo.core import Link, Meta, render_elements

elements = [
    *seo.elements(context),
    Meta("name", "google-site-verification", "abc123"),
    Link("icon", "/favicon.svg", type="image/svg+xml"),
]
html = render_elements(elements)

Custom renderers

A renderer is any callable matching ElementRenderer[T]: it takes a value and returns an iterable of head elements. Writing one for your own model keeps the rendering rules (escaping, URL resolution) in one place:

from collections.abc import Iterator
from dataclasses import dataclass

from goatseo.core import ElementRenderer, HeadElement, Meta, render_elements


@dataclass(frozen=True)
class AppLinks:
    ios_url: str
    android_package: str


def app_links_elements(links: AppLinks) -> Iterator[HeadElement]:
    yield Meta("property", "al:ios:url", links.ios_url)
    yield Meta("property", "al:android:package", links.android_package)


renderer: ElementRenderer[AppLinks] = app_links_elements
render_elements(renderer(AppLinks("example://home", "com.example")))

The built-in renderers follow the same shape: metadata_elements, open_graph_elements, twitter_elements and jsonld_element.

Changing the output format

render_elements(elements, separator="\n", json_indent=None) controls the separator between tags and the indentation of JSON-LD. SEO.render forwards both options.