PEP 816: Python Finally Gets Serious About WebAssembly

CPython has had informal WebAssembly support since Python 3.11. PEP 816 changes that. Brett Cannon just locked down exactly which WASI and SDK versions each Python release will target — and why SDK versions 26 and 27 got blacklisted entirely.

WebAssembly support in CPython has existed since Python 3.11. It worked. People used it. But nobody wrote down the rules.

The WASI SDK version each Python release targeted was informal. There was no guarantee about what WASI spec version you were getting. Extension module maintainers trying to ship Wasm-compatible builds had to reverse-engineer which SDK version CPython was built against for each release. And the whole thing quietly broke twice when SDK versions 26 and 27 shipped with a bug that caused CPython to hang on REPL exit.

PEP 816, authored by Brett Cannon and now marked Active, fixes that. It formalizes how CPython targets WASI and the WASI SDK going forward. If you ship Python in a browser, on a serverless edge, in a plugin sandbox, or anywhere else Wasm runs, this PEP changes your life.

The Fundamental Problem With Python and Wasm

To understand what PEP 816 actually solves, you need to understand something unusual about how Python runs in Wasm at all.

Most languages that target WebAssembly compile your code directly to a .wasm binary. You write Rust, you get a Wasm module. The module is self-contained and portable. If you want to understand how these virtual machines work under the hood, we built one from scratch in C -- the fundamentals carry over.

Python doesn't work that way. CPython is an interpreter. Your .py files don't compile to machine code -- the interpreter reads and executes them at runtime. So to run Python in Wasm, you don't compile your Python program to Wasm. You compile the interpreter itself to Wasm, and then your Python program runs on that Wasm interpreter.

That has two immediate consequences.

Every Python program running in Wasm carries the full weight of the CPython interpreter and the standard library. A "hello world" in Wasm Python is not a 10kb binary. It pulls in megabytes of runtime before your first print() executes.

Second: any module that isn't pure Python breaks. NumPy, for example, ships C extension modules. Unless someone has built a Wasm-specific version of those C extensions against the WASI SDK, the module simply doesn't load. This is why Python's standard library has a documented list of modules that aren't available in Wasm environments -- the WASI SDK doesn't expose the system interfaces those modules need.

The more Python and the broader ecosystem demand those interfaces, the more likely the Wasm runtimes are to add them. PEP 816 makes Python a more credible participant in those negotiations by giving the community explicit version targets to build against.

WASI vs. the WASI SDK

This trips people up, so let's be precise.

WASI (the WebAssembly System Interface) is a specification. It defines the ABIs that Wasm programs use to talk to the host system: file I/O, network sockets, clocks, randomness sources. WASI is a spec, not code. The Bytecode Alliance drives its development, with roughly one new version per year.

The WASI SDK is an implementation. It's a modified Clang compiler with wasi-libc as the sysroot. If you want to compile CPython to Wasm, you use the WASI SDK to do it. wasi-libc is the C standard library implementation for WASI, and it provides the bridge between C-level system calls and WASI's host APIs.

The distinction matters because they have different versioning and compatibility guarantees.

WASI versions are backwards-compatible. WASI 0.2 programs can still run on a WASI 0.3 runtime (with some caveats around new capabilities). The WASI SDK is not. wasi-libc makes zero ABI compatibility guarantees between major SDK versions. Code compiled against SDK 24 will not necessarily work with SDK 25. This is the foundational reason PEP 816 exists – the ecosystem needs a coordination point.

Why SDK 26 and 27 Got Skipped Entirely

This is the part that got my attention.

WASI SDK versions 26 and 27 shipped with a bug in the thread support code that causes CPython to hang. The hang can happen during normal REPL exit -- not some exotic edge case, but the thing you do literally every time you finish a Python session.

CPython 3.13 and 3.14 both use WASI SDK 24. The project skipped 26 and 27. Without PEP 816, you'd have to dig through CPython's build configuration to find that. With PEP 816, the designated support table is the official record, and changes to it go into PEP 11 with explicit notes explaining why.

The other skip is WASI 0.2. The CPython team evaluated the spec version timeline and decided – on advice from the Bytecode Alliance – that it made more sense to wait for WASI 0.3 than to implement 0.2. WASI 0.3.1 is scheduled to include some form of threading support, which matters enormously for Python. Until WASI has threading, CPython can't support free-threading in Wasm builds. No threading support in the spec means no free-threading in the interpreter, full stop.

The Current Version Map

This is what PEP 816 records officially:

Python WASI WASI SDK
3.11 0.1 21
3.12 0.1 21
3.13 0.1 24
3.14 0.1 24

Everything before 3.15 predates this PEP. Those rows are reconstructed from what was actually used at the time. Python 3.15 will be the first release where the WASI version gets locked at beta 1 as an explicit policy decision.

On tier support: WASI was a Tier 3 platform for 3.11 and 3.12. It became Tier 2 starting with 3.13. That matters for bug prioritization – Tier 2 means WASI breakage is a release blocker, not a "we'll get to it" item.

