Validador de sitemap XML
Todas las reglas del protocolo. Sin límite de URL ni registro.
Todo se ejecuta en esta pestaña. Nada de lo que pegues se sube, se registra ni se envía a ningún sitio. Abre el panel de red y compruébalo.
Pega tu sitemap arriba, o escribe su dirección y pulsa Descargar, y se comprueban de una vez todas las reglas del protocolo de sitemaps.org: el número de URLs contra el límite de 50.000, el tamaño sin comprimir contra el límite de 50 MB, y el formato de cada loc, lastmod, changefreq y priority. Los archivos de índice de sitemaps tienen su propio conjunto de reglas.
La mayoría llega desde Search Console con un "No se ha podido leer el sitemap" o un "El sitemap se puede leer, pero tiene errores", y ninguno de los dos nombra una línea. Aquí cada hallazgo cita el valor al que ha objetado, referencia la regla y dice qué escribir en su lugar.
Dos cosas lo separan del resto de esta búsqueda. No se sube nada, así que un sitemap de staging que lista páginas que aún no has lanzado se queda en tu máquina. Y las comprobaciones que no se han podido ejecutar se informan como no ejecutadas, no como superadas.
Los dos límites duros
Un solo sitemap puede listar 50.000 URLs y puede ocupar 50 MB sin comprimir, que el protocolo expresa como 52.428.800 bytes. Los dos se comprueban, y el tamaño en bytes se informa estés cerca o no, porque es el único número que no puedes juzgar mirando el archivo.
Gzip no te da margen: el límite se aplica tras descomprimir, así que un sitemap.xml.gz de 3 MB que se expande a 61 MB lo supera. Un índice de sitemaps lleva los mismos dos límites. Los dos avisan al noventa por ciento, ya que un sitemap construido a partir de un catálogo que crece cruza la línea un martes cualquiera.
- 50.000 entradas <url> por archivo de sitemap.
- 52.428.800 bytes sin comprimir, lo comprimas con gzip o no.
- 2.048 caracteres por valor de <loc>.
- 50.000 entradas <sitemap> por índice de sitemaps.
El espacio de nombres tiene que ser exacto
La raíz es <urlset>, o <sitemapindex> para un índice, y debe declarar http://www.sitemaps.org/schemas/sitemap/0.9. Tres detalles pillan a la gente: es http y no https incluso en un sitio https, no lleva barra final, y la versión es 0.9 y siempre lo ha sido.
Si te equivocas, nada se queja en voz alta. El archivo sigue estando bien formado y sigue abriéndose en un navegador. Los buscadores simplemente no reconocen elementos en el espacio de nombres equivocado, así que leen tu sitemap como un documento que no contiene nada.
Mezclar hijos <url> y <sitemap> es un error, porque un archivo o es un sitemap o es un índice. La codificación declarada debe ser UTF-8. De los elementos de extensión solo se comprueba que tengan un prefijo declarado; las reglas de imagen, vídeo y noticias no se validan aquí.
lastmod, changefreq y priority
lastmod debe ser un W3C Datetime. AAAA-MM-DD vale por sí solo; una marca de tiempo completa vale si lleva zona, como en 2026-03-14T09:30:00+00:00. Un espacio en lugar de la T, una marca de tiempo Unix, 14/03/2026, o una marca sin zona son todos errores.
Se ejecuta una segunda comprobación de lastmod que ningún otro validador gratuito hace. Si más de cuatro entradas llevan uno y todos los valores son idénticos, recibes una advertencia: tu generador está estampando la hora del build en vez de la fecha en que cambió el contenido. Google usa lastmod solo cuando es consistente y verificablemente exacto, así que ese patrón le enseña a ignorar el campo.
changefreq acepta siete valores en minúsculas: always, hourly, daily, weekly, monthly, yearly, never. priority debe estar entre 0.0 y 1.0. Los dos se informan además como información cuando están presentes, porque Google afirma en su propia documentación que los ignora.
Mismo host, mismo directorio, y descargar por URL
Un sitemap en https://example.com/catalog/sitemap.xml puede listar URLs bajo https://example.com/catalog/ y no puede listar URLs bajo https://example.com/images/. Mismo host, mismo esquema, en su propio directorio o por debajo. Esas reglas necesitan la dirección en la que se publicará el sitemap, que el archivo no contiene.
Para eso está la casilla de URL, y puedes escribir una dirección sin descargar nada. Escríbela y se activan tres comprobaciones: otro host es error, un esquema distinto es error, y un loc fuera del propio directorio del sitemap es advertencia. Déjala vacía y las tres aparecen como no ejecutadas en vez de pasar en silencio.
Descargar pide el archivo desde tu navegador directamente a tu servidor, sin proxy de por medio, así que CORS lo bloquea en muchos alojamientos. Cuando ocurre, obtienes el motivo y un comando curl para ejecutarlo tú.
Comprobar un sitemap desde código
Merece la pena meterlo en un paso de build, para que un sitemap que ha rebasado el límite de 50.000 URLs rompa tu despliegue en vez de tu indexación. Cada ejemplo cuenta URLs, mide el tamaño sin comprimir y comprueba lastmod.
// Node 18 or later. npm i @xmldom/xmldom
import { DOMParser } from '@xmldom/xmldom';
const SITEMAP_NS = 'http://www.sitemaps.org/schemas/sitemap/0.9';
const MAX_URLS = 50_000;
const MAX_BYTES = 52_428_800;
const response = await fetch('https://example.com/sitemap.xml');
const xml = await response.text();
const bytes = Buffer.byteLength(xml, 'utf8');
// xmldom does not resolve external entities, so there is no XXE surface here.
const doc = new DOMParser().parseFromString(xml, 'text/xml');
const root = doc.documentElement;
if (root.namespaceURI !== SITEMAP_NS) {
throw new Error('Namespace is "' + root.namespaceURI + '", expected "' + SITEMAP_NS + '"');
}
const locs = doc.getElementsByTagNameNS(SITEMAP_NS, 'loc');
console.log(locs.length + ' URLs, ' + bytes + ' bytes uncompressed');
if (locs.length > MAX_URLS) throw new Error('Over the 50,000 URL limit');
if (bytes > MAX_BYTES) throw new Error('Over the 50 MB limit');
const lastmods = doc.getElementsByTagNameNS(SITEMAP_NS, 'lastmod');
for (let i = 0; i < lastmods.length; i++) {
const v = lastmods.item(i).textContent.trim();
// W3C Datetime: a bare date, or a timestamp that carries a zone.
const ok = /^\d{4}-\d{2}-\d{2}$/.test(v)
|| (/^\d{4}-\d{2}-\d{2}T/.test(v) && /(Z|[+-]\d{2}:\d{2})$/.test(v));
if (!ok) console.error('Invalid lastmod: ' + v);
}# pip install lxml requests
import re
import sys
import requests
from lxml import etree
SITEMAP_NS = 'http://www.sitemaps.org/schemas/sitemap/0.9'
MAX_URLS, MAX_BYTES = 50_000, 52_428_800
raw = requests.get('https://example.com/sitemap.xml', timeout=30).content
# The three flags are the safe form. lxml will otherwise expand entities and
# fetch a DTD the document references.
parser = etree.XMLParser(resolve_entities=False, no_network=True, load_dtd=False)
root = etree.fromstring(raw, parser)
if etree.QName(root).namespace != SITEMAP_NS:
sys.exit('Wrong namespace: %s' % etree.QName(root).namespace)
locs = root.findall('{%s}url/{%s}loc' % (SITEMAP_NS, SITEMAP_NS))
print('%d URLs, %d bytes uncompressed' % (len(locs), len(raw)))
if len(locs) > MAX_URLS:
sys.exit('Over the 50,000 URL limit')
if len(raw) > MAX_BYTES:
sys.exit('Over the 50 MB limit')
W3C = re.compile(r'^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2}))?$')
for lm in root.iter('{%s}lastmod' % SITEMAP_NS):
if not W3C.match((lm.text or '').strip()):
print('Invalid lastmod:', lm.text)
# The protocol publishes an XSD, which lxml can check structure against:
# schema = etree.XMLSchema(etree.parse('sitemap.xsd'))
# schema.assertValid(root.getroottree())import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.format.DateTimeParseException;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
final String NS = "http://www.sitemaps.org/schemas/sitemap/0.9";
byte[] bytes = HttpClient.newHttpClient()
.send(HttpRequest.newBuilder(URI.create("https://example.com/sitemap.xml")).build(),
HttpResponse.BodyHandlers.ofByteArray())
.body();
DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
// Without setNamespaceAware(true) every namespace lookup below returns nothing.
// This is the single most common reason sitemap parsing code silently finds zero URLs.
f.setNamespaceAware(true);
f.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
f.setXIncludeAware(false);
Document doc = f.newDocumentBuilder().parse(new ByteArrayInputStream(bytes));
NodeList locs = doc.getElementsByTagNameNS(NS, "loc");
System.out.printf("%d URLs, %d bytes uncompressed%n", locs.getLength(), bytes.length);
if (locs.getLength() > 50_000) System.err.println("Over the 50,000 URL limit");
if (bytes.length > 52_428_800) System.err.println("Over the 50 MB limit");
NodeList lastmods = doc.getElementsByTagNameNS(NS, "lastmod");
for (int i = 0; i < lastmods.getLength(); i++) {
String v = lastmods.item(i).getTextContent().trim();
try {
if (v.length() == 10) LocalDate.parse(v);
else OffsetDateTime.parse(v); // rejects a timestamp with no zone
} catch (DateTimeParseException e) {
System.err.println("Invalid lastmod: " + v);
}
}using System.Globalization;
using System.Xml;
const string Ns = "http://www.sitemaps.org/schemas/sitemap/0.9";
// XmlReader streams, so a 50 MB sitemap never becomes a 400 MB DOM.
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit, // external entities are never fetched
XmlResolver = null,
IgnoreWhitespace = true,
};
string[] w3c =
{
"yyyy-MM-dd",
"yyyy-MM-ddTHH:mmK",
"yyyy-MM-ddTHH:mm:ssK",
"yyyy-MM-ddTHH:mm:ss.FFFFFFFK",
};
byte[] bytes = await new HttpClient().GetByteArrayAsync("https://example.com/sitemap.xml");
int urls = 0;
using var stream = new MemoryStream(bytes);
using var reader = XmlReader.Create(stream, settings);
while (reader.Read())
{
if (reader.NodeType != XmlNodeType.Element || reader.NamespaceURI != Ns) continue;
if (reader.LocalName == "loc")
{
urls++;
}
else if (reader.LocalName == "lastmod")
{
var v = reader.ReadElementContentAsString().Trim();
// TryParse would accept 03/14/2026. TryParseExact against the four
// permitted layouts is what the protocol actually asks for.
if (!DateTimeOffset.TryParseExact(v, w3c, CultureInfo.InvariantCulture,
DateTimeStyles.None, out _))
{
Console.Error.WriteLine("Invalid lastmod: " + v);
}
}
}
Console.WriteLine(urls + " URLs, " + bytes.Length + " bytes uncompressed");
if (urls > 50_000) Console.Error.WriteLine("Over the 50,000 URL limit");
if (bytes.Length > 52_428_800) Console.Error.WriteLine("Over the 50 MB limit");# The protocol publishes an XSD, so xmllint can check the structure offline.
curl -sL https://example.com/sitemap.xml -o sitemap.xml
curl -sO https://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd
xmllint --noout --nonet --schema sitemap.xsd sitemap.xml
# The two hard limits:
xmllint --nonet --xpath 'count(//*[local-name()="loc"])' sitemap.xml # cap 50,000
wc -c < sitemap.xml # cap 52,428,800
# A gzipped sitemap must be decompressed before either check:
curl -sL https://example.com/sitemap.xml.gz | gunzip > sitemap.xml
# Pull every URL out, one per line, for a crawler or a spreadsheet:
xmllint --nonet --xpath '//*[local-name()="loc"]/text()' sitemap.xml | tr -s '\n'Los flags de seguridad no son adorno: Java, Python y C# resuelven todos entidades externas salvo que se les diga lo contrario, y un sitemap suele ser un archivo que no escribiste tú, que llega por red desde un plugin. El flag --nonet de xmllint hace el mismo trabajo.
Preguntas frecuentes
Search Console dice que mi sitemap tiene errores, pero esta herramienta dice que está bien. ¿Por qué?
Los dos miran cosas distintas. Esta página comprueba el archivo: estructura, espacio de nombres, límites, fechas, y las reglas de host y directorio en cuanto le das la dirección del sitemap. Search Console comprueba además lo que solo un rastreador puede, empezando por si el archivo era accesible.
"No se ha podido obtener" suele significar un 404, una cadena de redirecciones, una regla de robots.txt que cubre la ruta del sitemap, o una página de error HTML servida con estado 200. Descargar la URL aquí muestra el código de estado real. El otro desajuste habitual es que las URLs son válidas pero las páginas detrás están en noindex o han desaparecido, que es un problema de rastreo.
¿Google usa changefreq y priority?
No. Google afirma en su documentación de sitemaps que ignora los dos. Esta herramienta informa de su presencia como información y no como error, porque son elementos legales y no hacen daño.
En la práctica: ninguna combinación de valores de priority hará que una página se rastree antes que otra, y un changefreq puesto a hourly no subirá tu frecuencia de rastreo. Si mantienes código que los calcula, ese código no se está ganando el sueldo. lastmod es la excepción, y es el único elemento opcional que merece la pena acertar.
¿Qué formato necesita lastmod?
W3C Datetime, un subconjunto de ISO 8601. La forma válida más simple es una fecha a secas, 2026-03-14. Si incluyes una hora tienes que incluir zona: 2026-03-14T09:30:00Z y 2026-03-14T09:30:00+00:00 son las dos correctas.
Las formas rechazadas son justo las que producen de forma natural las bases de datos y las plantillas. Un espacio en lugar de la T no vale, una marca de tiempo sin zona no vale, y tampoco un número de época Unix ni ninguna fecha con el día delante como 14/03/2026. Si las fechas malas vienen de un plugin y no de tu código, el valor citado en el error suele identificar cuál.
¿Cuántas URLs puede tener un sitemap?
50.000, y además debe mantenerse por debajo de 52.428.800 bytes sin comprimir. El que alcances primero es el que manda, y en un sitio con URLs largas el límite de tamaño suele llegar antes que el de cuenta.
Pasado cualquiera de los dos, divide el archivo y lista las partes en un índice de sitemaps: un archivo pequeño en el mismo espacio de nombres cuyos hijos son elementos <sitemap>, cada uno con un <loc>. Un índice puede listar 50.000 sitemaps y está sujeto a los mismos 50 MB. Envía el índice en lugar de cada hijo, y referéncialo desde robots.txt.
¿Por qué ha fallado el botón Descargar con mi sitemap?
Casi siempre CORS. Tu navegador no dejará que esta página lea una respuesta de otro dominio salvo que ese dominio envíe una cabecera Access-Control-Allow-Origin, y pocos servidores la envían para sitemap.xml. Eso es política de tu servidor, no un fallo del archivo.
No lo sorteamos con un proxy, porque un proxy llevaría tu URL y tu archivo a través de un servidor que gestionamos nosotros. El mensaje de fallo te da un comando curl para la dirección que escribiste. Un sitemap comprimido con gzip hay que descomprimirlo antes: curl -sL https://example.com/sitemap.xml.gz | gunzip.
¿Se sube mi sitemap a algún sitio?
No. El analizador y todas las reglas son JavaScript ejecutándose en esta pestaña. No hay backend al que enviar nada ni analítica con acceso al editor. Abre tu panel de red y valida un archivo: los recursos de la página se cargan una vez y después nada.
Un sitemap publicado es público, pero el archivo que estás depurando a menudo no lo es. Los sitemaps de staging listan páginas sin lanzar, herramientas internas y sitios de clientes bajo acuerdo de confidencialidad, y varios de los comprobadores posicionados para esta búsqueda funcionan en servidor. Tu entrada se guarda en el localStorage de este navegador para que un refresco no la pierda; Limpiar la elimina.