Sitemap Collector Functions
The sitemap is assembled from collectors — each collector is a named source of canonical, root-relative URLs. Core ships five (products, categories, CMS pages, landing pages, static paths); the blog module registers three more. Extensions add their own from bootstrap.ts.
The pipeline is: collectors emit SitemapEntry paths → the generator expands each into one absolute, localized URL per enabled locale → the renderer serializes them into sitemap-<name>.xml children plus a sitemap.xml index.
Import
import {
registerSitemapCollector,
getSitemapCollectors,
createEntityCollector,
createStaticCollector,
generateSitemap
} from '@evershop/evershop/base/services/sitemap';
The sitemap feature lives in the base module, so the export path mirrors that.
Types
interface SitemapEntry {
path: string;
lastmod?: string;
changefreq?: SitemapChangeFreq;
priority?: number;
}
interface CollectorStat {
count: number;
maxUpdatedAt: string | null;
pathsHash?: string | null;
}
interface SitemapCollector {
name: string;
collect(): Promise<SitemapEntry[]>;
getFingerprint?(): Promise<CollectorStat>;
}
SitemapChangeFreq is the sitemaps.org vocabulary: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never'.
Every type on this page is importable from the same path — SitemapEntry, SitemapUrlRecord, SitemapAlternate, CollectorStat, SitemapCollector, SitemapChangeFreq, EntityCollectorSpec, StaticCollectorSpec, GenerateOptions, GenerateResult. The two exceptions are SitemapStorage and SitemapConfig: they appear in the GenerateOptions signature but are not re-exported, so those two override slots are effectively internal.
path must be canonical and root-relativeA collector emits exactly a url_rewrite.request_path — /women/shoes/awesome-shoes. Never an absolute URL, never a locale prefix. The base URL and the /<locale> prefixes are applied later by the generator.
registerSitemapCollector
registerSitemapCollector(collector: SitemapCollector): void
Register a source of sitemap URLs. Internally routes through addProcessor('sitemapCollectors', …), so ordering follows module load order.
Parameters
| Parameter | Type | Description |
|---|---|---|
collector | SitemapCollector | The collector. Its name doubles as the child-file basename (sitemap-<name>.xml), so it must be a URL-safe slug. |
Return Value
void.
Throws
Registration goes through the value registry, which is locked once every module's bootstrap.ts has run. Calling registerSitemapCollector from a middleware, a resolver or an API handler throws:
Registry is locked. Most likely you are trying to add a processor from a middleware.
Consider using a bootstrap file to add processors
Example
import {
registerSitemapCollector,
createEntityCollector
} from '@evershop/evershop/base/services/sitemap';
export default () => {
registerSitemapCollector(
createEntityCollector({
name: 'lookbooks',
table: 'lookbook',
entityType: 'lookbook',
where: 'e.status = true',
changefreq: 'weekly',
priority: 0.5
})
);
};
getSitemapCollectors
getSitemapCollectors(): SitemapCollector[]
The registered collectors, in registration order. Synchronous — reads the registry's already-resolved value.
Example
import { getSitemapCollectors } from '@evershop/evershop/base/services/sitemap';
const names = getSitemapCollectors().map((c) => c.name);
// ['products', 'categories', 'cms-pages', 'landing-pages', 'static', ...]
createEntityCollector
createEntityCollector(spec: EntityCollectorSpec): SitemapCollector
Build a collector that enumerates an entity's live friendly URLs by joining the entity table to url_rewrite. This is the shortcut for any entity that has url_rewrite rows — which is every entity EverShop gives a friendly URL.
Parameters
spec
Type: EntityCollectorSpec
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Collector name and child-file basename (sitemap-<name>.xml). URL-safe slug. |
table | string | Yes | The entity table, aliased e in the generated SQL. |
entityType | string | Yes | The url_rewrite.entity_type value for this entity. Bound as a query parameter. |
where | string | No | SQL boolean expression over the alias e, e.g. 'e.status = true'. Defaults to 'true' (all rows). |
updatedAtColumn | string | No | Timestamp column driving <lastmod>. Defaults to 'updated_at'; pass 'created_at' for tables that have no updated_at. |
changefreq | SitemapChangeFreq | No | Emitted on every entry produced by this collector. |
priority | number | No | 0.0 – 1.0. Emitted on every entry. |
table, entityType, where and updatedAtColumn are interpolatedOnly entityType is bound as a parameter; the rest are interpolated into the SQL string. They are meant to be trusted constants written by you, never user input.
Return Value
A SitemapCollector whose collect() reads request_path + the timestamp column, and whose getFingerprint() runs the same join as a count / max(updated_at) / md5(string_agg(request_path)). The pathsHash means a URL change that leaves updated_at untouched (a category move, an async url_rewrite rebuild) still flips the fingerprint.
Example
registerSitemapCollector(
createEntityCollector({
name: 'blog-tags',
table: 'blog_tag',
entityType: 'blog_tag',
updatedAtColumn: 'created_at',
changefreq: 'monthly',
priority: 0.3
})
);
createStaticCollector
createStaticCollector(spec: StaticCollectorSpec): SitemapCollector
A config-driven collector for fixed storefront paths. No database access.
Parameters
spec
Type: StaticCollectorSpec
| Field | Type | Required | Description |
|---|---|---|---|
paths | string[] | Yes | Root-relative paths. Anything not starting with / is silently skipped. |
changefreq | SitemapChangeFreq | No | Applied to every path. |
priority | number | No | Applied to every path. |
name | string | No | Defaults to 'static'. Override it when registering a second static collector, otherwise both write sitemap-static.xml. |
Return Value
A SitemapCollector. Its fingerprint is an md5 of the sorted path list, so adding, removing or swapping a path triggers a regenerate.
Example
import {
registerSitemapCollector,
createStaticCollector
} from '@evershop/evershop/base/services/sitemap';
export default () => {
registerSitemapCollector(
createStaticCollector({
name: 'marketing',
paths: ['/about', '/contact', '/size-guide'],
changefreq: 'monthly',
priority: 0.4
})
);
};
generateSitemap
generateSitemap(options?: GenerateOptions): Promise<GenerateResult>
Generate the whole sitemap set (index + per-collector children) into storage. Normally driven by a cron job and by the cold-path request handler; exposed for a programmatic "regenerate now".
Parameters
options
Type: GenerateOptions (optional)
| Field | Type | Description |
|---|---|---|
force | boolean | Rebuild even when the fingerprint is unchanged. |
collectors | SitemapCollector[] | Override the registered collectors. Defaults to getSitemapCollectors(). |
storage | SitemapStorage | Override the storage backend. |
config | SitemapConfig | Override the resolved sitemap config. |
baseUrl | string | Defaults to getBaseUrl(). |
locales | string[] | Defaults to await getEnabledLanguages(). |
defaultLocale | string | Defaults to await getStoreLanguage(). |
clock | () => number | Epoch-ms clock, injectable for deterministic tests. Defaults to Date.now. |
Return Value
Promise<GenerateResult>:
| Field | Type | Description |
|---|---|---|
generated | boolean | false when the run was skipped because the fingerprint was unchanged and the cached files are fresh and all present. |
files | string[] | ['sitemap.xml', 'sitemap-products.xml', …] |
fingerprint | string | The fingerprint computed for this run. |
urlCount | number | Total URL records written (or the cached count when skipped). |
Example
import { generateSitemap } from '@evershop/evershop/base/services/sitemap';
const result = await generateSitemap({ force: true });
console.log(result.generated, result.urlCount, result.files.length);
Notes
- Single-flight. Concurrent callers share one in-progress generation (per process); the second caller gets the first caller's promise rather than launching its own run.
- A collector without
getFingerprintdisables skipping for the whole run — every generation becomes a full rebuild. Implement it when you can summarize your set cheaply. - A collector that returns zero entries produces no child file at all.
- Children are written first, then the index, then the meta file — so a crash mid-write never advertises a child that is not on disk.
See Also
- Sitemap — The full sitemap guide (config keys, storage, cron, robots.txt)
- addProcessor — The registry primitive
registerSitemapCollectoris built on - Settings Getters —
getEnabledLanguages/getStoreLanguage, where the locale expansion gets its list