Stripping Sections from a Wasm Binary

This page answers one task: a release .wasm file is larger than expected, and inspection shows sections that do not affect execution — function names, debug information, toolchain metadata. You want to remove what users do not need, keep what you need for debugging production problems, and avoid removing anything that breaks the module.

Prerequisites

  • [ ] The release module and a way to rebuild it.
  • [ ] wasm-objdump -h or wasm-tools objdump to list sections, and at least one stripping tool.
  • [ ] A plan for production debugging (symbol storage for stack traces).

Standard sections versus custom sections

A module’s known sections — type, import, function, table, memory, global, export, start, element, data count, code, data — define its behaviour and must not be removed. Custom sections (id 0) carry a name and arbitrary bytes, which engines ignore for execution (with a few exceptions that change behaviour in toolchains, not engines). Toolchains emit several kinds:

  • name — function, local and other names. Makes stack traces and profiles readable; often 5–20% of an unstripped module.
  • DWARF sections (.debug_info, .debug_line, …) — full source-level debug information; can exceed the size of the code itself.
  • producers — which languages and tools built the module; small.
  • target_features — features the object was compiled with; used by linkers, not engines; small.
  • sourceMappingURL and external_debug_info — pointers to separate debug files.
  • Toolchain-specific sections such as dylink.0 (dynamic linking metadata) or component-related sections — which are required by their consumers.
Common custom sections and whether to strip them DWARF debug sections are large and only needed by debuggers, so strip them from releases and keep them separately. The name section helps stack traces and is moderately sized; strip and keep a symbol map, or keep it if size allows. Producers and target features are small metadata. dylink.0 and component sections are required by their loaders and must be kept. section typical size needed at runtime? release decision .debug_* (DWARF) very large no strip; archive separately name moderate no (helps traces) strip + keep map or keep producers tiny no strip or keep target_features tiny no strip dylink.0 / component sections small yes for loaders keep

Step 1 — see what is there

wasm-objdump -h app.wasm
#   Custom start=... size=0x0005e3f0 ".debug_info"
#   Custom start=... size=0x0001a2c4 "name"
#   Custom start=... size=0x00000043 "producers"
wasm-tools objdump app.wasm      # alternative listing with sizes

Add up custom section sizes to see what stripping can save.

Step 2 — strip with the right tool

wasm-strip app.wasm                                # WABT: removes all custom sections in place
wasm-opt -O3 --strip-debug --strip-producers app.wasm -o app.min.wasm   # Binaryen: selective
wasm-tools strip app.wasm -o app.min.wasm          # removes custom sections; options to keep some

wasm-opt --strip-debug removes DWARF and the name section; --strip-producers removes the producers section. In Rust, strip = true (or "symbols") in the Cargo release profile removes names and debug info at link time; wasm-pack and Emscripten have their own options (Emscripten removes most custom sections at -O2+ unless debug flags are given). Know what your pipeline already does before adding another step.

Step 3 — keep what you need for debugging

Stripping names makes production stack traces show wasm-function[1234] instead of function names. Before stripping, save what you need: keep the unstripped module (or just its name section) in your artefact storage, keyed by the stripped module’s content hash, and use it to symbolicate reported stack traces. For full source-level debugging of production builds, emit DWARF into a separate file and reference it with an external_debug_info section, which DevTools can follow when you have access to the file.

Stripping while keeping debuggability Build with names and debug info. Archive the unstripped module or its debug sections keyed by the release module's content hash. Strip custom sections for the shipped file. When a production trace with function indices arrives, look up the archive by hash and map indices back to names and source lines. build with names + DWARF full artefact archive debug data keyed by release hash strip for release shipped .wasm trace arrives function indices symbolicate names + lines

Step 4 — do not strip required sections

Some custom sections are part of a contract with a loader rather than metadata: dylink.0 in dynamically linked side modules tells the loader how much memory and table space the module needs; component-model binaries use custom sections during componentisation (wit-bindgen’s component type sections in core modules before wasm-tools component new); some toolchains read target_features during linking. Strip only final artefacts, after linking and componentisation, and test the stripped file exactly as shipped.

Step 5 — measure the result

Compare raw and compressed sizes before and after. DWARF often dominates raw size but compresses well; the name section is moderate either way. Report the compressed difference, which is what users download.

Names: strip or keep?

