XML sitemaps
What's listed
URLs come from two sources, in this order:
- Structure pages and listing entries, using their Structure URLs (the Structure pages switch under Sources on the Sitemap screen, on by default).
- Channel entries from channels that are enabled in MetaCraft, have Include in sitemap and Has detail pages on, and have a URL pattern, e.g.
/blog/{url_title}/. Pattern tokens:{url_title},{entry_id},{channel_short_name}. A pattern must start with a fixed folder.
An entry is listed only if all of these are true:
- its status is one of the public statuses (Sitemap screen → Rules; default
open) - its entry date has passed and it hasn't expired
- it resolves to
index(template parameters don't apply here; entry, channel and global robots do) - its sitemap setting isn't No (publish tab → Advanced)
- its canonical URL is its own URL. Entries canonicalized to another page are left out.
- it still exists. Structure URLs for deleted entries are skipped and counted as orphaned on the Sitemaps screen.
URLs are normalized (base URL, trailing-slash policy) and de-duplicated. Structure wins over a channel pattern. <lastmod> is the entry's edit date. Over 45,000 URLs, sitemap.xml becomes a sitemap index pointing at sitemap-1.xml, sitemap-2.xml, …
Setup (template mode, recommended)
In template mode MetaCraft builds the sitemap when /sitemap.xml is requested and keeps it in its own cache. No file is written to the server. If an old sitemap.xml file is already in the web root, remove it first: the web server sends a real file before ExpressionEngine sees the request, so MetaCraft's sitemap would never be reached.

The Sitemap screen: status, sources, rules and a preview.
- Sitemap screen: turn the Enabled switch on, check the public statuses under Rules, and set URL patterns under Sources for channels whose entries have their own pages.
- Create a template group (e.g.
metacraft) with a templatesitemap, type XML, containing only:{exp:metacraft:sitemap} - Template → Settings → Route:
sitemap.xml. (Template routes must be enabled: Settings → Template Settings → Enable template routes.) - For large sites, add a second XML template
sitemap_part:
with the route{exp:metacraft:sitemap part="{segment:part}"}sitemap-{part:integer}.xml. It's harmless on small sites (returns 404). - Open
https://your-site/sitemap.xml. You should get XML withContent-Type: application/xml. - Add the sitemap to
robots.txt(MetaCraft never edits that file):
Then submit the same URL in Google Search Console / Bing Webmaster Tools.Sitemap: https://your-site/sitemap.xml
The Sitemaps screen shows status, URL count, when it was generated, a preview of the XML, and a Regenerate now button.
When it rebuilds
The sitemap is cached. It's marked stale (never rebuilt during the save itself) when:
- an entry is saved or deleted (any way: CP, bulk edit, Channel Form, API)
- MetaCraft settings, channel settings or sitemap settings change, or the SEO Lite import runs
- the Structure tree changes (detected from the stored page URLs)
- a scheduled entry goes live or an entry expires
- it's more than 24 hours old
The next request for /sitemap.xml rebuilds it. Only one request rebuilds at a time; others are served the previous version meanwhile. If a rebuild fails, the previous sitemap keeps being served and the Sitemaps screen shows the error (details in the developer log). A failed rebuild isn't retried on every request.
Static mode
For hosts where requests for .xml files never reach PHP, MetaCraft can write real files instead.
Sitemap screen → Rules → Serve as: Static file, optionally a folder inside the web root (blank = the web root). Then rebuild from cron:
php /path/to/site/system/ee/eecli.php metacraft:sitemap
- Files are written atomically (temporary file, then rename) with permissions
0644. - MetaCraft never overwrites a file it didn't create. If a
sitemap.xmlalready exists, it stops and says so. - It never touches
robots.txt. - Rebuild static files when entries are saved (optional) rebuilds at the end of any CP request that changed something, so cron can run less often.
- Remove static files on the Sitemaps screen deletes only files MetaCraft wrote. Switching back to template mode or uninstalling leaves them in place.
CLI
php system/ee/eecli.php metacraft:sitemap [--site=<id>] [--force] [--dry-run]
--force: rebuild even if the cached sitemap is fresh.--dry-run: build without saving; prints the URL count and the first 20 URLs.- Exit code is non-zero on failure, so cron can alert.
Cron examples
Every hour is plenty for most sites. Use the PHP binary that matches the site's PHP version.
# Generic
0 * * * * php /path/to/site/system/ee/eecli.php metacraft:sitemap >/dev/null 2>&1
# Cloudways (Application → Cron Job Management → Advanced)
0 * * * * cd /home/master/applications/<app>/public_html && php system/ee/eecli.php metacraft:sitemap >/dev/null 2>&1
# Forge / Ploi / plain Ubuntu with several PHP versions
0 * * * * /usr/bin/php8.3 /home/forge/example.com/system/ee/eecli.php metacraft:sitemap >/dev/null 2>&1
# cPanel (Cron Jobs)
0 * * * * /usr/local/bin/php /home/<user>/public_html/system/ee/eecli.php metacraft:sitemap >/dev/null 2>&1
Hosting notes
Nginx in front of Apache (Cloudways and similar). Nginx often serves static file extensions itself, .xml included, and returns its own 404 for a file that doesn't exist, so /sitemap.xml never reaches ExpressionEngine. Check after setting up the template:
curl -sI https://your-site/sitemap.xml
If the response is an Nginx 404 (not ExpressionEngine's), either ask the host to pass .xml requests through when no file exists, or use static mode with cron.
Plain Nginx + PHP-FPM. The usual try_files $uri $uri/ /index.php?$args; already sends /sitemap.xml to ExpressionEngine.
Apache. EE's standard .htaccess rewrite (send requests that aren't real files to index.php) already covers /sitemap.xml.
Base URL. Sitemap URLs use MetaCraft's base URL (Settings → General; blank = EE's). If the site is behind a proxy or uses a different public domain, set it explicitly.