Muhammad Yasir Amin

Loading websiteโ€ฆ

More from Muhammad Yasir Amin

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Subscribe on YouTube

How to Generate a sitemap.xml in Laravel 13: Route, Command and robots.txt

๐Ÿ› ๏ธ Laravel Tutorials4 min read

How to Generate a sitemap.xml in Laravel 13: Route, Command and robots.txt

Muhammad Yasir Amin

Muhammad Yasir Amin

Senior Full Stack Engineerโ€ขNov 12, 2026
How to Generate a sitemap.xml in Laravel 13: Route, Command and robots.txt

A sitemap.xml tells search engines which pages exist on your site. In Laravel 13 you can create one without a package: build the XML from your models in a Blade view, serve it from a route, or write it to public/sitemap.xml with a scheduled Artisan command. This guide shows both ways, the size limits to respect and how to tell search engines where the file is.

Targets: Laravel 13 (PHP 8.3 to 8.5). The example lists static pages and published posts.

What does a sitemap contain?

An XML file with one <url> entry per page. Each entry has the full address in <loc> and, optionally, the last change date in <lastmod>. One sitemap file can hold up to 50,000 URLs and 50 MB uncompressed. Larger sites use a sitemap index file that points to several sitemaps.

Option 1: Serve the sitemap from a route

Create the controller:

Code
php artisan make:controller SitemapController
JavaScript
<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Response;

class SitemapController extends Controller
{
    public function __invoke(): Response
    {
        $pages = [
            ['loc' => url('/'), 'lastmod' => null],
            ['loc' => url('/about'), 'lastmod' => null],
            ['loc' => url('/blog'), 'lastmod' => null],
        ];

        $posts = Post::query()
            ->where('published_at', '<=', now())
            ->orderByDesc('updated_at')
            ->get(['slug', 'updated_at']);

        $xml = view('sitemap', ['pages' => $pages, 'posts' => $posts])->render();

        return response($xml, 200, ['Content-Type' => 'application/xml']);
    }
}

Create resources/views/sitemap.blade.php. The XML declaration must be printed from PHP, because Blade would otherwise treat the first characters as PHP short tags in some setups:

HTML / XML
{!! '<?xml version="1.0" encoding="UTF-8"?>' !!}
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
@foreach ($pages as $page)
    <url>
        <loc>{{ $page['loc'] }}</loc>
    </url>
@endforeach
@foreach ($posts as $post)
    <url>
        <loc>{{ url('/blog/'.$post->slug) }}</loc>
        <lastmod>{{ $post->updated_at->toAtomString() }}</lastmod>
    </url>
@endforeach
</urlset>

Using {{ }} escapes characters such as the ampersand, which must be escaped in XML. Add the route:

CSS
use App\Http\Controllers\SitemapController;
use Illuminate\Support\Facades\Route;

Route::get('/sitemap.xml', SitemapController::class);

Make sure there is no real file named public/sitemap.xml, because the web server would serve the file instead of Laravel.

Option 2: Write a file with a scheduled command

A file is faster to serve and puts no database load on each crawl. Create a command:

Code
php artisan make:command GenerateSitemap
JavaScript
<?php

namespace App\Console\Commands;

use App\Models\Post;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;

class GenerateSitemap extends Command
{
    protected $signature = 'sitemap:generate';

    protected $description = 'Write public/sitemap.xml';

    public function handle(): int
    {
        $pages = [
            ['loc' => url('/'), 'lastmod' => null],
            ['loc' => url('/about'), 'lastmod' => null],
        ];

        $posts = Post::query()
            ->where('published_at', '<=', now())
            ->get(['slug', 'updated_at']);

        File::put(
            public_path('sitemap.xml'),
            view('sitemap', ['pages' => $pages, 'posts' => $posts])->render()
        );

        $this->info('Sitemap written with '.($posts->count() + count($pages)).' URLs.');

        return self::SUCCESS;
    }
}

Schedule it in routes/console.php:

CSS
use Illuminate\Support\Facades\Schedule;

Schedule::command('sitemap:generate')->dailyAt('02:00');

Use one option only. If you use the file approach, remove the route, or the file will win anyway.

Step 3: Tell search engines where the sitemap is

Add this line to public/robots.txt:

YAML
User-agent: *
Disallow:

Sitemap: https://example.com/sitemap.xml

Also submit the address in Google Search Console and Bing Webmaster Tools. Use your real production domain, and keep APP_URL set correctly so url() generates the right links in console commands.

Should I use a package?

Packages such as spatie/laravel-sitemap can crawl your site or build the file from models and handle sitemap indexes. If you need that, check the package's Packagist page for the versions that support Laravel 13. For a small site, the plain approach above has no dependency to maintain.

How do I check that it works?

  1. Open /sitemap.xml in the browser. You should see XML, not HTML.
  2. Check that only public, published pages are listed and that every URL starts with your real domain.
  3. Paste the address in Google Search Console's sitemap tool and confirm that it is read without errors.

Common mistakes

  • Wrong Content-Type: return application/xml, not HTML.
  • Listing private or noindex pages: only list pages you want in search results.
  • Relative URLs: every <loc> must be a full absolute URL.
  • Wrong APP_URL in commands: the file then contains http://localhost addresses.
  • Forgetting limits: split into several files after 50,000 URLs.

FAQ

Do I need lastmod?

It is optional, but accurate dates help search engines decide what to crawl again. Do not set it to the current time for every page, because search engines learn to ignore inaccurate values.

What about priority and changefreq?

Google has said it ignores these fields, so you can leave them out.

How do I add images or videos?

Google supports image and video sitemap extensions with extra XML namespaces. See Google's sitemap documentation for the format.

References

Article Index & Topic Tags
LaravelSitemapSeoXml

YouTube Channel

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Muhammad Yasir Amin
Available for Contracts
Author & Solutions Architect

Senior Full Stack Engineer

Independent Senior Full Stack Engineer working on Laravel, React and Next.js applications, APIs and delivery workflows.

Laravel & PHP ApplicationsReact & Next.js InterfacesREST APIs & IntegrationsDatabase Queries & DebuggingDocker Development WorkflowsClear Scope & Handover
Discuss Your Next Step

What Does Your Application Need Next?

Share your current system, the problem you want to solve and what a useful result looks like. We can identify the right starting point and agree on a practical scope.

Technical Audit

Find the Next Useful Fix

Application Development

Scoped, Reviewable Milestones

Ongoing Support

Agreed Capacity and Priorities

Share your goals, current stack and requirements before the call.