← Chrome 149 reference

v149 · webassembly · experimental

WebAssembly Custom Descriptors

A WasmGC extension that attaches JavaScript prototypes to WebAssembly struct types. JS code can call methods on Wasm objects using normal dot-notation; Wasm objects can participate in instanceof checks. Builds on the WasmGC proposal (structs, arrays, and precise GC).

Experimental — origin trial WebAssembly Custom Descriptors is in a Chrome origin trial as of Chrome 149. The WAT syntax and JS API are still being finalised by the WebAssembly CG. Check the spec repo before shipping anything that depends on this feature.

at a glance

ChromeOrigin trial in Chrome 149
StatusWebAssembly CG proposal — phase 2
Spec repoWebAssembly / custom-descriptors
Overviewproposals/custom-descriptors/Overview.md
ChromeStatus6024844719947776 — WebAssembly Custom Descriptors
PrerequisiteWasmGC (shipped in Chrome 119)

why it exists

WasmGC introduced first-class structs and arrays with precise garbage collection. However, to share type-level data — vtables, interface tables, static fields — across all instances of a struct type, WasmGC programs had to store a reference to that data inside each struct instance. This wastes memory (one pointer per object) and requires explicit field lookups in every vtable call.

Custom descriptors solve this with an engine-managed runtime type table: each struct type can have a descriptor type whose single instance is shared by all objects of that struct type. This eliminates the per-object vtable pointer. As a second-order effect, the first field of the descriptor can be a JavaScript prototype object, enabling seamless JS-Wasm interop: wasmObject.myMethod() dispatches through the prototype chain without any wrapping.

Source: custom-descriptors/Overview.md §1 "Motivation".

shape of the API

WAT: descriptor and describes clauses

Structs and their descriptors are paired in a rec group. The struct uses descriptor to name its descriptor type; the descriptor uses describes to name the struct it covers.

(rec
  (type $counter (descriptor $counter.vtable)
    (struct (field $val (mut i32))))

  (type $counter.vtable (describes $counter)
    (struct
      (field $proto (ref extern))  ;; first field → JS prototype
      (field $get   (ref $get_fn))
      (field $inc   (ref $inc_fn))
    )
  )
)

WAT: allocating with a descriptor

struct.new_desc $T desc_ref field... Like struct.new but takes a reference to the descriptor instance as the final operand. The descriptor is shared — typically imported as a global.
struct.new_default_desc $T desc_ref Same but initialises all fields to zero / null defaults.

WAT: accessing the descriptor

ref.get_desc $T obj_ref Returns the descriptor for the given struct instance. If obj_ref has an exact type, the descriptor's exact type is known.

JavaScript prototype integration

If a struct type has a descriptor whose first field is an immutable externref, the WebAssembly JS API uses that field's value as the prototype for all instances of the struct. No wrapping needed — the Wasm struct acts as a JS object.

// JavaScript
const counterProto = {};
counterProto.get = function() {
  return wasmExports["counter.get"](this);
};
counterProto.inc = function() {
  wasmExports["counter.inc"](this);
};

const { instance } = await WebAssembly.instantiateStreaming(
  fetch("counter.wasm"),
  { env: { "counter.proto": counterProto } }
);

const c = instance.exports.counter.value;
console.log(c.get());   // normal method call on a Wasm object
c.inc();
console.log(c instanceof Object);  // true — prototype chain works

Declarative configureAll (builtin)

For large modules, import configureAll from "wasm:js-prototypes" to set up all prototypes and constructors in one call from the start function. Requires { builtins: ["js-prototypes"] } in the compile options.

// WAT: import the builtin
(import "wasm:js-prototypes" "configureAll"
  (func $configureAll (type $configureAll_t)))

// JS: compile with the builtin enabled
const { instance } = await WebAssembly.instantiate(bytes, imports, {
  builtins: ["js-prototypes"]
});

const Counter = constructors.Counter;   // installed by configureAll
const c = new Counter(0);
console.log(c.get());
console.log(c instanceof Counter);      // true
Source: custom-descriptors/Overview.md §3–§5.

key instructions summary

struct.new_descAllocate a struct with an explicit descriptor reference.
struct.new_default_descAllocate with zero-initialised fields + descriptor.
ref.get_descRetrieve the descriptor instance for a struct object.
ref.cast_desc_eqCast + check that two objects share the same descriptor instance.
br_on_cast_desc_eqBranch if descriptor of two objects is the same.
br_on_cast_desc_eq_failBranch if descriptors differ.
Source: custom-descriptors/Overview.md binary format table.

browser support

ChromeOrigin trial in Chrome 149 (requires WasmGC, Chrome 119+)
EdgeTracking Chromium
FirefoxNo signal (as of Chrome 149)
SafariNo signal (as of Chrome 149)
Source: chromestatus browser views, May 2026.

see also