Meta Tag
MetaTag is final — unlike every other middleware on this list, it isn't
designed for extension, and has no __invoke(). Call
MetaTag::set() from a controller/view; call MetaTag::render() once, from
the layout's <head>.
The two things it must have first
MetaTag has no configure() call — it reads two ambient sources the
consuming app owns:
- A global
APPconstant withname/base_urlkeys, defined once at boot (e.g. inconfig/bootstrap.php) — the title/URL fallback when a page doesn't set its own. SEO_DEFAULT_DESCRIPTION,SEO_DEFAULT_IMAGE,SEO_TWITTER_SITE,SEO_LOCALE,SEO_ORG_NAME,SEO_ORG_LOGOenvironment variables, read viagetenv().
Without both, fields that should have a sensible default silently fall back to 'Application' or an empty string instead.
Setting a page's meta
MetaTag::set([
'title' => 'Routing',
'description' => 'The full Route API.',
'canonical' => '', // '' resolves to the current request URL
'robots' => 'index, follow',
'og_type' => 'article',
]);
Or the fluent instance form, for building it up incrementally: MetaTag::set([])->title('Routing')->description('...').
Structured data (JSON-LD)
MetaTag::set(['json_ld' => MetaTag::schemaArticle([
'headline' => 'Routing',
'datePublished' => '2026-08-08',
])]);
MetaTag::set(['json_ld' => MetaTag::schemaBreadcrumb([
['name' => 'Docs', 'url' => '/docs'],
['name' => 'Routing', 'url' => '/docs/concepts/routing'],
])]);
Also available: schemaWebPage(), schemaOrganization(). Each is a plain
method returning an array — call set(['json_ld' => ...]) or
addJsonLd() as many times as needed; every call appends its own
<script type="application/ld+json"> tag.
Rendering
<head>
<?= MetaTag::render(); ?>
</head>
Emits <title>, description, robots, canonical link, every Open Graph tag, every Twitter Card tag, and every registered JSON-LD block — in that order, all HTML-escaped.
Passing meta into a view explicitly
MetaTag::toViewData(): array // title, description, canonical, og_url, images, json_ld, ... — for custom partial rendering
Not injected automatically — View has no implicit variable injection, so whoever wants this in a template passes it in themselves.
Next steps
- Request/Response Lifecycle — where
MetaTag::render()fits, insideView::render()'s layout step.