Skip to main content

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-relative

A 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

ParameterTypeDescription
collectorSitemapCollectorThe 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

extensions/my-extension/src/bootstrap.ts
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

FieldTypeRequiredDescription
namestringYesCollector name and child-file basename (sitemap-<name>.xml). URL-safe slug.
tablestringYesThe entity table, aliased e in the generated SQL.
entityTypestringYesThe url_rewrite.entity_type value for this entity. Bound as a query parameter.
wherestringNoSQL boolean expression over the alias e, e.g. 'e.status = true'. Defaults to 'true' (all rows).
updatedAtColumnstringNoTimestamp column driving <lastmod>. Defaults to 'updated_at'; pass 'created_at' for tables that have no updated_at.
changefreqSitemapChangeFreqNoEmitted on every entry produced by this collector.
prioritynumberNo0.0 – 1.0. Emitted on every entry.
table, entityType, where and updatedAtColumn are interpolated

Only 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

modules/blog/bootstrap.ts (core)
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

FieldTypeRequiredDescription
pathsstring[]YesRoot-relative paths. Anything not starting with / is silently skipped.
changefreqSitemapChangeFreqNoApplied to every path.
prioritynumberNoApplied to every path.
namestringNoDefaults 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

extensions/my-extension/src/bootstrap.ts
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)

FieldTypeDescription
forcebooleanRebuild even when the fingerprint is unchanged.
collectorsSitemapCollector[]Override the registered collectors. Defaults to getSitemapCollectors().
storageSitemapStorageOverride the storage backend.
configSitemapConfigOverride the resolved sitemap config.
baseUrlstringDefaults to getBaseUrl().
localesstring[]Defaults to await getEnabledLanguages().
defaultLocalestringDefaults to await getStoreLanguage().
clock() => numberEpoch-ms clock, injectable for deterministic tests. Defaults to Date.now.

Return Value

Promise<GenerateResult>:

FieldTypeDescription
generatedbooleanfalse when the run was skipped because the fingerprint was unchanged and the cached files are fresh and all present.
filesstring[]['sitemap.xml', 'sitemap-products.xml', …]
fingerprintstringThe fingerprint computed for this run.
urlCountnumberTotal 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 getFingerprint disables 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 registerSitemapCollector is built on
  • Settings GettersgetEnabledLanguages / getStoreLanguage, where the locale expansion gets its list