What the New Rules Actually Say

PEP 816's specification section is short. Two rules.

Rule one: Whatever WASI version and WASI SDK version CPython's CI and stable Buildbot builder use at the beta 1 release, that's the version supported for the lifetime of that Python release. No mid-cycle upgrades. No surprises when you go to compile your extension against a new point release.

Rule two: Changing the WASI spec version after it's been recorded requires steering council approval. Changing the WASI SDK version is at the maintainers' discretion but explicitly should not happen outside of genuine emergencies (like the SDK 26/27 hang bug scenario).

The split is intentional. SDK version bumps are a lower-stakes coordination issue – they affect build tooling. WASI spec version bumps change what the runtime supports, which changes what stdlib modules work, which changes what user code can rely on. The steering council gate on spec version changes reflects how much user-facing impact those carry.

Building CPython for WASI Today

If you want to run Python in a Wasm environment right now, the build process looks like this. You need the WASI SDK installed (version 24 for Python 3.13/3.14) and a Wasm runtime like wasmtime.

# Source: CPython configure/Makefile.pre.in
# https://github.com/python/cpython/blob/main/Tools/wasm/wasm_build.py
 
# Set up the WASI SDK path
export WASI_SDK_PATH=/opt/wasi-sdk
 
# Configure CPython for the wasm32-wasi target
./configure \
  --host=wasm32-wasi \
  --build=$(./config.guess) \
  --with-build-python=$(which python3) \
  CC="${WASI_SDK_PATH}/bin/clang" \
  CPP="${WASI_SDK_PATH}/bin/clang-cpp" \
  AR="${WASI_SDK_PATH}/bin/llvm-ar" \
  RANLIB="${WASI_SDK_PATH}/bin/llvm-ranlib" \
  "CFLAGS=-isysroot ${WASI_SDK_PATH}/share/wasi-sysroot"
 
make -j$(nproc)

The --host=wasm32-wasi flag tells the configure script you're cross-compiling for the wasip1 target. --build points at your host machine's native toolchain -- you need a native Python build to bootstrap the interpreter before the cross-compiled version exists. The CC and related flags route everything through the WASI SDK's Clang instead of your system compiler.

Once built, you run your Python script through wasmtime:

# Run a Python script in the WASI runtime
wasmtime run \
  --dir /path/to/your/scripts \
  python.wasm \
  -- /path/to/your/scripts/hello.py

The --dir flag grants the Wasm module access to a host directory. WASI's security model is capability-based – the module can only touch filesystems you explicitly grant it. This is one of Wasm's genuinely useful security properties, and it's one reason plugin sandboxes and serverless runtimes are interested in Python-on-Wasm as an option.

What This Means for Library Authors

If you maintain a Python library with C extension modules and you want to support Wasm, PEP 816 gives you the targeting information you previously had to infer.

For Python 3.15 and later releases, you'll know by beta 1 exactly which WASI SDK version to build your Wasm-specific extension against. You won't have to wait until the final release and reverse-engineer it. You can start Wasm CI work at beta 1 and have it ready by the time the final Python release ships.

For pure-Python libraries, this PEP doesn't change your workload. Pure Python already works in Wasm as long as you stay within the subset of stdlib that WASI exposes. The modules missing from Wasm environments are largely the ones that need OS-level threading, raw sockets, or subprocess spawning -- things WASI 0.1 doesn't support. WASI 0.3 threading support changes the threading situation. Networking capabilities in WASI 0.2 are why the Python team is skipping to 0.3 instead of implementing 0.2 -- they want socket support, and WASI 0.2 had it, but the timeline favored jumping ahead.

For Django and Flask users wondering if their web apps can run in Wasm: not in any production-relevant way yet. WASI 0.1 has no socket support, which means no inbound HTTP. Once WASI 0.3 lands with real networking, that calculus changes.

The Bigger Picture

PEP 816 is administrative work. No new features, no exciting new syntax, no performance gains you can benchmark. Brett Cannon wrote down a policy that CPython was already trying to follow, got it formally adopted, and now future releases have a published contract with the ecosystem.

That kind of work is what makes a platform reliable rather than just capable. Python being able to run in Wasm has been true since 3.11. Python being something that library authors, serverless platform builders, and browser-based notebook developers can build on confidently requires knowing that the WASI SDK under their builds won't shift mid-release-cycle.

The async/await work that made Python viable for high-throughput I/O was also largely invisible to most users until suddenly it wasn't -- and every Discord bot, every FastAPI service, and every async data pipeline depended on it. WASI support is following the same arc. The policy work happens first. The interesting applications show up later, once people can actually depend on it.

Python 3.15 will be the first release built under these rules from day one. By the time WASI 0.3 threading support lands and CPython adopts it, there will be a published PEP with the exact SDK version, the exact spec version, and any notes about versions that got skipped and why. The ecosystem will know what to build against before the final release ships.

That's the goal. It took until 2025 to get there, but it got there.

PEP 816 is available at peps.python.org/pep-0816. The full version support table and specification rules are maintained there and in PEP 11.