Sitemap and JSON-LD

The SEO plugin ships two indexability primitives: a sitemap helper that builds /sitemap.xml from published content, and a JSON-LD block emitted per page by SeoTags. Together they let search engines discover and richly render every public page.

Sitemap

Mamenein\FilamentSeo\Sitemap::xml(array $urls): string is a static helper that takes a list of ['loc' => string, 'lastmod' => string|null] rows and returns a valid <urlset> document. The plugin does not register the route itself — the clone wires it on its public controller:

// routes/web.php
Route::get('/sitemap.xml', [PublicPageController::class, 'sitemap']);

The controller enumerates published Posts and Docs, mapping each to its public URL and updated_at date:

$urls = collect([['loc' => url('/'), 'lastmod' => null]])
    ->merge(Post::query()->published()->get()->map(fn (Post $post) => [
        'loc' => url($post->publicPath()),
        'lastmod' => $post->updated_at?->toDateString(),
    ]))
    ->merge(Doc::query()->published()->get()->map(fn (Doc $doc) => [
        'loc' => url($doc->publicPath()),
        'lastmod' => $doc->updated_at?->toDateString(),
    ]))
    ->values()->all();

return response(Sitemap::xml($urls), 200, [
    'Content-Type' => 'application/xml; charset=UTF-8',
]);

lastmod is optional; the helper omits the element when null. loc is escaped with e().

JSON-LD

SeoTags::toHtml() renders the meta tags view and embeds a <script type="application/ld+json"> block. The schema type emitted is WebPage — verified in SeoTags.php:

$jsonLd = json_encode([
    '@context' => 'https://schema.org',
    '@type' => 'WebPage',
    'name' => $this->title,
    'description' => $this->description,
    'url' => $this->canonical,
    'image' => $this->ogImage,
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

SeoTags::for($model) builds the tags from the model's morph SEO row ($model->seo), falling back to the model's title attribute and url()->current() for the canonical. Example output for a published post:

{
  "@context": "https://schema.org",
  "@type": "WebPage",
  "name": "Shipping to Magic Containers",
  "description": "Stateless deploy notes for FrankenPHP on bunny.net.",
  "url": "https://edge.qcentic.com/blog/shipping-to-magic-containers",
  "image": "https://cdn.qcentic.com/media/42/og.png"
}

Nested SEO on the Content API

When posts or docs are created through /api/v1, the Content API accepts a nested seo payload and stores it on the morph via $model->seo()->updateOrCreate([], $values). Accepted keys: title, description, canonical_url, og_image_url. API-published pages are therefore fully indexed by both the sitemap and JSON-LD — no panel visit required. See the Content API page.

flowchart LR
    A[Published Post/Doc] --> B[Sitemap helper]
    B --> C[/sitemap.xml/]
    C --> D[Search engine]
    E[SeoTags::for model] --> F[JSON-LD block]
    F --> D

See also:

Built by Qcentic