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 Meta Laravel Package

iki/seo-meta

iki/seo-meta is a Laravel package for managing SEO metadata in your application, letting you define and render page titles, meta descriptions, canonical URLs, and social sharing tags from a clean, fluent PHP API.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check before you install. This package has 0 stars, 0 dependents, and its last release was July 2023. Treat it as unmaintained code you are adopting, not a library you are consuming. Budget time to read the source before wiring it into a resource — the whole field is likely a few hundred lines, and you will probably need to fork it.

  1. Confirm the actual composer name and Nova constraint. The repository slug (nova-seo-field) and the package name (iki/seo-meta) do not match, which usually means a rename or a packagist/vendor mismatch. Check composer.json in the repo for name, require, and the extra.laravel.providers entry before running:

    composer require iki/seo-meta
    

    The release date implies Nova 4.x. If you are on Nova 5, expect to resolve conflicts in Field base-class signatures and Vue asset registration manually.

  2. Look at these files first, in this order:

    • src/*Field.php — the field class; tells you the column name, the meta()/rules() defaults, and whether it serializes JSON.
    • resources/js/field.js + .vue — what is actually editable (title, description, OG, Twitter, canonical?).
    • database/migrations/ — whether it ships a migration or expects you to add a column.
    • config/ — defaults for title length, fallbacks, locales.
  3. Add the storage column yourself. Do not rely on a package migration for a field of this age. A json (or longText cast to array) column is the safe bet:

    Schema::table('pages', fn (Blueprint $t) => $t->json('seo')->nullable());
    

    On the model: protected $casts = ['seo' => 'array'];

  4. First use case: attach the field to a Page or Post resource that has title and body columns, let an editor fill in an SEO title/description, and render it in your layout's <head>. Get that single happy path working end-to-end (edit in Nova → save → inspect page source) before touching fallbacks, locales, or sitemaps.

Implementation Patterns

Wrap the field instead of scattering it. Create one reusable factory so every resource gets identical behavior:

// app/Nova/Fields/SeoMeta.php
class SeoMeta extends \Iki\SeoMeta\SeoMetaField
{
    public function __construct($attribute = 'seo')
    {
        parent::__construct($attribute);
        $this->rules('array');
    }
}

Then SeoMeta::make() in Page, Post, Product, etc. When (not if) you forgo the upstream package, you change one file.

Normalize access with a value object or a trait. Raw array access from Blade ($page->seo['description'] ?? '') rots fast, especially once you add locales. Put a small resolver on the model or a dedicated SeoData object:

trait HasSeoMeta
{
    public function seo(string $key, ?string $fallback = null): ?string
    {
        return data_get($this->seo, $key) ?: $fallback;
    }
}

// title fallback chain: explicit SEO title -> model title -> app name
$title = $page->seo('title', $page->title.' — '.config('app.name'));

Render through a Blade component, not the layout directly. A single <x-seo-meta :model="$page" /> component that emits title, description, canonical, OG, and Twitter tags keeps the head clean and gives you one place to sanitize and truncate. Fall back to sensible app-level defaults so a page with an empty field never ships blank tags.

Keep OG images out of the JSON blob. Store a path or disk reference in the field, resolve the URL at render time via Storage::disk(...)->url(...). Baking absolute URLs into the column breaks the moment you change domains or move to a CDN.

Locale-aware pattern. If the site is multilingual, key the payload by locale (['en' => [...], 'ar' => [...]]) from day one. Retrofitting this later means a data migration plus a Vue component change — and you may not be able to change the Vue component if you cannot build the package's assets.

Validation belongs to you. Nova field-level rules() is convenient but rarely enough. Add explicit rules on the resource or a FormRequest:

'seo.title' => ['nullable', 'string', 'max:60'],
'seo.description' => ['nullable', 'string', 'max:160'],
'seo.og.image' => ['nullable', 'string'],

Sitemap / structured data. Once the field is populated, seo.title and seo.description are the natural source for a sitemap generator and for JSON-LD Article/Product schemas. Drive those from the same resolver so the meta tag and the schema never disagree.

Gotchas and Tips

  • Abandonment is the headline risk. Zero stars and no release since mid-2023 means no security patches, no Nova 5 support, and no one to ask. Pin the exact version in composer.json, and seriously consider vendoring the source into app/Nova/Fields with a comment crediting the original. Check the license first — if there isn't one, you technically have no right to redistribute it, only to use it as installed.

  • The package name/repo mismatch will bite you. iki/seo-meta vs nova-seo-field suggests either a fork, a renamed repo, or a never-published package. Verify it actually resolves on Packagist; if not, you need a vcs repository entry in composer.json and a version constraint pointing at a branch (which is a maintenance liability, since branches move).

  • Nova asset compilation. Nova tool/field packages ship compiled JS. If the compiled bundle is missing or broken for your Nova version, you must npm install && npm run prod inside vendor/iki/seo-meta — and that edit vanishes on composer install. Either publish the assets, fork the package to a local path repository, or accept the field is read-only-ish until you rebuild it.

  • JSON column limits. MySQL json is fine, but text columns cap at 64KB and editors will paste long meta descriptions. Use json/longText. Also watch that the cast is array (not json/collection) so data_get() behaves.

  • Empty-string vs null. Nova forms often submit '' for untouched subfields. Normalize on save (array_filter with a callback that keeps 0/false but drops empty strings) so your fallback chains actually trigger instead of rendering empty <meta name="description" content="">.

  • Escaping and truncation. Never {!! !!} the stored description into a content="" attribute. Use {{ }} and a Str::limit() at the tag level, not in the field, so you can tune per-locale. Strip tags on input if the editor allows HTML.

  • Debugging checklist when the field "doesn't save": dump the request payload ($request->input('seo') in a temporary middleware) → check the model cast → check $fillable/$guarded includes the column → check the Nova resource isn't calling ->hideFromIndex() weirdly → check the DB column actually stores JSON (inspect with SELECT seo FROM pages LIMIT 1).

  • Extension points are thin. With a package this small, "extension" realistically means subclassing the field class and overriding meta()/component(), plus overriding the Vue component path. Confirm the class uses $this->component = 'seo-meta-field' style assignment so you can swap it; if the component name is hardcoded in a closure, your only option is a fork.

  • Have an exit plan. Because the data model is just a JSON column, migration off the package is cheap: keep the column, drop the field class, and replace it with a small custom Nova field or a KeyValue/Textarea composition. Write the resolver trait so it doesn't reference the package namespace — that way removing the dependency is a one-line composer remove plus a field swap.

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.
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json