Santaji GadeSEO, Technical SEO2 weeks ago39 Views

Most sitemap plugins are black boxes. Here's real, complete PHP code for four working sitemap generators from a basic script to a database-driven version that handles the 50,000 URL limit automatically.
Table of Contents
ToggleMost sitemap plugins are black boxes. You install one, hope it catches every URL correctly, and rarely look at what it actually outputs. Building your own PHP sitemap generator takes about thirty minutes and gives you full control over exactly which pages get included, how often they update, and what happens once you cross 50,000 URLs.
A sitemap generator built in PHP reads a list of URLs, either from an array, a directory scan, or a database query, then outputs valid XML following the official sitemaps.org protocol that search engines expect.
This guide walks through four working sitemap generator versions, from a basic static script to a database-driven generator that handles sitemap indexes automatically.
We covered why this file matters in our XML sitemap guide. This article is the hands-on build: real, complete PHP code you can run today.
maximum URLs allowed in a single sitemap file per the sitemaps.org protocol
maximum uncompressed file size per sitemap file before splitting is required
working sitemap generator versions covered, from basic to fully automated
This is the simplest working sitemap generator version, using PHP's built-in DOMDocument class rather than string concatenation, since DOMDocument automatically escapes special characters correctly.
<?php header('Content-Type: application/xml; charset=utf-8'); $baseUrl = 'https://www.brandella.in'; $urls = [ ['loc' => '/', 'lastmod' => '2026-07-30', 'changefreq' => 'daily', 'priority' => '1.0'], ['loc' => '/blog/', 'lastmod' => '2026-07-29', 'changefreq' => 'daily', 'priority' => '0.9'], ['loc' => '/about/', 'lastmod' => '2026-06-15', 'changefreq' => 'monthly', 'priority' => '0.6'], ]; $xml = new DOMDocument('1.0', 'UTF-8'); $xml->formatOutput = true; $urlset = $xml->createElement('urlset'); $urlset->setAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9'); $xml->appendChild($urlset); foreach ($urls as $entry) { $urlNode = $xml->createElement('url'); $urlNode->appendChild($xml->createElement('loc', $baseUrl . $entry['loc'])); $urlNode->appendChild($xml->createElement('lastmod', $entry['lastmod'])); $urlNode->appendChild($xml->createElement('changefreq', $entry['changefreq'])); $urlNode->appendChild($xml->createElement('priority', $entry['priority'])); $urlset->appendChild($urlNode); } echo $xml->saveXML();
Save as sitemap.php and visit it directly in a browser to see valid XML output
For static sites, scanning a folder of HTML files with PHP's RecursiveDirectoryIterator removes the need to maintain a URL list by hand entirely.
<?php function scanDirectory($dir, $extension = 'html') { $urls = []; $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS) ); foreach ($files as $file) { if ($file->getExtension() === $extension) { $relativePath = str_replace($dir, '', $file->getPathname()); $urls[] = [ 'loc' => str_replace('\\', '/', $relativePath), 'lastmod' => date('Y-m-d', $file->getMTime()), 'changefreq' => 'weekly', 'priority' => '0.5' ]; } } return $urls; } $urls = scanDirectory(__DIR__ . '/public');
Automatically finds every HTML file, using the file's own modified date for lastmod
For dynamic sites built on a CMS or custom database, querying published content directly through PHP's PDO extension keeps the sitemap perfectly in sync with what actually exists.
<?php $pdo = new PDO('mysql:host=localhost;dbname=brandella;charset=utf8mb4', 'db_user', 'db_password'); $stmt = $pdo->query("SELECT slug, updated_at FROM posts WHERE status = 'published'"); $urls = []; while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { $urls[] = [ 'loc' => '/' . $row['slug'] . '/', 'lastmod' => date('Y-m-d', strtotime($row['updated_at'])), 'changefreq' => 'weekly', 'priority' => '0.7' ]; }
Replace the query with whatever table structure your own CMS or app actually uses
The sitemaps.org protocol caps a single file at 50,000 URLs. Larger sites need a sitemap index file pointing to multiple chunked sitemaps.
A single sitemap.xml file, exactly like Version 1 above, is all that's needed. No index file is required.
Split the URL array into chunks of 50,000 using PHP's array_chunk function, generate one sitemap file per chunk, then build a sitemap index file listing each chunked file's location.
<?php $chunks = array_chunk($allUrls, 50000); $indexXml = new DOMDocument('1.0', 'UTF-8'); $indexXml->formatOutput = true; $sitemapindex = $indexXml->createElement('sitemapindex'); $sitemapindex->setAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9'); $indexXml->appendChild($sitemapindex); foreach ($chunks as $i => $chunk) { file_put_contents("sitemap-" . ($i + 1) . ".xml", buildSitemapXml($chunk)); $sitemap = $indexXml->createElement('sitemap'); $sitemap->appendChild($indexXml->createElement('loc', $baseUrl . "/sitemap-" . ($i + 1) . ".xml")); $sitemap->appendChild($indexXml->createElement('lastmod', date('Y-m-d'))); $sitemapindex->appendChild($sitemap); } file_put_contents('sitemap-index.xml', $indexXml->saveXML());
Wrap the Version 1 logic in a buildSitemapXml() function, then call it once per chunk here
Change the final line from echo $xml->saveXML(); to file_put_contents('sitemap.xml', $xml->saveXML()); so running the script regenerates the actual file on disk.
A daily cron entry like 0 2 * * * php /path/to/sitemap-generator.php regenerates the sitemap automatically every night at 2am.
Add Sitemap: https://www.brandella.in/sitemap.xml to your robots.txt file so crawlers can discover the generated file automatically.
Paste URLs, one per line, to see the exact XML this script would generate.
Mirrors the output of the PHP script above, without needing to run PHP
Before relying on any sitemap generator in production, confirm the output actually validates.
According to Google Search Central's guide to building a sitemap, submitting the file through Search Console's Sitemaps report is the most reliable way to confirm Google can parse it without errors.
A quick manual check works too: open the generated file directly in a browser.
A well-formed sitemap generator produces XML the browser renders as a structured tree, while a broken one shows a parsing error at the exact line where the invalid character or malformed tag sits.
According to Yoast's developer blog on building a custom sitemap script, a hand-built sitemap generator makes the most sense when a site has an unusual content structure, like large volumes of downloadable files, that generic plugins do not handle well by default.
For a standard WordPress blog with no unusual content types, an established plugin usually remains the more practical choice, since it also handles edge cases like taxonomy pages and media attachments without any custom code required.
A sitemap generator scanning tens of thousands of database rows or files can consume significant memory if not handled carefully.
According to an open-source PHP sitemap generator reference implementation, generation time scales directly with site size, so very large catalogs benefit from batching database queries rather than loading every URL into memory at once.
According to PHP's own documentation for array_chunk, this same batching function is what splits URLs into manageable groups for both memory efficiency and the 50,000 URL sitemap limit.
Running the sitemap generator as a scheduled background job, rather than triggering it from a live page request, keeps this resource usage isolated from actual visitor traffic.
| Mistake | Why It Matters |
|---|---|
| Using string concatenation instead of DOMDocument | Special characters in URLs can break XML validity |
| Including noindexed or redirected URLs | Sends contradictory signals about which pages matter |
| Forgetting the 50,000 URL limit | Large sites need a sitemap index, not one giant file |
| Never automating regeneration | A stale sitemap missing new content defeats its purpose |
| Not referencing the file in robots.txt | Makes discovery slower for crawlers checking that file first |
DOMDocument builds valid XML more safely than manual string concatenation
A directory scanner removes the need to maintain a URL list by hand
Database-driven generation keeps dynamic sites perfectly in sync
50,000 URLs per file is the hard limit; use a sitemap index beyond that
CRON automation keeps the file fresh without manual regeneration
Always validate output before relying on it for production traffic










