hcengineering/platform · error · PlatformError

Modification is not allowed

Error message

Modification is not allowed

What it means

Model documents are handed out as read-only freeze proxies; the proxy's set trap unconditionally throws this PlatformError on any property assignment. The model is immutable from the client's perspective — mutations must go through transactions/update APIs, never direct property writes. Note that errors thrown inside a Proxy set trap surface at the assignment site, which may be far from the model code.

Source

Thrown at foundations/core/packages/core/src/proxy.ts:55

    }
    if (value instanceof Set) {
      throw new PlatformError(unknownError('Set is not allowed in model'))
    }
    return new Proxy(value, _createFreezeProxy(value))
  }
  return value
}
/**
 * @internal
 */
export function _createFreezeProxy (doc: Doc): ProxyHandler<Doc> {
  return {
    get (target: any, property: string, receiver: any): any {
      const value = target[property]
      return freeze(value)
    },
    set (target, p, newValue, receiver): any {
      throw new PlatformError(unknownError('Modification is not allowed'))
    },
    defineProperty (target, property, attributes): any {
      throw new PlatformError(unknownError('Modification is not allowed'))
    },

    deleteProperty (target, p): any {
      throw new PlatformError(unknownError('Modification is not allowed'))
    },
    setPrototypeOf (target, v): any {
      throw new PlatformError(unknownError('Modification is not allowed'))
    }
  }
}

/**
 * @internal
 */
export function _toDoc<D extends Doc> (doc: D): D {

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Use the library's update/transaction API ($set) to change document data instead of direct assignment.
  2. Unwrap the proxy with _toDoc(doc) only if you need to read internals — but perform writes through the proper API.
  3. Build a new plain object (spread) for derived/computed results instead of mutating the frozen doc.
  4. Clone fields you need to modify: const copy = { ...toDoc(doc) }; copy.field = x; then persist copy via update.

Example fix

// before
doc.title = 'new title'
await tx.updateDoc(doc)
// after
await tx.updateDoc(_class, _id, _id, { $set: { title: 'new title' } })
Defensive patterns

Strategy: try-catch

Validate before calling

function isFrozenModelDoc(v: unknown): boolean {
  return v != null && typeof v === 'object' && (v as any).$___proxy_target !== undefined
}
if (!isFrozenModelDoc(doc)) {
  // safe-ish plain object; still prefer the update API for persistence
}

Type guard

function isMutableCopy<T extends object>(v: T): v is T & { __mutable: true } {
  return Object.isFrozen(v) === false && (v as any).$___proxy_target === undefined
}

Try / catch

try {
  (doc as any).title = 'x'
} catch (e) {
  if (e instanceof PlatformError && e.message.includes('Modification is not allowed')) {
    await tx.updateDoc(_class, _id, _id, { $set: { title: 'x' } })
  } else throw e
}

Prevention

When it happens

Trigger: Writing doc.someField = value on any object obtained from the model/query results (e.g. inside helpers like groupByArray or while processing queried docs), or via Object.assign(doc, {...}) and destructuring-with-reassignment on a frozen doc.

Common situations: Mutating a fetched document in place before saving; augmenting query results with computed fields; cache-style code assuming plain mutable objects; copying patterns that rely on assignment into the same object.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/49d46635778cca62. Report an issue: GitHub.