Laravel Head is a new first-party package from the Laravel open-source team, shown on stage during Taylor Otwell's day-one keynote at Laracon US 2026. It gives you a fluent API for everything that goes in your document's <head> element — titles, meta descriptions, canonical URLs, Open Graph tags, robots directives, JSON-LD structured data, and resource hints — and resolves them per request for Blade, Livewire, and Inertia applications.
Here's what the package covers:
- Five-layer precedence — page defaults, route group metadata, route metadata, runtime metadata, and error metadata merge field by field
- Route-level metadata — a
withHead()method on routes, groups, resources, and singletons that stores plain arrays and stays compatible with cached routes - Open Graph and X cards —
og(),ogImage(),ogVideo(), andogAudio()methods, with Twitter tags derived from the same title, description, and image - JSON-LD schemas — builders for
article,blogPosting,product,offer,brand,breadcrumbs,faq,organization,person,webPage, andwebSite, plus registration for custom types - Performance and discovery —
preload(),prefetch(),preconnect(),dnsPrefetch(),paginate(),alternates(), andfeed() - Browser and app metadata — theme colors with media queries, favicons, Apple touch icons, and a
pwa()method for installable web apps - Error page metadata — per-status title, description, and robots values registered once in a service provider
Metadata Resolves in Five Layers
Page metadata comes from five layers, ordered from lowest to highest priority: page defaults, route group metadata, route metadata, runtime metadata, and error metadata. Higher layers replace lower layers one field at a time, so a runtime title replaces a route title without touching the route's description.
Site-wide defaults go in a service provider:
use Laravel\Head\Enums\OgType;use Laravel\Head\Facades\Head;use Laravel\Head\HeadBuilder; Head::defaults(function (HeadBuilder $head) { $head ->title('Laravel', suffix: ' - Laravel') ->description('Build something great.') ->canonical() ->og(siteName: 'Laravel', type: OgType::Website) ->searchableByRobots() ->preconnect('https://fonts.example.com');});
The suffix registered in defaults carries into higher layers, so Head::title('About') renders About - Laravel. Pass exact: true when a title should ignore an inherited prefix or suffix. Calling canonical() with no argument uses the current request URL and normalizes it to https, which you can turn off with forceHttps: false.
Robots directives accept a raw string, RobotsRule enum cases, or a list mixing both. The searchableByRobots() and hiddenFromRobots() shorthands render all and none respectively.
Metadata on Routes
For pages whose metadata is known ahead of time, withHead() attaches it directly to the route definition:
Route::view('/contact', 'contact') ->name('contact') ->withHead( title: 'Contact Us', description: 'Get in touch.', );
It works on groups at any position in the chain, and on resource and singleton routes:
Route::withHead(robots: 'noindex, nofollow') ->prefix('admin') ->name('admin.') ->group(function () { Route::get('/dashboard', DashboardController::class) ->name('dashboard') ->withHead(title: 'Dashboard'); });
Under the hood, withHead() writes plain arrays through Laravel's native route metadata API — the equivalent of calling metadata() with the attributes nested under a head key — so route caching still works. The named arguments are deliberately limited to the package's built-in properties so static analysis catches typos; anything registered by a custom tag builder goes through an extensions array instead.
Anything that isn't known until the request arrives is set at runtime through the facade:
public function show(Post $post){ Head::title($post->title) ->description($post->description) ->when($post->isDraft(), fn ($head) => $head->hiddenFromRobots()); return view('posts.show', ['post' => $post]);}
Single-value fields take the last call. Repeatable fields keep multiple entries, but reusing the same key updates the earlier one — for ogImage(), the URL is the key, so calling it twice with the same URL merges the attributes rather than emitting two tags.
Open Graph, X Cards, and Schemas
Document title and description fill in missing og:title and og:description automatically. Register a Twitter card type in your defaults, and the card tags are derived from the same values:
use Laravel\Head\Enums\TwitterCard; Head::defaults(fn (HeadBuilder $head) => $head->twitter( card: TwitterCard::SummaryWithLargeImage,));
Setting a title, description, and ogImage() on a page then produces both sets of tags, with twitter() and twitterImage() available when a page needs different social copy.
Structured data uses a separate Schema facade, with builders that nest:
use Laravel\Head\Enums\OfferAvailability;use Laravel\Head\Facades\Schema; Head::schema( Schema::product() ->name($product->name) ->offers( Schema::offer() ->price($product->price) ->currency('USD') ->availability(OfferAvailability::InStock) ));
Breadcrumbs and FAQs take arrays in bulk — Schema::breadcrumbs()->items([...]) assigns positions in the order given. Unknown factory methods produce a generic schema object, and you can register a typed class with a #[SchemaType] attribute for anything you want first-class methods on. Invalid JSON-LD throws an exception outside production and logs a warning in production.
Rendering Across Blade, Livewire, and Inertia
Blade and Livewire both render the resolved tags with a @head directive in the layout:
<head> <meta charset="utf-8"> @head</head>
The directive renders synchronously, so metadata has to be defined before the layout renders. Livewire needs no extra configuration — the resolver is request-scoped, so each wire:navigate visit fetches a document whose head reflects the destination route.
Inertia gets the most machinery. When Inertia is installed, the package shares the resolved head as an array of rendered element strings under a head prop on every page object:
{ "props": { "head": [ "<title data-inertia=\"title\">Dashboard - Laravel</title>", "<meta data-inertia=\"description\" name=\"description\" content=\"Your application overview.\">" ] }}
You enable Inertia's serverHead option — available in Inertia 3.5 and later — wherever you call createInertiaApp(), including your SSR entry point if you have one. Each element carries a stable data-inertia key that Inertia adopts and keeps synchronized across visits, instant visits, and back/forward navigation. Because @head puts the tags in the initial HTML response, crawlers and link-preview bots read them without running JavaScript, and no client-side <Head> component is needed.
Tags that never change for the session — viewport, color scheme, favicons, manifest — can be registered separately with Head::inertiaGlobals(). Those are excluded from the head prop, rendered without ownership attributes, and never updated after the first response.
If you're adding this to an existing Inertia app, remove title callbacks from app.tsx and ssr.tsx and move anything currently in Inertia's <Head> component over, so the two never manage the same element.
Installation
Laravel Head requires PHP 8.3 and Laravel 13.17 or later, and installs through Composer:
composer require laravel/head
From there, register defaults in a service provider and add @head to your layout. Applications that want the resolved metadata as data rather than markup can call Head::toArray(), which returns titles, Open Graph values, JSON-LD schemas, and the rest as a structured array.
Full documentation is in the docs, and the source is on the laravel/head GitHub repository.