Skip to content

Passing Strings

WebAssembly functions only accept and return numeric types — integers and floats. There is no string type. The standard workaround is to encode string data as bytes in linear memory and pass integer coordinates (a pointer and a length) to identify where those bytes live.

The round-trip from JavaScript string to Wasm and back has five steps:

  1. Encode the string to a Uint8Array of UTF-8 bytes using TextEncoder
  2. Write those bytes into a known region of Wasm memory
  3. Call the Wasm function with the byte offset (pointer) and the byte count (length)
  4. Wasm does its work and returns the pointer and length of the result bytes
  5. Decode the result bytes back to a JavaScript string using TextDecoder
flowchart LR
  A["JS string\n'Hello'"] --> B["TextEncoder\n-> Uint8Array"]
  B --> C["mem.buffer\n(write at ptr)"]
  C --> D["Wasm function\n(ptr, len)"]
  D --> C
  C --> E["TextDecoder\n-> JS string"]
String round-trip through linear memory

TextEncoder.encode(str) converts a JavaScript string to a Uint8Array of UTF-8 bytes. Multi-byte characters (emoji, accented letters, CJK) produce more bytes than characters, so always use the byte length for memory operations, not the string length.

Pick an address in Wasm memory that you control (for example, a static region starting at byte 256) and write the encoded bytes there:

const encoded = new TextEncoder().encode(str);
const ptr = 256;
new Uint8Array(mem.buffer).set(encoded, ptr);

After calling the Wasm function you get back (ptr, len). Slice the memory view and decode:

const decoded = new TextDecoder().decode(
new Uint8Array(mem.buffer, outPtr, outLen)
);

The three-argument Uint8Array constructor creates a view starting at outPtr with outLen elements — no copy, no allocation.

The Wasm module below exposes an echo function that simply returns the same (ptr, len) pair it receives. The JavaScript side encodes "Hello Wasm", writes it at offset 256, calls echo, then decodes the result.

WebAssembly
OptionBenefitCost
Send (ptr, len) through linear memory instead of a string directlyZero-copy byte read/write, no re-serializing across the host boundaryYou have to encode/decode UTF-8 yourself and compute the byte length correctly every time
A fixed address for writing strings (manual layout)Simple, no allocator or extra runtime neededNo GC to warn you when a new string is longer than the reserved space — you must budget the buffer size yourself
  • Using str.length (character count) instead of encoded.length (the real byte count after UTF-8 encoding) when computing the length to pass to Wasm, truncating or mis-decoding multi-byte characters like emoji or Thai text
  • Writing bytes at a fixed offset (e.g. 256) without leaving room for the string to grow, letting new data overwrite another region still in use
  • Reusing a cached typed-array view across calls and forgetting that a memory.grow in between detaches the old view, causing the string to be written or read at the wrong location

💡 Real-world example

FFmpeg.wasm passes filenames and metadata strings through this same (ptr, len) shape across linear memory to drive a decoder compiled from C, avoiding a slower string-conversion glue layer.

How do you pass a JavaScript string to a WebAssembly function?
What does TextEncoder.encode(str) return?
What is the standard Wasm convention for representing a string?