Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Seo Title Laravel Package

murdercode/seo-title

A Laravel package for managing SEO titles and meta tags. Generate dynamic, model-aware page titles and descriptions with customizable templates, length limits, and sensible fallbacks, so every route or Eloquent record gets consistent, search-friendly metadata.

View on GitHub
Deep Wiki
Context7

Getting Started

murdercode/seo-title is a small Laravel Nova 4 field that wraps a text input with SEO-oriented UI (character counting and a search-result style preview) so editors can see how a title will look in Google while typing.

Install

composer require murdercode/seo-title
php artisan nova:publish   # only if the package ships compiled assets

Requirements to confirm before installing: PHP 8.1+, Laravel 9/10, and Nova 4 (this package targets the Nova 4 field API; it will not work on Nova 3).

Where to look first

  1. vendor/murdercode/seo-title/README.md — the canonical usage snippet and the exact field class/namespace (verify it; the namespace does not always mirror the Composer name).
  2. vendor/murdercode/seo-title/src/ — usually a single SeoTitle.php extending Laravel\Nova\Fields\Text. Read component() to confirm the Vue component name, and check whether it adds rules, maxlength, or a preview option.
  3. vendor/murdercode/seo-title/resources/js/ — the preview/counter logic lives here, which tells you what the field actually validates client-side.

First use case

Add a seo_title string column and drop the field into a Resource:

// database/migrations/...
$table->string('seo_title', 70)->nullable();

// app/Nova/Page.php
use Murdercode\SeoTitle\SeoTitle; // confirm namespace from src/SeoTitle.php

public function fields(NovaRequest $request)
{
    return [
        SeoTitle::make('SEO Title', 'seo_title')
            ->rules('nullable', 'string', 'max:60')
            ->help('Aim for 50–60 characters.'),
    ];
}

Then render it in your front end:

<title>{{ $page->seo_title ?? $page->title }}</title>

That fallback pattern (SEO field, then content title) is the single most useful habit with a field like this — it means an empty SEO title never produces an empty <title>.

Implementation Patterns

Pair it with a meta description field. The field is only useful if the surrounding SEO block is coherent. Group title, description, and slug in a Nova panel or a separate "SEO" tab so editors see them together:

Panel::make('SEO', [
    SeoTitle::make('SEO Title', 'seo_title')->rules('nullable', 'max:60'),
    Text::make('Meta Description', 'meta_description')
        ->rules('nullable', 'max:160')
        ->hideFromIndex(),
    Text::make('Slug', 'slug')->rules('required', 'alpha_dash'),
]),

Store SEO data in a JSON column when you have many fields or locales. A single seo JSON column keeps migrations stable, and Nova handles it with dot notation:

SeoTitle::make('SEO Title', 'seo.en.title')->rules('nullable', 'max:60'),
SeoTitle::make('SEO Title (FR)', 'seo.fr.title')->rules('nullable', 'max:60'),

Centralize rendering with an accessor or a small helper rather than repeating ?? $model->title in every Blade view:

// app/Models/Page.php
public function getSeoTitleAttribute(): string
{
    return $this->attributes['seo_title']
        ?? $this->title;
}

Use it on the index view sparingly. The SERP preview is a form component; on index tables it degrades to a plain value. If you want a quick editorial audit, show the field on index and sort by it to find empty SEO titles:

SeoTitle::make('SEO Title', 'seo_title')->sortable()->exceptOn('detail'),

Generate a default from content on create. A common workflow is to prefill the SEO title from the page title via a creating observer so the field is never blank, while still allowing editors to override it.

Integrate with sitemap/SEO packages. If you use spatie/laravel-sitemap or a seoable trait, map this column into the model's meta accessor rather than calling the field anywhere outside Nova — the field is strictly an admin-side concern.

Validation belongs outside the field. Treat the Nova field as presentation; put the length limits in a Form Request or in the model so API/import paths get the same protection.

Gotchas and Tips

This package is very small (2 stars, 0 dependents, last release Feb 2024). Assume minimal maintenance. Pin the version in composer.json, and be prepared to vendor the field class into app/Nova/Fields/ if you need a fix — it is almost certainly a thin Text subclass with a custom Vue component, so copying it is a 20-minute job.

Do not rely on the client-side character counter for validation. Counters are cosmetic. Always add ->rules('max:60') (or a model-level rule) — otherwise an editor pasting a long string will happily save 300 characters.

Character count is not pixel width. Google truncates by rendered pixel width, not characters. A 60-character string of Ws and a 60-character string with narrow letters render very differently. If the preview looks wrong, that is why — it is a heuristic, not a guarantee.

Multibyte characters may be miscounted. If the package's counter uses strlen rather than a Unicode-aware length, CJK titles and emoij will report inflated counts. Check the JS component; if it is wrong, override the Vue component or accept the inaccuracy and document it.

Confirm the actual namespace and component name. The Composer name (murdercode/seo-title) and the PHP namespace (Murdercode\SeoTitle in the example above) do not always match, and the repo name (Nova4-SeoTitle) differs again. A Class not found error almost always means you guessed the namespace — open src/ and copy the real one.

Missing assets after install. If the field renders as a plain input or the JS errors, the compiled assets were never published. Run php artisan nova:publish and, if the package registers assets via a service provider, confirm Nova::script() / Nova::style() calls exist. Clear storage/app/nova-* and re-publish if you upgraded.

Nova 4 only. Installing alongside Nova 3 will either fail dependency resolution or silently break the field. Check the composer.json require block for the Nova constraint before adopting.

Empty vs null. ->nullable() in the migration plus a fallback accessor prevents the <title></title> problem. Also decide whether an empty string should be treated as "use the default" — normalize '' to null in a mutator so the fallback logic stays simple.

Extension points. Because it extends Text, you can chain the usual Nova modifiers — ->placeholder(), ->help(), ->hideFromIndex(), ->required() — and you can subclass it to add your own default rules:

class SeoTitle extends \Murdercode\SeoTitle\SeoTitle
{
    public function __construct($name, $attribute = null, $resolveCallback = null)
    {
        parent::__construct($name, $attribute, $resolveCallback);
        $this->rules('nullable', 'string', 'max:60');
    }
}

Testing. In Nova tests, resolve the field and assert against the underlying model attribute rather than the component; there is no meaningful rendering to assert for a custom field in a feature test. Use Nova::actingAs() and put() against the resource endpoint.

Have an exit plan. Given the adoption numbers, budget for replacing this with a plain Text::make('SEO Title')->maxlength(60)->help(...) plus your own Blade preview if the package goes stale. The functional delta is small, and the migration is a one-line field swap plus deleting a column of custom markup.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums