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

Laravel Sitemap Laravel Package

ultrono/laravel-sitemap

A lightweight Laravel package for generating XML sitemaps. Easily define URLs and metadata such as priority and change frequency, then output sitemap files or responses for search engines with minimal configuration and a simple, fluent API.

Deep Wiki
Context7

Getting Started

  1. Install: composer require ultrono/laravel-sitemap
  2. Publish the config (namespace depends on the package's provider): php artisan vendor:publish --provider="Ultrono\Sitemap\SitemapServiceProvider" --tag="sitemap-config". Confirm the provider class name by checking the package's composer.json extra.laravel.providers entry or its README.
  3. Skim these files first, in order — they are the entire public API surface:
    • src/SitemapGenerator.php — crawl-based generation
    • src/Sitemap.php — building sitemaps by hand / from models
    • src/Tags/Url.php — the tag object (lastmod, changefreq, priority, alternates, images)
    • config/sitemap.php — crawl defaults, output path, throttling
  4. First use case — the one-liner most people ship:
    use Ultrono\Sitemap\SitemapGenerator;
    
    SitemapGenerator::create(config('app.url'))
        ->writeToFile(public_path('sitemap.xml'));
    
  5. Wrap that in an Artisan command (or a scheduled job) before you touch models. A scheduled full crawl is the lowest-effort, highest-value integration — no model changes required.

Note: this package is a fork/republish of the well-known spatie/laravel-sitemap, with the namespace swapped. The repository link isn't verifiable from the metadata, so treat the namespace (Ultrono\Sitemap) as authoritative and confirm the exact class names in src/ after composer require. Every pattern below maps 1:1 to the Spatie API it descends from.

Implementation Patterns

Crawl-based (fastest to ship). Point the generator at your production URL and let it discover pages. Tune the crawler with configureCrawler:

SitemapGenerator::create('https://example.com')
    ->configureCrawler(fn ($crawler) => $crawler
        ->setMaximumDepth(4)
        ->setConcurrency(2)
        ->setDelayBetweenRequests(200))
    ->writeToFile(public_path('sitemap.xml'));

This is fine for marketing sites. It is not fine for large apps — it re-crawls every run and is HTTP-bound.

Model-based (the pattern you'll actually keep). Make your Eloquent models describe their sitemap entry by implementing the package's Sitemapable contract:

use Ultrono\Sitemap\Contracts\Sitemapable;
use Ultrono\Sitemap\Tags\Url;

class Post extends Model implements Sitemapable
{
    public function toSitemapTag(): Url|string|array
    {
        return Url::create(route('posts.show', $this))
            ->setLastModificationDate($this->updated_at)
            ->setChangeFrequency(Url::CHANGE_FREQUENCY_WEEKLY)
            ->setPriority(0.8);
    }
}

Then the whole sitemap is a composition:

Sitemap::create()
    ->add(Url::create('/')->setPriority(1.0))
    ->add(Post::published()->get())
    ->add(Page::all())
    ->writeToFile(public_path('sitemap.xml'));

add() accepts a single tag, a string/relative path, an array, a Collection, or any iterable of Sitemapable — which is what makes Post::all() work.

Sitemap index for large sites. Split by content type and emit an index:

SitemapIndex::create()
    ->add(Sitemap::create()->add(Post::all())->writeToFile(public_path('sitemap-posts.xml')))
    ->add(Sitemap::create()->add(Page::all())->writeToFile(public_path('sitemap-pages.xml')))
    ->writeToFile(public_path('sitemap.xml'));

Rich tags. Url exposes addImage(), addVideo(), addNews(), and addAlternate() (hreflang). Locale-aware sites usually build one sitemap per locale and cross-link with alternates.

Workflow: command + schedule. Define sitemap:generate in routes/console.php (or an invokable command), then $schedule->command('sitemap:generate')->dailyAt('03:00')->withoutOverlapping();. For anything bigger than a small site, dispatch generation to a queued job and write to a temp path before atomically moving into public/.

Integration tip: link it from robots.txt. A sitemap nobody references is a sitemap nobody fetches:

Route::get('robots.txt', fn () => response(
    "User-agent: *\nAllow: /\nSitemap: ".url('sitemap.xml'),
)->header('Content-Type', 'text/plain'));

Gotchas and Tips

  • Crawling is slow and hits your own app. For non-trivial sites, use model-based generation. Crawling is only worth it when pages are dynamic in ways you can't enumerate.
  • Memory blows up on large datasets. add(Post::all()) loads everything. Swap to Post::query()->chunk(1000, fn ($posts) => $sitemap->add($posts)) and reuse the same Sitemap instance.
  • The 50,000-URL / 50MB limit is real. Search engines cap a single sitemap file. Past that, use SitemapIndex — the package won't warn you, the crawlers just silently truncate.
  • writeToFile() needs a writable absolute path. public_path() is right for production; use storage_path('app/sitemap.xml') and serve it via a route if your host mounts public/ read-only (common on containerised deploys).
  • Trailing slashes and duplicates. The package does not dedupe Url entries by default in every version. If your routes generate both /posts/1 and /posts/1/, you'll ship duplicate entries. Normalise in toSitemapTag() or filter before adding.
  • Relative URLs are accepted but ambiguous. Url::create('/about') is stored as-is; whether it's absolute depends on config. Prefer route() / url() for anything you're debugging at 2am.
  • lastmod needs a Carbon instance. Passing a string works in some versions and not others — pass $model->updated_at directly.
  • Crawler respects robots.txt by default. If pages are missing from a crawl, that is the first thing to check; also check for JS-rendered content, which the crawler will not execute.
  • Config is cached. After editing config/sitemap.php, run php artisan config:clear — otherwise your crawl settings silently do nothing on config:cache deployments.
  • Testing. Assert on the generated XML rather than on the file path: build the Sitemap in the test, call ->render(), and assert assertSee('https://...'). Filesystem assertions get flaky in parallel test runs.
  • Extension point to remember: SitemapGenerator::create(...)->getSitemap() returns the built Sitemap before writing — the hook for post-crawl manipulation (filtering, adding tags, splitting) without re-crawling.
  • Lock the package version in CI. It's a low-star fork; a namespace change or dropped class between point releases will break your build at the worst possible time.
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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib