Pass Strings Between JavaScript and WebAssembly
WebAssembly (Wasm) only natively supports numeric data types, making
the transfer of strings between JavaScript and Wasm a unique challenge.
This article explains how to pass string data back and forth by
leveraging WebAssembly’s shared linear memory, manually encoding and
decoding strings using TextEncoder and
TextDecoder, and utilizing modern tooling to simplify the
process.
The Core Concept: Shared Linear Memory
Because WebAssembly cannot directly receive or return JavaScript
strings, both environments must communicate through Wasm’s linear
memory. This memory is represented as a raw ArrayBuffer
that JavaScript can read and write to. Passing a string requires
converting the characters into raw bytes (usually UTF-8), writing those
bytes to memory, and passing the memory address (pointer) and length
between JavaScript and WebAssembly.
1. Passing Strings from JavaScript to WebAssembly
To send a JavaScript string to a WebAssembly module, you must manually allocate memory inside the Wasm instance, write the string bytes to that space, and then pass the memory pointer to your Wasm function.
Step A: Encode the String
Use the browser’s built-in TextEncoder to convert the
JavaScript UTF-16 string into a UTF-8 byte array
(Uint8Array).
const encoder = new TextEncoder();
const encodedString = encoder.encode("Hello from JS!");Step B: Allocate Memory in WebAssembly
Your WebAssembly module must export an allocation function (often
named malloc or alloc) that reserves space in
its linear memory and returns a pointer (index).
const length = encodedString.length;
const pointer = wasmModule.instance.exports.alloc(length);Step C: Copy Bytes into Wasm Memory
Access the raw memory buffer of the Wasm instance, create a
Uint8Array view starting at the allocated pointer, and copy
your encoded string into it.
const memoryBuffer = new Uint8Array(wasmModule.instance.exports.memory.buffer);
memoryBuffer.set(encodedString, pointer);Step D: Call the Wasm Function
Now, pass the pointer and the byte length as numeric arguments to your WebAssembly function.
wasmModule.instance.exports.process_string(pointer, length);2. Passing Strings from WebAssembly to JavaScript
To retrieve a string generated or modified by WebAssembly, the Wasm function must return a pointer to the memory location of the string, along with its length.
Step A: Receive Pointer and Length
Call your WebAssembly function. It should return either a pointer, or write the pointer and length to a known memory address. For this example, let’s assume the Wasm function returns the pointer directly, and we know the length, or the function returns the length.
const pointer = wasmModule.instance.exports.get_string_pointer();
const length = wasmModule.instance.exports.get_string_length();Step B: Read and Decode the Memory
Create a temporary view of the Wasm linear memory using the returned
pointer and length. Then, use TextDecoder to convert those
bytes back into a JavaScript string.
const memoryBuffer = new Uint8Array(wasmModule.instance.exports.memory.buffer);
const stringBytes = memoryBuffer.subarray(pointer, pointer + length);
const decoder = new TextDecoder("utf-8");
const jsString = decoder.decode(stringBytes);
console.log(jsString); // Outputs the string from WebAssemblySimplifying the Process with Tooling
While manually managing pointers and byte buffers is highly performant, it is prone to memory leaks if allocated memory is not freed. Most developers use high-level tooling to automate this glue code:
- Rust (
wasm-bindgen): If you are using Rust,wasm-bindgenautomatically generates the JavaScript wrapper code. You can pass RustStringor&strtypes directly in your function signatures, and the compiler handles the encoding, decoding, and memory allocation behind the scenes. - C/C++ (Emscripten): Emscripten provides helper
functions like
stringToUTF8andUTF8ToStringto automatically handle string conversions in the compiled JavaScript output.