3x1io/filament-sitemap
Filament Sitemap by 3x1io generates and manages XML sitemaps directly from your Filament admin panel. Register models or custom URLs, configure priorities and update frequencies, and serve search-engine-ready sitemap files with ease. Built for Laravel and Filament SEO workflows.
filament-sitemap is a small Filament plugin that wires sitemap generation into a panel — you get a settings page for sitemap options and the package handles serving/regenerating the XML. It sits on top of spatie/laravel-sitemap for generation and spatie/laravel-settings for persisted configuration.
Minimal path to first output:
composer require 3x1io/filament-sitemapphp artisan vendor:publish --tag=filament-sitemap-configphp artisan vendor:publish --tag=filament-sitemap-migrations (or the --force/timestamp variant introduced in v1.0.1)php artisan migrate — the package stores its settings via spatie/laravel-settings.->plugin(FilamentSitemapPlugin::make())), then php artisan filament:optimize-clear if you were already cached./sitemap.xml and confirm it renders. If the settings page shows an onboarding hint, v1.0.1 adds a config toggle to suppress it.Where to look first, in order: the published config file (it controls route, model sources, crawler settings and the v1.0.1 hint toggle), then the settings page in the panel, then the generated sitemap.xml. The settings page is the intended surface for day-to-day work; the config file is where you set structural defaults that the settings page then overrides.
First realistic use case: a content site with Post and Page models. Publish config + migrations, add the two models to the sitemap source list, visit the settings page, hit regenerate, verify the XML. That loop is the whole plugin in miniature.
Config-first, settings-second. Treat the published config as your team's defaults and the Filament settings page as per-environment overrides. If you only ever touch the settings page, you lose the ability to review sitemap structure in code review; if you only ever touch config, editors can't adjust anything. Decide explicitly which fields are editor-owned and which are locked in config.
Registering across panels. The plugin is registered per panel provider, so a multi-panel app (admin + a public-facing panel) can expose sitemap settings in one and not the other. Keep registration in the panel that non-technical users actually log into.
Scheduling regeneration. The XML is a cache-like artifact — stale sitemaps are the most common complaint. Pair the settings page's manual regenerate with a scheduled command in routes/console.php or the scheduler (->daily()), and make sure the schedule runs after any content import jobs. If your models are large, regenerate nightly rather than on every save.
Model source lists. Keep the list of sitemap-able models in config rather than adding entries ad hoc through the UI, so new content types are a code change with a corresponding test. Each source typically needs a scope or query constraint (published only, not soft-deleted) — set that in config, not in a model global scope, so other parts of the app aren't affected.
Local vs production. Generation against a local URL produces absolute URLs pointing at localhost. Either generate only in production/staging, or set APP_URL correctly in every environment where you regenerate. If you commit a generated sitemap (some teams do), generate it in CI with a production APP_URL.
Integration with spatie/laravel-sitemap. If you already use that package directly, don't run two generators. Pick one: either let the plugin own the sitemap and drop your custom command, or disable the plugin's route and keep your own command pointed at the same output path. Running both will fight over the file/route.
Settings hint toggle (v1.0.1). The "hide settings hint" config is useful for client-facing panels where the onboarding notice looks unfinished. Set it in config once per panel; don't ask editors to dismiss it.
Migration collisions are the reason v1.0.1 exists. The package publishes migrations that create settings tables/rows for spatie/laravel-settings. If you (or the settings package) already published a migration with the same timestamp-prefixed filename, Laravel silently drops one, and you get "table already exists" or a missing settings row at runtime. v1.0.1 adds a publish option that appends a fresh timestamp, specifically to avoid this clash. Use that option rather than copying files manually. After publishing, always git diff the database/migrations directory and confirm exactly one new file, with a timestamp later than any existing spatie/laravel-settings migration.
Symptom → cause cheat sheet.
/sitemap.xml 404s → plugin not registered in the panel, or route cache stale (php artisan route:clear).spatie/laravel-settings not installed at a compatible version.Config caching. php artisan config:cache will freeze the hint toggle and any structural settings. Any deploy that changes the package config must re-run config:clear && config:cache — include it in your deploy script rather than remembering manually.
Permissions. The settings page is a Filament page and therefore gated by panel access. If editors shouldn't be able to change sitemap structure but should see it, you'll need a policy or a canView/canAccess override on the page rather than relying on panel-level access alone.
Extension points. Before forking, exhaust: the config file (source models, constraints, output path, route), the settings page (runtime values), and Filament's page/plugin hooks for layout changes. The package is thin — most "I need to customise X" cases end up as a SitemapSource-style config entry or a small custom command that calls the same generator, which is a cheaper long-term bet than a fork.
Version pinning. v1.0.1 is a patch release with two additive changes (hint toggle, safer migration publish). Nothing breaking, nothing deprecated. It's a safe drop-in, but pin ^1.0.1 explicitly so the migration-timestamp fix isn't lost to a stale lockfile in another environment.
Testing. Assert on the generated XML rather than the page HTML: fetch the sitemap route in a feature test, parse it, and assert that a known published record appears and a known draft does not. That single test catches the majority of real-world regressions for this plugin.
How can I help you explore Laravel packages today?