apify/crawlee · error · Error

The custom statistics field `${String(key)}` collides with a

Error message

The custom statistics field `${String(key)}` collides with a built-in one - it would shadow the value the crawler tracks. Rename it in `stateExtension`.

What it means

Statistics supports custom tracked fields via a stateExtension. This error is thrown in the Statistics constructor when a custom field name matches one of the built-in statistics fields, because it would shadow values the crawler tracks internally. Rename the custom field to avoid the collision.

Source

Thrown at packages/core/src/crawlers/statistics.ts:361

        this.id = id ?? String(Statistics.#id++);
        this.#persistStateKey = `CRAWLEE_CRAWLER_STATISTICS_${this.id}`;

        this.log = (log ?? serviceLocator.getLogger()).child({ prefix: 'Statistics' });
        this.errorTracker = new ErrorTracker({ ...errorTrackerConfig, saveErrorSnapshots });
        this.errorTrackerRetry = new ErrorTracker({ ...errorTrackerConfig, saveErrorSnapshots });
        this.#logIntervalMillis = logIntervalSecs * 1000;
        this.#logMessage = logMessage;
        this.#stateExtension = stateExtension as StatisticStateExtensionOptions<
            StateExtension,
            PersistedStateExtension
        >;
        this.#defaultStateExtension = this.#resolveDefaultStateExtension(this.#stateExtension);
        this.#stateExtensionKeys = Object.keys(this.#defaultStateExtension()) as (keyof StateExtension)[];

        for (const key of this.#stateExtensionKeys) {
            if ((key as string) in this.#builtInDefaultState()) {
                throw new Error(
                    `The custom statistics field \`${String(key)}\` collides with a built-in one - it would shadow ` +
                        'the value the crawler tracks. Rename it in `stateExtension`.',
                );
            }
        }

        // `calculate()` is late-bound on purpose - it is an override point, and a subclass's must be the one that runs.
        this.#stateCodec = buildStatisticStateCodec({
            statsId: this.id,
            defaultState: () => this.#defaultState(),
            calculate: () => this.calculate(),
        });

        this.#recoverableState = new RecoverableState({
            persistStateKey: this.#persistStateKey,
            persistenceEnabled: persistenceOptions.enable,
            keyValueStore,
            logger: this.log,

View on GitHub (pinned to dbe57fb09c)

Solutions

  1. Rename the colliding key in stateExtension (deserialize/defaultState)
  2. Log Object.keys(built-in default state) to compare against your custom keys
  3. Pick a prefixed naming convention for custom fields (e.g. `custom_` prefix)
  4. After library upgrades, re-check for collisions with newly added built-in fields

Example fix

// before
stateExtension: { deserialize: (s) => ({ errors: s.e }) }
// after
stateExtension: { deserialize: (s) => ({ customErrors: s.e }) }
Defensive patterns

Strategy: validation

Validate before calling

const builtIn = Object.keys(crawler.stats.state); // inspect before naming custom fields
const clash = Object.keys(myDefaults).filter((k) => builtIn.includes(k));
if (clash.length) throw new Error(`rename custom fields: ${clash}`);

Try / catch

try { new Statistics({ stateExtension }); } catch (e) { if (String(e).includes('collides with a built-in')) renameFields(); else throw e; }

Prevention

When it happens

Trigger: Passing a stateExtension whose deserialized/default state contains a key present in the built-in default state (e.g. `errors`, `requestsFinished`, `crawlerStartedAt`).

Common situations: Extending crawler stats with common field names after migrating code; copying built-in field names into custom extensions; merging two configs where a custom field collides with newer built-ins after a library upgrade.

Related errors


AI-assisted analysis of apify/crawlee@dbe57fb09c (2026-08-30). Data as JSON: /api/errors/ac2ccf3ca9f2887f. Report an issue: GitHub.