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

mulertech/seo-bundle

SEO toolkit for Laravel: manage meta tags, Open Graph, Twitter cards, canonical URLs, robots directives, JSON-LD structured data, and XML sitemaps from one bundle. Blade helpers keep head markup clean and consistent across pages.

View on GitHub
Deep Wiki
Context7

Getting Started

Add the package to a Laravel app and confirm the schema_org_json_ld() helper is autoloaded (check composer.json autoload files or the service provider if the helper is not immediately available after composer dump-autoload).

The fastest first win is an FAQ page:

@push('head')
    {!! schema_org_json_ld('faqPage', $faqs->map(fn ($faq) => [
        'question' => $faq->question,
        'answer'   => Str::markdown($faq->answer), // rendered + sanitised in the app
    ])->all()) !!}
@endpush

Put the @stack('head') in your layout so every type renders in <head>.

Where to look first:

  • The helper itself, to see how it escapes and where it injects the <script type="application/ld+json"> tag.
  • The valid-type list — the quickest way to see it is to pass a nonsense type once and read the InvalidArgumentException message, which enumerates every supported type.
  • The BlogPostingSeoInterface definition, since blogPosting is the only type that takes an object rather than an array.

Start with faqPage or breadcrumbList: they are the least ceremony and give an immediate, verifiable result in the Rich Results Test.

Implementation Patterns

Keep mapping in one place. Rather than building arrays in Blade, add a small presenter or toSchemaOrg() method on each model and call it from the view. This keeps the shape (which keys exist, which are strings) next to the data and makes the new validation failures easy to fix.

FAQ pages. faqPage takes a list of {question, answer}. Render markdown to HTML in the application before passing it in — the package does not convert markdown. A single accessor or presenter method ($faq->schemaAnswer()) avoids repeating Str::markdown() in every view.

Blog posts. blogPosting requires an object implementing BlogPostingSeoInterface, not an array. Implement the interface on the Post/Article model (or a dedicated DTO) and pass the model straight through. This is the natural place for title, description, dates and author fields.

Breadcrumbs. Build the list from a shared helper rather than per-page: a BreadcrumbService that appends entries as the request resolves, or a view composer. The package deliberately renders nothing for an empty list, so returning [] on the homepage is a safe no-op.

Service pages. service needs at minimum a string title. A Service model with a real description is preferable to relying on the field being omitted.

Layout integration. Use @push('head') per view so blocks compose naturally on pages that need several (e.g. breadcrumbList + faqPage + service). Alternatively wrap the helper in your own <x-schema-org type="..." :data="..." /> Blade component to centralise the {!! !!} unescaped echo and keep the raw echo in exactly one file.

Testing. Assert on the decoded JSON rather than the raw string:

$data = json_decode(/* extract the script body */, true);
$this->assertSame('FAQPage', $data['@type']);

In feature tests, an assertSee('FAQPage', false) catches the common regression of a block silently missing.

Conditional emission. Because empty breadcrumbList/faqPage lists render nothing, you can call the helper unconditionally in layouts and let it decide — no need for @if ($items) guards around the call.

Gotchas and Tips

v1.6.0 is a behavioural change, not just a feature. Calls that used to render nothing now throw InvalidArgumentException. Any code passing partial arrays, models that don't implement BlogPostingSeoInterface, or non-string title values will start returning 500s after upgrading. Grep for every schema_org_json_ld( call and run a smoke test on each page type before deploying.

Read the exception message. It names the expected shape and, for unknown types, lists the valid ones. This is intentionally the primary debugging surface — don't catch and swallow it, because a swallowed exception recreates the old silent-failure problem.

answer is trusted HTML. The value is emitted as-is into the JSON-LD. Anything user-submitted must be markdown-rendered and sanitised (HTML Purifier, strip_tags with a narrow allowlist, or Str::markdown plus a sanitiser) before it reaches the helper. Watch out for closing </script> sequences and quotes — sanitising addresses both XSS and malformed JSON.

blogPosting is interface-gated. Passing an array, a stdClass, or a model without the interface throws. If you already have a DTO for SEO metadata, implement the interface on it rather than adding methods to an Eloquent model that is used everywhere else.

Empty lists are silent by design. [] for breadcrumbList or faqPage renders nothing because Google rejects empty entries. That is now the only silent path — a missing key or wrong type throws. So "no output" reliably means "empty input", and "error" means "bad data": a much better signal than before, but only if you don't wrap the helper in a try/catch.

Missing description on a Service is now omitted rather than emitted as "description": "". If you had a test or a snapshot asserting the empty-string form, update it. Also consider whether the page actually wants a description — Google generally expects one for Service.

Watch the raw echo. Always use {!! !!} for the helper output (it is a <script> tag), and keep that echo confined to a layout or component. Escaping it with {{ }} produces a visible script tag in the page body.

Debugging checklist for a missing block: (1) is the type spelled exactly as listed in the exception? (2) is the list non-empty? (3) is @stack('head') present in the layout? (4) does the rendered <script type="application/ld+json"> decode as valid JSON? (5) does the Rich Results Test accept it?

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource