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 module wrapper
Section titled “The module wrapper”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.
Declaring a function
Section titled “Declaring a function”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.
Exporting functions
Section titled “Exporting functions”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.
Run it
Section titled “Run it”The example below defines a multiply function using the inline export syntax and calls it from JavaScript.
Trade-offs
Section titled “Trade-offs”| Option | Benefit | Cost |
|---|---|---|
| Writing WAT by hand | You see the (module ...) and (func ...) structure directly, and understand exactly how exports work at the binary-format level | You 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 codebases | Reading the resulting .wasm directly is hard; you need a tool to convert it back to WAT to debug |
Common mistakes
Section titled “Common mistakes”- Declaring
(result i32)but ending the function body without exactly onei32left 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
funcbody, producing a wrong exported result even though the syntax is completely valid - Using
br/br_ifto jump out of a block inside a function without considering what the branch’s target expects on the stack, so thefuncsignature and the actual stack state don’t match
💡 Real-world example
Teams debugging a production
.wasmon any Wasm-hosting platform commonly usewasm2watto turn the binary module back into readable WAT, to verify that an exported function’s signature and body actually match what was intended.