dotnet/aspnetcore · error

The property '${identifier}' is not writable.

Error message

The property '${identifier}' is not writable.

What it means

Thrown in the JSCallType.SetValue branch when isWritableProperty(parent, memberName) returns false (line 645). isWritableProperty checks the data/accessor descriptor's 'writable' flag or presence of a 'set'; missing properties and read-only properties (e.g. frozen objects, getter-only accessors, built-in constants) are not writable.

Source

Thrown at src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts:646

          }
          throw new Error(`The value '${identifier}' is not a function.`);

      case JSCallType.ConstructorCall:
          const ctor = parent[memberName];
          if (ctor instanceof Function) {
              const bound = ctor.bind(parent);
              return (...args: any[]) => new bound(...args);
          }
          throw new Error(`The value '${identifier}' is not a function.`);

      case JSCallType.GetValue:
          if (!isReadableProperty(parent, memberName)) {
              throw new Error(`The property '${identifier}' is not defined or is not readable.`);
          }
          return () => parent[memberName];
      case JSCallType.SetValue:
          if (!isWritableProperty(parent, memberName)) {
              throw new Error(`The property '${identifier}' is not writable.`);
          }
          return (...args: any[]) => parent[memberName] = args[0];
      }
  }

  function isReadableProperty(obj: any, propName: string) {
      // Return false for missing property.
      if (!(propName in obj)) {
          return false;
      }

      // If the property is present we examine its descriptor, potentially needing to walk up the prototype chain.
      while (obj !== undefined) {
          const descriptor = Object.getOwnPropertyDescriptor(obj, propName);

          if (descriptor) {
              // Return true for data property
              if (descriptor.hasOwnProperty("value")) {

View on GitHub (pinned to 294cab2f9b)

Solutions

  1. Check Object.getOwnPropertyDescriptor; if writable is false or only a getter exists, do not write directly.
  2. Expose a setter function in a JS shim and call it instead.
  3. Make the target object extensible / use a mutable container.
  4. Pick a different, writable property to hold the value.

Example fix

// before
await JS.InvokeVoidAsync("window.myConfig", newValue); // read-only

// after
// shim.js
let _cfg;
export function setConfig(v) { _cfg = v; }
export function getConfig() { return _cfg; }
// C#: await JS.InvokeVoidAsync("shim.setConfig", newValue);
Defensive patterns

Strategy: validation

Validate before calling

// shim: safe set
export function setProp(obj, k, v) {
  let o = obj;
  while (o) { const d = Object.getOwnPropertyDescriptor(o, k); if (d) { if (!d.writable && !d.set) throw new TypeError(`${k} read-only`); break; } o = Object.getPrototypeOf(o); }
  obj[k] = v;
}

Type guard

function isWritable(o:any, k:string):boolean {
  let cur = o;
  while (cur) { const d = Object.getOwnPropertyDescriptor(cur, k); if (d) return !!d.writable || !!d.set; cur = Object.getPrototypeOf(cur); }
  return Object.isExtensible(o);
}

Try / catch

try { await JS.InvokeVoidAsync('obj.prop', val); } catch (e) { if (/not writable/i.test(e.message)) { /* use setter shim */ } else throw e; }

Prevention

When it happens

Trigger: Writing to a read-only property (e.g. 'window.undefined', a frozen object's field), a getter-only accessor, or a property that doesn't exist yet on a non-extensible object. Also writing to a property whose prototype descriptor is configurable:false writable:false.

Common situations: Trying to assign to a DOM read-only property; mutating a frozen/sealed export; version change that made a property read-only; SSR where the global is a stub.

Related errors


AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06). Data as JSON: /api/errors/347cb6d3821b5cf3. Report an issue: GitHub.