The sitemap protocol is one of the smallest specifications you will ever have to learn. It has one wrapper element, one entry element, and three optional child tags. That is the entire thing. Most people never look at it because a plugin generates the file for them, which is usually the right call — but not always, and not knowing the format is what makes a broken sitemap hard to diagnose.
This piece walks through the format itself. If what you actually want is to produce a file right now, the Sitemap Generator will do it from a list of URLs, and Sitemap Generator: How to Create and Submit an XML Sitemap covers that workflow end to end.
The minimum viable sitemap
A sitemap is a UTF-8 encoded XML file. It opens with an XML declaration, wraps everything in a urlset element carrying the protocol namespace, and lists each address in a url element containing at least a loc.
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/</loc>
</url>
<url>
<loc>https://example.com/about/</loc>
</url>
</urlset>
That file is complete and valid. Everything else in the protocol is optional, and two of the three optional tags are largely ignored by Google. If you are hand-writing a sitemap, this is very often all you need.
The namespace declaration is not decorative. Drop it and validators reject the file, and search engines will not parse it. Copy it exactly, including the http rather than https — it is an identifier, not an address to fetch.
The three optional tags
lastmod
The date the page last changed materially, in W3C Datetime format — either 2026-08-23 or a full timestamp such as 2026-08-23T14:20:00+00:00. Google has said publicly that it uses this value only where a site has proven it accurate. A site that stamps the build date on every URL at every deploy teaches crawlers to ignore the field, so leave it out rather than fake it.
changefreq
A hint at how often the page changes: always, hourly, daily, weekly, monthly, yearly or never. Google ignores it. It costs nothing to include and buys nothing either.
priority
A number from 0.0 to 1.0 expressing importance relative to other pages on your own site. It was widely abused — every page set to 1.0 — and the major engines stopped acting on it years ago. Setting every URL to the same value is exactly as useful as omitting it.
The honest summary: loc matters, lastmod matters if you can keep it truthful, and the other two are historical.
The rules that break sitemaps
- Absolute URLs only. Every
locmust be a complete address including the protocol. Relative paths are invalid. - Same host, same protocol. A sitemap at
https://example.com/sitemap.xmlcannot listhttp://URLs, and cannot list URLs on a different subdomain. - Location determines scope. A sitemap can only list URLs at or below its own directory. One placed at
/blog/sitemap.xmlcannot legally list/products/pages. - 50,000 URLs and 50 MB uncompressed, per file. Beyond either limit you need several files and an index.
- Escape five characters. Ampersand, single quote, double quote, less-than and greater-than must be entity-escaped inside
loc. An unescaped ampersand in a query string is the single most common cause of an invalid sitemap.
That last one catches people constantly. ?a=1&b=2 must be written ?a=1&b=2 in the file. If you are assembling URLs by hand, run them through the URL Encoder and Decoder first so the escaping is right before it reaches the XML.
Sitemap index files
Once you pass 50,000 URLs, or once it makes sense to split by content type, you list your sitemaps in an index file. The structure mirrors a sitemap almost exactly, with sitemapindex replacing urlset and sitemap replacing url.
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://example.com/sitemap-posts.xml</loc>
<lastmod>2026-08-23</lastmod>
</sitemap>
<sitemap>
<loc>https://example.com/sitemap-pages.xml</loc>
</sitemap>
</sitemapindex>
An index may reference up to 50,000 sitemaps, and indexes cannot nest inside other indexes. Splitting by content type is worth doing even below the size limit, because Search Console reports indexing coverage per submitted file — separate files for posts, pages and products turn one vague number into three useful ones.
Sitemaps may be gzipped, and the limits apply to the uncompressed size. Serve sitemap.xml.gz with the right content encoding and crawlers handle it without complaint.
When hand-writing beats generating
Plugins are the right answer for most WordPress sites: Rank Math or Yoast will keep the file current as you publish, which is the part humans are worst at. There are three situations where a hand-made file is genuinely better.
- A small static site. Twelve pages that change twice a year do not need a generator. A hand-written file is smaller, faster and never wrong.
- A subset you want crawled first. A supplementary sitemap containing only the forty URLs you actually care about is a legitimate, well-supported way to focus attention during a migration or a launch.
- Auditing a broken generator. When a plugin is emitting noindexed URLs or redirects, reading the raw XML is how you prove it. Knowing the format turns a mystery into a five-minute check.
Whichever route you take, the same three tests apply: every URL returns 200, every URL is the canonical version, and every URL is one you would be happy to see in search results. A sitemap full of redirects and 404s is worse than no sitemap, because it teaches Google that your list cannot be trusted — a point covered in more depth in How to Stop Crawl Waste From Eating Your Rankings.
Getting it discovered
Two things to do once the file exists. Submit the address in Google Search Console under Sitemaps, and add a Sitemap: line to your robots.txt so any crawler can find it unprompted. The Robots.txt Generator has a field for exactly that, and there is a fuller treatment in Robots.txt Generator: Writing Rules Crawlers Actually Follow.
Then leave it alone. Submitting a sitemap repeatedly does not speed anything up, and resubmitting an unchanged file achieves nothing at all.
