laravel-seo: meta tags straight from your Eloquent models
A package to handle the SEO in any Laravel application, big or small.
At a glance
- What is it?
- laravel-seo is a MIT licensed Laravel package that renders the head tags of a page from data attached to your Eloquent models, or from values computed on the fly, so a post gets a title, description, image, OpenGraph, Twitter and structured data tags without anyone writing a template for them. The judgement is that the package's value is in the defaults and the fallbacks, and that the configuration file is where you need to read before enabling anything.
- Who is it for?
- laravel-seo fits a Laravel team that wants consistent head tags across models without maintaining a hand written partial per content type, and it fits teams that would rather set a fallback image and a sitewide title suffix in one config file than repeat them in every controller.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 18 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Why the helpers-only packages were not enough
The README states the problem in blunt terms, and it should be read before the feature list. There are not many SEO packages for Laravel, and the ones that exist are quite complex to set up and very decoupled from the database. They provided helpers to generate the tags, but you still had to use those helpers, nothing was generated automatically, and they almost did not work out of the box. The design of laravel-seo answers that complaint directly. It generates valid and useful meta tags straight out of the box with limited initial configuration, while still providing a simple but powerful API to work with. The comparison the author draws is with Spatie's media library package, where the package works in almost the same way but only for SEO, and the argument is that a website is best off having attention for SEO right from the beginning rather than retrofitting it later.
The full set of tags the package renders
The list of what gets generated is eight items, and between them they cover what a crawler and a social card need. There is the title tag, with a sitewide suffix, and meta tags for author, description, image and robots among others. Then come OpenGraph tags for Facebook, LinkedIn and the rest, and Twitter tags. Structured data is supported as Article, Breadcrumbs, FAQPage, or any custom schema, which is the piece that lets a page appear as a rich result rather than a plain link. The favicon is handled as well, and there is a robots tag and an alternates links tag for the hreflang style of alternate versions of a page. The consequence for an application is that one call in the layout replaces a set of conditionals, and the risk is that you no longer have a single place in the codebase where you can see what a page advertises, which is a problem if you ever need to debug why a social card looks wrong.
SEO data lives on the model, not in a second place
The usage pattern is what separates this from a helper library. Each model gets an associated SEO record, and you write to it the same way you would write to any other relation. The README shows a post being updated with a title and a description:
$post = Post::find(1);
$post->seo->update([
'title' => 'My great post',
'description' => 'This great post will enhance your live.',
]);Rendering then takes one line in the head of the layout, and the comment in that template says it plainly, there is no need to separately render a title tag or any other meta tags:
<!DOCTYPE html>
<html>
<head>
{!! seo()->for($post) !!}
{{-- No need to separately render a <title> tag or any other meta tags! --}}
</head>The blade directive is deliberately unescaped output, since it emits markup rather than text, and the seo() helper with a for() call is the only thing a template needs to know about the package. The trade-off in the design is that the SEO record is a separate row per model, so a post and its tags live in different tables and have to be kept in step, which is the coupling the README complains about in other packages, handled here by a first party relation rather than by a convention.
Dynamic data means you never have to save the tags
The second usage path is the one that removes the write entirely. A model can use the HasSEO trait and implement getDynamicSEOData() to return an SEOData object, and the package will use it without anything being stored. The README describes the effect as requiring zero additional work from you or from your users:
class Post extends Model
{
use HasSEO;
public function getDynamicSEOData(): SEOData
{
$pathToFeaturedImageRelativeToPublicPath = // ..;
// Override only the properties you want:
return new SEOData(
title: $this->title,
description: $this->excerpt,
image: $pathToFeaturedImageRelativeToPublicPath,
);
}
}The point of the comment about overriding only the properties you want is that everything not supplied falls back to the configuration. That is a good design for a package that has to work for a model you do not control, and a trap for a package that has to be predictable, because a typo in a property name now produces a missing tag rather than an error. The dynamic path is what you want for a post whose title and excerpt already exist on the model, and the stored path is what you want when an editor wants to override the description for search without changing the post.
Installing and publishing the configuration
The install is a Composer require, and the package name is the one to use:
composer require ralphjsmit/laravel-seoAfter that the migration and the configuration file have to be published, and they are published under separate tags so you can do one without the other:
php artisan vendor:publish --tag="seo-migrations"
php artisan vendor:publish --tag="seo-config"The next step is to open the newly published config/seo.php and check the values, and the README is clear that they are all defaults. The file starts with site_name, which is used in the OpenGraph tags, and a sitemap setting whose exact path is output as given, so a hardcoded URL and a relative path both work, with the relative path recommended and the leading slash required so that the search engine treats it as relative to the root domain rather than the current URL. The comment in that block also points at the spatie/laravel-sitemap package as a good way to generate sitemaps. After that you run the migrations and the tags appear in the head of your pages.
The configuration decisions that change what crawlers see
Four settings in config/seo.php have consequences that are easy to miss. The first is title inference. With infer_title_from_url enabled, a path like /foo/about-me produces the title About me with the site suffix appended, which is convenient on pages with no Eloquent model behind them and slightly risky on pages where the URL is a slug rather than a sentence. The suffix setting adds that suffix to every page, and homepage_title is separate, because the suffix is not applied to the homepage, so if you want it there you add it yourself. The second is the robots block, where the default value is a specific string of three directives, max-snippet:-1, max-image-preview:large and max-video-preview:-1, and the force_default switch exists for the case where you need noindex and nofollow across the entire site with no way to overwrite it from an SEOData object. The third is canonical_link, which controls whether self-referencing canonical tags are added to every page, where the config notes the debate about whether that is good practice and cites Google and Yoast recommending it. The fourth is the favicon setting, which accepts ico, png, gif, jpeg and svg, where the url is generated with secure_url() and the file therefore has to be reachable from the public folder.
State of the package and where the code lives
The package is MIT licensed, which matters because a package that writes into your head tags is a dependency you will keep for as long as the site runs. The releases are frequent and unremarkable, with 1.8.1 on 2026-03-26, 1.8.2 on 2026-08-08 and 1.8.3 on 2026-10-05, and the last push to the main branch was on 2026-10-05 with the repository not archived. The version line tells you something about the project's shape, a one-point-eight series with patch releases means the API has settled and the work is maintenance plus small additions. The tree shows a conventional Laravel package layout, with src holding the package code, config holding the published configuration, database holding the migrations, resources holding views and translations, docs holding the documentation, and tests alongside a phpunit configuration. The tooling is modern for Laravel, with pint.json for code style, a CHANGELOG.md, an editorconfig and a composer.json, and there is a todo file in the root, which is usually a sign that the author keeps the remaining ideas in the open rather than in an issue tracker. The homepage for the package is the author's own write-up at ralphjsmit.com/laravel-seo-package, and that article is the place to look for the reasoning behind the defaults.
Editorial conclusion
laravel-seo fits a Laravel team that wants consistent head tags across models without maintaining a hand written partial per content type, and it fits teams that would rather set a fallback image and a sitewide title suffix in one config file than repeat them in every controller. It is the wrong choice for an application whose pages are mostly one-off landing pages, since the model relationship and the title inference are aimed at repeated, model backed content, and it is a poor fit if you need per tag control at the template level, because the package deliberately takes over the head. Before you enable it, read the robots settings in config/seo.php, decide whether you want the default robots string or a forced noindex across the whole site, and check the title suffix behaviour, since the homepage is handled separately from every other page.
Frequently asked questions
How do I install laravel-seo in a Laravel application?
Run composer require ralphjsmit/laravel-seo, then publish the migration and the configuration file with php artisan vendor:publish --tag="seo-migrations" and php artisan vendor:publish --tag="seo-config". After that you edit the published config/seo.php, where the settings are described as defaults.
Can laravel-seo build tags from my model without storing them?
Yes. Use the HasSEO trait on the model and implement getDynamicSEOData(): SEOData, overriding only the properties you want, such as title, description and image. The README says this requires zero additional work from you or from your users, because nothing is saved to the SEO model.
Which meta tags does laravel-seo render?
A title tag with a sitewide suffix, meta tags such as author, description, image and robots, OpenGraph tags, Twitter tags, structured data in the form of Article, Breadcrumbs, FAQPage or a custom schema, a favicon, a robots tag and an alternates links tag. A single call in the layout, seo()->for($post), renders them all.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/ralphjsmit-laravel-seo)Was this useful?