pubkey/rxdb · error · RxError

DOC9

DOC9

Error message

        RxDB Error-Code: ${message}.
        Hint: Error messages are not included in RxDB core to reduce build size.
        To show the full error messages and to ensure that you do not make any mistakes when using RxDB,
        use the dev-mode plugin when you are in development mode: https://rxdb.info/dev-mode.html?console=error
        
Find out more about this error here: https://rxdb.info/errors.html?console=errors#DOC9 
Still stuck? Ask in the RxDB Discord: https://rxdb.info/chat 

What it means

DOC9 is thrown by RxSchema.validateChange() when a document update modifies a field marked as final in the schema. Final fields are immutable after the document is created; this protects primary keys and other frozen values from diverging between replicas. The error carries dataBefore, dataAfter and the offending fieldName.

Source

Thrown at src/rx-schema.ts:122

     */
    public get hash(): Promise<string> {
        return overwriteGetterForCaching(
            this,
            'hash',
            this.hashFunction(JSON.stringify(this.jsonSchema))
        );
    }

    /**
     * checks if a given change on a document is allowed
     * Ensures that:
     * - final fields are not modified
     * @throws {Error} if not valid
     */
    validateChange(dataBefore: any, dataAfter: any): void {
        this.finalFields.forEach(fieldName => {
            if (!deepEqual(dataBefore[fieldName], dataAfter[fieldName])) {
                throw newRxError('DOC9', {
                    dataBefore,
                    dataAfter,
                    fieldName,
                    schema: this.jsonSchema
                });
            }
        });
    }

    /**
     * creates the schema-based document-prototype,
     * see RxCollection.getDocumentPrototype()
     */
    public getDocumentPrototype(): any {
        const proto: any = {};

        /**
         * On the top level, we know all keys

View on GitHub (pinned to af6fb65f94)

Solutions

  1. Remove the final field from your update data; only patch mutable fields.
  2. Use incrementalModify()/incrementalPatch with a modifier that leaves final fields untouched.
  3. If the field genuinely must change, remove 'final' from the schema (requires schema version bump and a migration strategy).
  4. To 'change' an immutable document, remove it and re-insert a new document with a new primary key.

Example fix

// before
await doc.incrementalPatch({ id: 'new-id', counter: 5 }); // id is final
// after
await doc.incrementalPatch({ counter: 5 }); // only mutable fields
Defensive patterns

Strategy: try-catch

Validate before calling

function patchIsSafe(doc: RxDocument, patch: Record<string, any>): boolean {
  const finalFields: string[] = (doc.collection.schema as any).finalFields;
  return !finalFields.some(f =>
    f in patch && !deepEqual((doc.toJSON() as any)[f], patch[f]));
}

Try / catch

try {
  await doc.incrementalPatch(patch);
} catch (err: any) {
  if (err.code === 'DOC9') {
    const field = err.parameters?.fieldName;
    // strip the immutable field and retry
    const { [field]: _omit, ...rest } = patch;
    await doc.incrementalPatch(rest);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling doc.incrementalPatch()/incrementalModify()/update() or collection.upsert() with new data in which a final field (including the primary key and fields listed in schema.finalFields) has a value different from the stored document. deepEqual comparison fails, so even changed nested objects in a final field trigger it.

Common situations: Trying to change the primary key of an existing document; accidentally including final fields in a patch object built from spread state; upserting a full document where an immutable field was recalculated (e.g. timestamps or counters marked final); migrated code from databases without immutability rules.

Related errors


AI-assisted analysis of pubkey/rxdb@af6fb65f94 (2026-08-31). Data as JSON: /api/errors/b10820aa771e902d. Report an issue: GitHub.