laravel-sitemap-generator maintained by yvkibira
Laravel Sitemap Generator
Generate SEO-optimized XML sitemaps automatically from your Laravel application's routes. Zero assumptions about your app's structure — just install, configure exclusions, and go.
Non-Technical Overview
What does it do?
It creates a sitemap.xml file that tells search engines (Google, Bing, etc.) which pages exist on your website. This helps your pages get indexed faster and appear in search results.
How does it work?
- The package looks at all the pages (routes) your Laravel app has registered
- It filters out pages you don't want in search results (like admin panels)
- It generates a clean XML file with all your public page URLs
- Search engines find this file via your
robots.txtor when you submit it
What you need to do
- Install the package
- Publish the config file
- Tell it which pages to exclude (e.g.,
/admin/*) - If you have dynamic pages (like blog posts with
/blog/my-post), write a short class that tells the package how to find them - Set up a cron job to regenerate the sitemap daily (or enable the built-in scheduler)
For Developers
Installation
composer require yvkibira/laravel-sitemap-generator
php artisan vendor:publish --tag=sitemap-config
The package auto-registers via Laravel's package discovery. It:
- Registers
GET /sitemap.xmlroute - Registers
php artisan sitemap:generatecommand - Publishes
config/sitemap.phpwhen you run the vendor:publish command
Quick Start
# Generate sitemap immediately
php artisan sitemap:generate
# Output: Sitemap generated at storage/app/public/sitemap.xml
# Also written to: public/sitemap.xml
Visit /sitemap.xml in your browser to see the generated sitemap.
Configuration
Publish the config, then edit config/sitemap.php:
return [
'exclude' => [
'patterns' => [
'admin/*', // Exclude all admin routes
],
'middleware' => [
// 'auth', // Exclude routes with auth middleware
],
'route_names' => [
// 'admin.*', // Exclude routes named admin.*
],
],
'dynamic_routes' => [
// ⚠️ Parameterized routes (blog/{post}) do NOT appear
// unless you add them here. Nothing is auto-guessed.
// 'blog/{post}' => true,
],
'robots' => [
'enabled' => true,
'disallow' => [
'/admin',
],
],
'schedule' => [
'enabled' => false,
'frequency' => 'daily', // hourly, twice-daily, daily, or Laravel frequency methods
'time' => '03:00', // Only used when frequency is 'daily'
],
];
Route Exclusion
Three ways to exclude routes from the sitemap:
By URI pattern — Matches the URL path:
'exclude' => [
'patterns' => [
'admin/*',
'api/*',
'auth/*',
'_debugbar/*',
],
],
The * is a wildcard that matches any segment(s). admin/* matches /admin, /admin/users, /admin/posts/create, etc.
By middleware — Excludes routes that use specific middleware:
'exclude' => [
'middleware' => [
'auth',
'filament.auth',
],
],
By route name — Matches named routes:
'exclude' => [
'route_names' => [
'filament.admin.*',
'debugbar.*',
],
],
You can combine all three — a route is excluded if it matches any rule.
Dynamic Routes (Parameterized URLs)
⚠️ These do NOT appear in your sitemap unless you explicitly opt in below.
Routes with parameters like
/blog/{post}cannot be auto-discovered because the package doesn't know what values to substitute. You must configure each one before it appears in the sitemap. If a route is missing from your sitemap, check yourdynamic_routesconfig.
Opt-in Convention (true)
'dynamic_routes' => [
'blog/{post}' => true,
]
true tells the package to use convention:
- Extracts parameter name
post→App\Models\Post - Uses
slugcolumn if it exists, elsegetRouteKeyName() - Auto-filters by
is_publishedif that column exists - Sets
lastmodfromupdated_at
Config Override (Array)
For routes that need custom filtering:
'dynamic_routes' => [
'cloud-servers/{package}' => [
'where' => [
'type' => 'virtual_machine',
'is_product_page_published' => true,
],
'priority' => '0.9',
],
]
Available options: model, column, where (key-value array), priority, changefreq, scope.
If model or column are omitted, they're inferred from convention.
Simple Model Class String
'dynamic_routes' => [
'blog/{post}' => \App\Models\Post::class,
]
Equivalent to a config array with just model. Convention handles the rest (column, published filter).
Custom DynamicRouteResolver
For full control, implement the interface and tag it in a service provider:
use YvKibira\LaravelSitemapGenerator\Contracts\DynamicRouteResolver;
use YvKibira\LaravelSitemapGenerator\SitemapUrl;
class BlogPostResolver implements DynamicRouteResolver
{
public function resolve(string $uri, array $parameterNames): array
{
if ($uri !== 'blog/{post}') {
return [];
}
return Post::where('is_published', true)
->get()
->map(fn (Post $post) => new SitemapUrl(
url: url("blog/{$post->slug}"),
priority: '0.7',
changefreq: 'monthly',
lastmod: $post->updated_at,
))
->all();
}
}
// In a service provider
$this->app->tag(BlogPostResolver::class, 'sitemap.resolvers');
Custom resolvers take priority — they run before the convention/config resolver.
Scheduled Generation
Enable automatic daily sitemap regeneration in your config:
'schedule' => [
'enabled' => true,
'frequency' => 'daily',
'time' => '03:00',
],
Supported frequencies:
hourlytwice-dailydaily(uses thetimesetting)everyMinute,everyTwoMinutes,everyFiveMinutes,everyTenMinutes,everyFifteenMinutes,everyThirtyMinutes
SitemapUrl Object
new SitemapUrl(
url: 'https://example.com/page', // Full absolute URL
priority: '0.8', // SEO priority (0.0 to 1.0)
changefreq: 'weekly', // How often content changes
lastmod: Carbon::now(), // Optional: last modified date
);
Default priority is 0.8, default changefreq is weekly.
Robots.txt
The command generates a robots.txt file alongside the sitemap with:
User-agent: *
Disallow: /admin
Allow: /
Sitemap: https://example.com/sitemap.xml
Configure the disallow paths in config/sitemap.php under robots.disallow, or disable robots.txt entirely with robots.enabled: false.
Architecture
Route::getRoutes() ──► RouteDiscovery ──► Sitemap ──► XmlRenderer ──► XML string
│ │
exclude rules DynamicRouteResolver[]
(config) (tagged services)
Classes
| Class | File | Purpose |
|---|---|---|
SitemapUrl |
src/SitemapUrl.php |
Value object with url, priority, changefreq, lastmod |
DynamicRouteResolver |
src/Contracts/DynamicRouteResolver.php |
Interface for expanding parameterized routes |
RouteDiscovery |
src/Support/RouteDiscovery.php |
Reads all routes, applies exclusion filters |
Sitemap |
src/Sitemap.php |
Orchestrator: discovers routes, resolves params, builds URLs |
XmlRenderer |
src/Renderers/XmlRenderer.php |
Builds XML string from SitemapUrl array |
GenerateSitemapCommand |
src/Commands/GenerateSitemapCommand.php |
Artisan command for file generation |
SitemapController |
src/Http/Controllers/SitemapController.php |
HTTP endpoint serving sitemap.xml |
SitemapServiceProvider |
src/SitemapServiceProvider.php |
Registers command, route, config, scheduler |
Dependencies
laravel/framework(no Filament, no specific models, no packages)
The package is a Laravel package. It uses only Illuminate\Routing\Router and Illuminate\Routing\Route from the framework — no Filament, no Livewire, no models.
Testing
php artisan test --filter=SitemapPackage
The test suite includes:
- Unit tests for route discovery, filtering, and exclusion
- Unit tests for XML rendering
- Mock-based tests for the controller and command
- Integration tests with the host application's routes