Skip to content

The Imports Object

Not every WebAssembly module is self-contained. When a module needs to call a JavaScript function, read from shared memory, or access a global value managed by the host, it declares those dependencies with (import ...) statements. At instantiation time, JavaScript must supply matching values — this is the imports object.

An import declaration names the thing being imported and describes its type. The first two string arguments form a two-level namespace: a module name (the outer key) and a field name (the inner key):

(module
(import "env" "log" (func $log (param i32)))
)

Here "env" is the module namespace and "log" is the field name. The declaration says: “this module requires a function called log inside the env namespace; that function must accept one i32 argument.”

The imports object passed to WebAssembly.instantiate() mirrors that two-level namespace — the outer key is the module name and the inner key is the field name:

const importObject = {
env: {
log: (n) => console.log(n)
}
};

Each inner value must match the type declared in the WAT. A function import needs a JS function; a memory import needs a WebAssembly.Memory; a global import needs a WebAssembly.Global.

Here is a complete WAT module that imports the log function and calls it from an exported run function:

(module
(import "env" "log" (func $log (param i32)))
(func (export "run")
i32.const 42
call $log))

The run function pushes the constant 42 onto the stack, then calls $log — which consumes that value and passes it to whatever JS function was provided in the imports object.

On the JavaScript side:

const { instance } = await WebAssembly.instantiate(bytes, {
env: { log: (n) => console.log('Wasm says:', n) }
});
instance.exports.run(); // prints: Wasm says: 42
WebAssembly

Functions are the most common import, but the same mechanism works for memory, globals, and tables:

// Import a shared Memory
const memory = new WebAssembly.Memory({ initial: 1 });
const importObject = {
env: {
memory,
log: (n) => console.log(n)
}
};

A WebAssembly.Memory import lets JavaScript and Wasm share the same ArrayBuffer, making it possible to pass strings, arrays, and other structured data back and forth by reading and writing raw bytes. Globals and tables follow the same pattern — declare them in WAT with (import ...) and supply the matching WebAssembly.Global or WebAssembly.Table instance in the imports object.

OptionBenefitCost
instantiateStreaming + imports objectImports can be supplied as the stream compile starts, cutting latency before imported code is first calledThe server must respond with Content-Type: application/wasm, or it rejects with a TypeError before imports are even checked
instantiate on a full bufferIgnores the server’s MIME type entirely, so it always worksHas to wait for the full download first, so imported host functions are ready later
  • A field name or capitalization in the imports object that doesn’t exactly match the (import ...) declaration — e.g. Log instead of log — causing a LinkError
  • Passing a large string or array to an imported function one argument at a time, instead of writing it into shared memory once and passing just a pointer/length
  • Forgetting to use BigInt when an imported function must receive an i64 parameter from the Wasm side, so the JS callback receives an unexpected value

💡 Real-world example

Applications that port a large existing native engine (CAD tools, game engines, physics simulators) to run in the browser typically import dozens of host functions — for rendering, input handling, and file I/O — to bridge the ported engine to browser APIs.

In `(import "env" "log" ...)`, which is the module namespace?
What is the shape of the imports object for `(import "env" "log" (func ...))`?
What error is thrown when the imports object is missing a required import?