laravel-sitemap-generator maintained by ibraheem-ghazi
Laravel Sitemap Generator
A flexible Laravel package for generating XML sitemaps from Eloquent queries and custom URLs.
It provides:
- Separate sitemap classes for posts, products, pages, and other resources.
- Registration through configuration or PHP code.
- A
make:sitemapArtisan command for generating class stubs. - A sitemap index at
public/sitemap.xml. - Individual sitemap files under
public/sitemaps. - Optional monthly splitting based on
created_at. - Optional XSL styling for browser-friendly XML output.
- A locale-aware helper trait for alternate URLs.
Why This Package?
Applications often need multiple sitemap files for different resources. This package keeps each sitemap's query, URL mapping, output name, and chunking rules in a dedicated class. The package handles the Laravel integration, registration, file generation, and sitemap index.
The package is an orchestration layer around spatie/laravel-sitemap. Spatie provides the sitemap objects and XML renderer; this package adds Laravel-specific sitemap classes, Eloquent support, date splitting, registration, and optional XSL publishing.
Requirements and Dependencies
- PHP 8.0 or newer.
- Laravel 8, till 13.
spatie/laravel-sitemap7.3 or newer.- Query-backed models must have a
created_atattribute unless the age-based methods are overridden.
The package uses PHP 8 features such as typed properties, union types, and the nullsafe operator.
Installation
composer require ibraheem-ghazi/laravel-sitemap-generator
Laravel discovers SitemapGeneratorProvider automatically through Composer package discovery.
Publishing
Publish the configuration:
php artisan vendor:publish --tag=sitemap-config
Publish the optional XSL stylesheet:
php artisan vendor:publish --tag=sitemap-assets
Publish both files:
php artisan vendor:publish --provider="IbraheemGhazi\SitemapGenerator\SitemapGeneratorProvider"
The configuration is published to config/sitemap.php. The stylesheet is published to public/sitemap-styles.xsl. When the stylesheet exists, generated XML includes a reference to /sitemap-styles.xsl. It changes browser presentation only and does not change the sitemap data.
Configuration
The published config/sitemap.php contains two settings:
<?php
return [
'files' => [
// App\Sitemaps\PostSitemap::class,
],
'supported_locales' => [
app()->getLocale(),
],
];
files
Add sitemap classes here to include them in every call to generate():
'files' => [
App\Sitemaps\PostSitemap::class,
App\Sitemaps\ProductSitemap::class,
],
Configured classes are combined with runtime registrations, and duplicates are removed.
supported_locales
This setting is used by the HasAlternateUrls trait. By default, it contains the current application locale:
'supported_locales' => [
app()->getLocale(),
],
For a localized application, configure the supported locales explicitly:
'supported_locales' => [
'en',
'fr',
'de',
],
Creating a Sitemap Class
Each sitemap class extends AbstractSitemapFile and defines a filename, query, and model-to-URL conversion:
<?php
namespace App\Sitemaps;
use App\Models\Post;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use IbraheemGhazi\SitemapGenerator\AbstractSitemapFile;
use Spatie\Sitemap\Tags\Url;
class PostSitemap extends AbstractSitemapFile
{
protected string $filename = 'posts';
public function getQuery(): ?Builder
{
return Post::query()->where('published', true);
}
public function modelToSitemapUrl_Impl(Model $model): ?Url
{
/** @var Post $model */
return Url::create(route('posts.show', $model))
->setLastModificationDate($model->updated_at);
}
}
modelToSitemapUrl_Impl() must return a Spatie Url object for every model. The generated stub returns null intentionally and must be completed.
The base class automatically assigns priority and change frequency using the model's age. It calculates age from created_at and the current time.
Default priority rules:
| Age | Priority |
|---|---|
| Less than 7 days | 0.9 |
| Less than 30 days | 0.8 |
| Less than 180 days | 0.7 |
| 180 days or older | 0.5 |
Default change-frequency rules:
| Age | Frequency |
|---|---|
| Less than 30 days | daily |
| 30 days or older | weekly |
Override getPriorityByAge() or getFrequencyByAge() when your application needs different rules.
Custom URLs
Use getItems() for URLs that do not come from a model query:
use IbraheemGhazi\SitemapGenerator\AbstractSitemapFile;
use Spatie\Sitemap\Tags\Url;
class StaticPagesSitemap extends AbstractSitemapFile
{
protected string $filename = 'static-pages';
public function getItems(): array
{
return [
Url::create(route('home')),
Url::create(route('contact')),
Url::create(route('about')),
];
}
}
A sitemap class can provide only custom items, only a query, or both. You can use Spatie metadata methods such as setLastModificationDate(), setPriority(), and setChangeFrequency() when creating a Url.
For model URLs, the base class assigns priority and frequency after your implementation method returns. Those automatic values take precedence unless you override the relevant base behavior.
Generating Sitemaps
Generate every configured and runtime-registered sitemap:
use IbraheemGhazi\SitemapGenerator\SitemapGenerator;
SitemapGenerator::make()->generate();
generate() deletes and recreates public/sitemaps, generates each non-empty child sitemap, and writes a new sitemap index. Treat that directory as generated build output.
Runtime registration
use App\Sitemaps\PostSitemap;
use IbraheemGhazi\SitemapGenerator\SitemapGenerator;
SitemapGenerator::register(PostSitemap::class);
SitemapGenerator::make()->generate();
Remove a runtime registration with:
SitemapGenerator::unregister(PostSitemap::class);
Runtime registrations are held statically for the current PHP process. Use config/sitemap.php for persistent registration.
Refresh one sitemap
use App\Sitemaps\PostSitemap;
PostSitemap::refresh();
Or:
SitemapGenerator::make()->refreshBySitemapFile(
new PostSitemap()
);
This regenerates the selected child file and rebuilds public/sitemap.xml without first deleting every child file.
Console Generator
Create a sitemap class stub under app/Sitemaps:
php artisan make:sitemap Post
This creates app/Sitemaps/PostSitemap.php with a filename, a disabled chunking flag, and stubs for getQuery() and modelToSitemapUrl_Impl().
Generate a date-chunked class:
php artisan make:sitemap Post --chunked
# or
php artisan make:sitemap Post -c
Nested names are supported:
php artisan make:sitemap Admin/Post --chunked
This creates app/Sitemaps/Admin/PostSitemap.php in the App\Sitemaps\Admin namespace. The command refuses to overwrite an existing class. Complete the generated methods, then add the class to config/sitemap.php or register it at runtime.
Date Chunking
Set $chunkedByDate to true to split query results into monthly files:
class PostSitemap extends AbstractSitemapFile
{
protected string $filename = 'posts';
protected bool $chunkedByDate = true;
public function getQuery(): ?Builder
{
return Post::query()->where('published', true);
}
public function modelToSitemapUrl_Impl(Model $model): ?Url
{
return Url::create(route('posts.show', $model));
}
}
Example output:
public/sitemaps/posts-2026-09.xml
public/sitemaps/posts-2026-08.xml
Date chunking uses the query's created_at year and month. URLs returned by getItems() are written to a separate file:
public/sitemaps/posts-others.xml
Date chunking requires a non-null Eloquent query. Empty months and empty sitemap files are not written.
Generated Sitemap Files
Sitemap index
The generator writes:
public/sitemap.xml
This is a <sitemapindex> XML document. It contains one <sitemap> entry for each generated child file under public/sitemaps, using URLs based on the application's configured URL.
Regular sitemap
For $filename = 'posts' without chunking:
public/sitemaps/posts.xml
This is a <urlset> XML document. Each <url> contains the URL and any metadata provided by the Spatie Url object, such as last modification date, change frequency, and priority.
Browser display
If the XSL asset is published, opening the XML in a browser displays the styled sitemap index or URL table. Search engines still consume the underlying XML. The generated stylesheet reference is /sitemap-styles.xsl, so the application must serve that path publicly.
Search engine submission
Submit the index URL rather than every child file:
https://example.com/sitemap.xml
You can also reference it from robots.txt:
Sitemap: https://example.com/sitemap.xml
Ensure APP_URL, route URL generation, and web-server public file serving use the correct production domain.
Alternate URL Trait
HasAlternateUrls helps a class produce URLs for multiple locales. The consuming class must implement:
public function url(?string $locale = null): string
Example:
use IbraheemGhazi\SitemapGenerator\Traits\HasAlternateUrls;
class LocalizedPage
{
use HasAlternateUrls;
public function url(?string $locale = null): string
{
$locale = $locale ?: app()->getLocale();
return url("/{$locale}/about");
}
}
With this configuration:
'supported_locales' => ['en', 'fr', 'de'],
getAlternateUrls() returns a map like:
[
'x-default' => 'https://example.com/en/about',
'en' => 'https://example.com/en/about',
'fr' => 'https://example.com/fr/about',
'de' => 'https://example.com/de/about',
]
The trait only builds this locale-to-URL map. It does not automatically attach alternate links to a Spatie Url object. Use the map with the alternate-link support available in your sitemap workflow.
The x-default URL is based on the current application locale. The locale list is read from config('sitemap.supported_locales').
API Summary
SitemapGenerator
make()returns the application singleton.register(string $class)registers a sitemap class for the current process.unregister(string $class)removes a runtime registration.getRegisteredSitemaps()returns configured and runtime classes.generate()rebuilds all child files and the index.refreshBySitemapFile(AbstractSitemapFile $file)refreshes one file and the index.homePageUrl()returns a daily, priority-1Spatie home-page URL.
AbstractSitemapFile
getFileName()returns the output filename.getQuery()returns an optional Eloquent builder.getItems()returns custom SpatieUrlobjects.modelToSitemapUrl_Impl()converts a model into a SpatieUrl.isChunkedByDate()reports whether monthly splitting is enabled.refresh()refreshes the current sitemap class.
Operational Notes
- Run generation from a command, scheduler, deployment step, or queue appropriate for the data size.
- Query URL generation uses Eloquent cursors, but date chunking first queries distinct year/month combinations.
- Do not store unrelated public files inside
public/sitemaps;generate()deletes that directory. - A class with no query results and no custom items does not create an empty child file, but the index is still regenerated.
- Generated sitemap URLs must be publicly reachable.