Keeping the name section costs some kilobytes compressed and makes every production stack trace, profile and error report readable without extra infrastructure. Stripping it saves those kilobytes but requires a symbolication pipeline. For small teams without such a pipeline, keeping names is often the better trade; for large modules where every kilobyte matters, strip and symbolicate.

Splitting DWARF into a separate file

Rather than choosing between full debug information and a small release file, ship both: the small file to users, the debug information to developers only. Emscripten supports this with -gseparate-dwarf, which writes DWARF to a separate .debug.wasm file and adds an external_debug_info custom section to the main module pointing at it; Chrome’s C/C++ DevTools extension follows that pointer when the file is reachable (for example from an internal server or a local path mapping). For other toolchains, wasm-opt and wasm-tools can split or extract sections, and you can archive the original unstripped module as the debug companion. Keep the debug file’s URL private if the source structure is sensitive: DWARF contains file paths, function names, variable names and types, which is more than many teams want public.

Automating the policy in the build

Decide the policy once and encode it: which sections the shipped module keeps, which are archived, and where. Then enforce it in CI with a check that lists the shipped module’s sections and fails if anything unexpected is present — DWARF sneaking into a release because someone changed a profile, or a required section like dylink.0 missing from a side module. Archive the debug artefacts with the release’s content hash as the key so error-reporting tools can fetch them automatically, and test symbolication with a deliberately triggered trap in a staging build, so you know the pipeline works before you need it.

Privacy and intellectual property

Names and debug information reveal how code is organised. For most open-source or client-side code that is harmless — the logic ships anyway — but some teams prefer not to publish internal names. Stripping the name section removes the easy view; it does not make the code secret, since the instructions remain and can be decompiled. Treat stripping as a size and tidiness measure, not as protection.

Custom sections you add yourself

Teams sometimes embed their own metadata — a build ID, a licence notice, a feature list. Keep such sections small and stable, and remember that blanket stripping tools remove them too; use selective stripping if they must survive.

Verifying a stripped module behaves identically

Stripping should never change behaviour, and a quick check proves it: run the test suite against both the unstripped and stripped files, and compare a hash of the non-custom sections of both files, which must be identical. Any difference means a tool did more than strip.

Expected output

The release module drops from 3.8 MB to 1.1 MB raw by removing DWARF and names (from 690 KB to 520 KB with Brotli); the unstripped artefact is archived by content hash for symbolication; dylink.0 is kept in side modules; and the shipped file passes the same tests as the unstripped build.

Gotchas

  • Stripping before linking or componentisation. Required metadata disappears. Strip final artefacts.
  • Losing symbols. Production traces become indices. Archive unstripped builds.
  • Double stripping steps. Pipelines already strip. Check before adding tools.
  • Removing dylink.0. Side modules fail to load. Keep loader sections.
  • Measuring only raw size. Users download compressed bytes.
  • Blanket stripping of your own metadata. Build IDs disappear too. Strip selectively when needed.

Performance note

Stripping does not change execution speed, but smaller files download and compile slightly faster; removing 2.7 MB of DWARF cut download time on a 10 Mbit/s link by about 0.15 s after compression.

Module size before and after stripping Megabytes of a release module with DWARF and names, with DWARF stripped, and with DWARF, names and producers stripped, shown as raw sizes. MB (raw) with DWARF + names 3.8 MB DWARF stripped 1.3 MB all custom sections stripped 1.1 MB

Frequently Asked Questions

Do engines use the name section? Only for diagnostics — stack traces and profiles show names when present.

Is stripping reversible? No — keep the unstripped artefact if you might need it.

Does stripping affect WebAssembly.Module.customSections? Yes — stripped sections are no longer available to code that reads them.

Should I strip in development builds? No — names and debug info make debugging possible.

Can I ship a small module but keep source-level debugging? Yes — split DWARF into a separate file referenced by external_debug_info and keep that file private.

Does stripping protect my code? No — instructions remain and can be decompiled; stripping saves size and hides names only.

How do I make sure releases never ship DWARF? List the shipped module’s sections in CI and fail on anything outside the agreed policy.

Where should debug artefacts be stored? In private artefact storage keyed by the release module’s content hash, so error tools can fetch them.

How do I prove stripping changed nothing functional? Run the tests on both files and compare a hash of their non-custom sections, which must match.

← Back to Wasm Binary Format Deep Dive