Using Global Imports and Exports
This page answers one task: you want to pass a few scalar values into a WebAssembly module at instantiation — a feature flag, a buffer size, a base address — or
read values the module publishes, or share a counter between several instances. WebAssembly.Global objects do exactly that, and you want to declare, create
and use them correctly.
Prerequisites
- [ ] A module you can edit in WAT, or a toolchain that can import and export globals.
- [ ] Familiarity with import objects and
instance.exports. - [ ] Node.js or a browser to run the examples.
What Wasm globals are
A global is a single typed value — i32, i64, f32, f64, v128 (inside Wasm only), funcref or externref — that lives outside linear memory. A module
can define globals, import them, and export them. Each global is either immutable (const, the default) or mutable (mut). In JavaScript, imported and
exported globals are WebAssembly.Global objects with a value property, and immutable globals can also be imported as plain numbers.
Globals are fast — global.get compiles to a register or memory load — and clearly typed, which makes them better than memory for a handful of configuration
values and better than a function call for values read in hot loops.
Step 1 — import an immutable configuration value
(module
(import "env" "max_items" (global $max i32))
(func (export "clamp") (param $n i32) (result i32)
(select (local.get $n) (global.get $max)
(i32.lt_u (local.get $n) (global.get $max)))))
const { instance } = await WebAssembly.instantiate(bytes, { env: { max_items: 100 } });
instance.exports.clamp(250); // 100
An immutable i32, f32 or f64 global can be imported as a plain JavaScript number; the engine wraps it. For i64, pass a BigInt (100n). Immutable
imported globals can also appear in constant expressions — data segment offsets, element segment offsets and other globals’ initialisers — which is how
position-independent modules receive __memory_base and __table_base.
Step 2 — share a mutable global
(import "env" "counter" (global $counter (mut i32)))
(func (export "tick")
(global.set $counter (i32.add (global.get $counter) (i32.const 1))))
const counter = new WebAssembly.Global({ value: "i32", mutable: true }, 0);
const a = await WebAssembly.instantiate(bytes, { env: { counter } });
const b = await WebAssembly.instantiate(bytes, { env: { counter } });
a.instance.exports.tick(); b.instance.exports.tick();
console.log(counter.value); // 2
Mutable globals must be imported as WebAssembly.Global objects, not numbers, and the declared mutability and type must match exactly — importing a mutable
global where the module declares an immutable one (or vice versa) is a LinkError. All instances importing the object see each other’s writes immediately.
Step 3 — read exported globals
(global $version (export "version") i32 (i32.const 3))
(global $heap_base (export "__heap_base") i32 (i32.const 66560))
console.log(instance.exports.version.value); // 3
console.log(instance.exports.__heap_base.value); // 66560
Toolchains export useful globals: wasm-ld can export __heap_base and __data_end (where static data ends and the heap may start) with
--export=__heap_base, and __stack_pointer is a mutable global the compiled code uses for its shadow stack. Reading these from JavaScript helps when writing a
custom allocator or inspecting memory layout; writing __stack_pointer from JavaScript is almost always a mistake.
Step 4 — use globals in toolchain code
In Rust and C, Wasm globals are not ordinary language-level variables — those live in linear memory. Clang has only experimental support for declaring Wasm globals directly, so from C or Rust the dependable route is an imported function or a value written into memory. In practice, most applications use globals indirectly: toolchains create them for the stack pointer, memory base and TLS base, while application configuration is passed through imported functions or written into memory. Hand-written WAT and code generators are where globals shine as an explicit interface.
Step 5 — choose globals, functions or memory
Use globals for a few scalars that are set at instantiation or shared as simple counters. Use imported functions when the value is computed lazily or comes from a JavaScript object. Use memory when you pass structured data — arrays, strings, records. Globals cannot be indexed, so a hundred configuration values belong in memory, not a hundred globals.
Globals across threads
A WebAssembly.Global cannot be shared between workers: posting one clones its current value, not the global. Each thread’s instance has its own globals,
which is why __stack_pointer and the thread-local storage base work per thread. Shared counters across threads belong in shared memory with atomic
instructions.
Verifying the examples
The WAT above assembles with wat2wasm and behaves as described in Node.js. A combined module that imports max_items and counter and exports version,
tick and clamp gives:
const counter = new WebAssembly.Global({ value: "i32", mutable: true }, 0);
const a = await WebAssembly.instantiate(bytes, { env: { max_items: 100, counter } });
const b = await WebAssembly.instantiate(bytes, { env: { max_items: 100, counter } });
a.instance.exports.clamp(250); // 100
a.instance.exports.clamp(5); // 5
a.instance.exports.tick(); b.instance.exports.tick();
counter.value; // 2
a.instance.exports.version.value; // 3
await WebAssembly.instantiate(bytes, { env: { max_items: 100, counter: 5 } });
// LinkError: Import #1 "env" "counter": imported mutable global must be a WebAssembly.Global object
The error message names the import index, module and field, so a wrong import is easy to find even in modules with dozens of imports.
Globals as feature flags
A common use of immutable imported globals is selecting behaviour at instantiation without rebuilding: a debug flag that enables extra checks, a
simd_enabled flag that picks a code path, or a log_level. Because the value is constant for the instance’s lifetime, optimising tiers treat
global.get of an immutable import as a constant and remove the untaken branch, so the check costs nothing in hot code. Different instances of the same compiled
module can receive different flags — a debug instance and a release instance share compiled code but behave differently.
Externref globals
With reference types, a global can hold any JavaScript value: new WebAssembly.Global({ value: "externref", mutable: true }, someObject). Wasm code cannot
inspect the value, but it can pass it to imported functions, which makes an externref global a convenient way to give a module a handle — a canvas context, a
database connection, a logger — without managing a table. Reading .value in JavaScript returns the original object.
Inspecting globals while debugging
Browser DevTools show a module’s globals in the scope panel when paused in Wasm code, alongside locals and memory. From JavaScript, iterate
WebAssembly.Module.exports(module) and log the .value of every export whose kind is "global" to get a quick snapshot of a running instance’s published
state — handy for watching __stack_pointer during deep recursion or confirming a configuration value was received.
Naming and documenting the interface
Imported globals are part of a module’s contract with its host, just like imported functions. Give them descriptive field names under a consistent module name
(config.max_items, config.log_level), keep their types and mutability stable across releases, and document defaults next to the loader code that supplies
them. A small table in the repository — name, type, mutability, meaning — prevents the slow drift where JavaScript and Wasm disagree about what a value means.
Expected output
A module receives max_items as a plain number and clamps inputs to it; two instances share a mutable counter that reads 2 after each ticks once; JavaScript reads
exported version and __heap_base; and a mutability mismatch fails with a clear LinkError.
Gotchas
- Passing a number for a mutable import. LinkError. Use a
WebAssembly.Global. - Mismatched mutability or type. LinkError. Declare exactly the same.
- Plain numbers for i64. Use BigInt.
- Writing toolchain globals.
__stack_pointerbelongs to compiled code. Read only. - Expecting globals to cross threads. They are per instance per thread.
- Hundreds of configuration globals. Unwieldy and unindexable. Put bulk configuration in memory.
Performance note
In a loop that read a configuration value on every iteration, global.get of an imported immutable global ran as fast as a constant after tier-up, while calling
an imported JavaScript function for the same value was about 15× slower.
Frequently Asked Questions
Can I change an immutable global after instantiation? No — create it mutable if the value must change.
Can a global hold a JavaScript object? Yes — with type externref, supported in all current browsers.
Are exported globals live?
Yes — .value always reads the current value.
Do globals count toward memory size? No — they live outside linear memory.
Does reading an immutable global cost anything in optimised code? Usually not — optimising tiers fold it into a constant and remove dead branches.
Can two instances get different values for the same imported global? Yes — each import object supplies its own value, while compiled code is shared.
Should configuration globals have their own import module name?
Yes — a dedicated name such as config keeps them separate from functions under env.
What happens if I forget to supply an imported global? Instantiation fails with a LinkError naming the missing module and field.
Related
- Writing an import object by hand — import objects.
- Reflecting on module imports and exports — discovering globals.
- Exporting memory and globals from WAT — the WAT side.
- Passing a table at instantiation — the table equivalent.
← Back to Wasm Instantiation Lifecycle