Skip to content

Modules and Functions

Every piece of WebAssembly code lives inside a module. The module is the top-level unit of compilation and deployment — it is what you compile, transfer over the network, and instantiate in the browser. Inside a module you define functions, declare memory, import from JavaScript, and choose what to export.

The outermost s-expression in every WAT file is (module ...). Everything else is nested inside it:

(module
;; functions, imports, exports, memory, globals go here
)

A module with no contents is valid WAT and compiles to a tiny .wasm file — it just has nothing useful in it yet.

Functions are declared with (func ...). A function that takes no arguments and returns nothing looks like this:

(module
(func
;; empty body — does nothing
)
)

To accept arguments you add (param <name> <type>) attributes. To declare a return type you add (result <type>). Parameter names start with $ and are optional but strongly recommended for readability:

(module
(func (param $x i32) (param $y i32) (result i32)
local.get $x
local.get $y
i32.mul)
)

This module compiles fine, but the function is completely unreachable from JavaScript because it has not been exported.

JavaScript can only call functions the module explicitly exports. There are two equivalent syntaxes.

Separate export statement — define the function with a name, then export it:

(module
(func $multiply (param $x i32) (param $y i32) (result i32)
local.get $x
local.get $y
i32.mul)
(export "multiply" (func $multiply))
)

Inline export — attach the export directly to the func declaration:

(module
(func (export "multiply") (param $x i32) (param $y i32) (result i32)
local.get $x
local.get $y
i32.mul)
)

Both forms produce identical .wasm bytes. The inline style is more concise; the separate-statement style is useful when you want to export the same function under multiple names or keep declarations and exports visually separated.

The example below defines a multiply function using the inline export syntax and calls it from JavaScript.

WebAssembly
OptionBenefitCost
Writing WAT by handYou see the (module ...) and (func ...) structure directly, and understand exactly how exports work at the binary-format levelYou write all the boilerplate yourself, with no high-level type system checking it before compiling
Letting a compiler generate Wasm (Rust/C/AssemblyScript)Memory layout and imports/exports are managed automatically — well suited to large codebasesReading the resulting .wasm directly is hard; you need a tool to convert it back to WAT to debug
  • Declaring (result i32) but ending the function body without exactly one i32 left on the stack — e.g. branching out midway without pushing a return value — so the module fails to validate
  • Pushing operands in the wrong order before an instruction inside a func body, producing a wrong exported result even though the syntax is completely valid
  • Using br/br_if to jump out of a block inside a function without considering what the branch’s target expects on the stack, so the func signature and the actual stack state don’t match

💡 Real-world example

Teams debugging a production .wasm on any Wasm-hosting platform commonly use wasm2wat to turn the binary module back into readable WAT, to verify that an exported function’s signature and body actually match what was intended.

Which syntax exports a function from a WAT module?
Can JavaScript call a function that is defined but not exported?
What is the outermost s-expression in every WAT file?