apify/crawlee · error · TypeError

Configuration is immutable. Pass options via the constructor

Error message

Configuration is immutable. Pass options via the constructor instead.

What it means

Crawlee Configuration instances are frozen: resolved option fields get setters that throw a TypeError. Options must be provided when the Configuration is constructed; mutating a global or existing instance afterwards is deliberately unsupported.

Source

Thrown at packages/core/src/configuration.ts:267

            const parsed = fieldDef.schema.safeParse(undefined);
            values[key] = parsed.success ? parsed.data : undefined;
        }

        return values;
    }

    /**
     * Registers getters (and throwing setters) on the instance for each field.
     */
    private registerAccessors(): void {
        const fields = (this.constructor as typeof Configuration).fields;
        const descriptors: PropertyDescriptorMap = {};

        for (const key of Object.keys(fields)) {
            descriptors[key] = {
                get: () => this.#resolvedValues[key],
                set() {
                    throw new TypeError('Configuration is immutable. Pass options via the constructor instead.');
                },
                enumerable: true,
                configurable: false,
            };
        }

        Object.defineProperties(this, descriptors);
    }

    /**
     * Reads the first defined env var value for a field definition.
     * Empty strings are treated as unset, falling through to crawlee.json or schema defaults.
     * (Crawlee v3 coerced `''` to `false`/`0`/`''` per type — v4 drops that for consistency.)
     */
    private static readEnvVar(fieldDef: ConfigField): string | undefined {
        if (!fieldDef.envVar) return undefined;
        const envVars = Array.isArray(fieldDef.envVar) ? fieldDef.envVar : [fieldDef.envVar];
        for (const envVar of envVars) {

View on GitHub (pinned to dbe57fb09c)

Solutions

  1. Pass all options to the constructor: `new Configuration({ persistStorage: false, ... })`.
  2. Replace the global instance early with `Configuration.getGlobalConfig()` / set a fresh `new Configuration(...)` before any storage clients initialize.
  3. Restructure code so options are known before the run starts instead of mutating later.

Example fix

// before
const config = Configuration.getGlobal();
config.persistStorage = false;
// after
const config = new Configuration({ persistStorage: false });
Defensive patterns

Strategy: validation

Validate before calling

// configure once, at startup, before anything reads the config
const config = new Configuration({ persistStorage: false, defaultDatasetId: 'my-dataset' });

Type guard

function isMutableConfigWrite(key: string): boolean { return ['persistStorage','defaultDatasetId','memoryMbytes'].includes(key); } // treat all resolved fields as read-only instead

Try / catch

try { (config as any).persistStorage = false; } catch (err) { if (err instanceof TypeError && err.message.includes('immutable')) { config = new Configuration({ persistStorage: false }); } else { throw err; } }

Prevention

When it happens

Trigger: Assigning to a configuration field at runtime, e.g. `Configuration.getGlobal().persistStorage = false` or `config.defaultDatasetId = 'x'`, after the instance was created.

Common situations: Toggling settings mid-run (persistStorage, proxies) in tests; mutating `Configuration.getGlobal()` imported from another module; code written against older Crawlee versions where the config was mutable.

Related errors


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