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:
php artisan make:controller SitemapController<?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:
{!! '<?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:
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:
php artisan make:command GenerateSitemap<?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:
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:
User-agent: *
Disallow:
Sitemap: https://example.com/sitemap.xmlAlso 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?
- Open
/sitemap.xmlin the browser. You should see XML, not HTML. - Check that only public, published pages are listed and that every URL starts with your real domain.
- 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://localhostaddresses. - 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.



