Pyodide 314.0: Python Wheels Hit PyPI, Finally
Pyodide jumped from 0.29 to 314.0 on 13 June 2026 and HN ran the post at 52 points, 10 comments, posted by a maintainer, and the headline read "Python packages can now publish WebAssembly wheels to PyPI." That last phrase is the story. The version number is a consequence. The number is a marketing translation of a packaging-ecosystem unlock, and the unlock is real, and it is the kind of change that, once it sticks, doesn't get unwound.
The release post opens with the line that frames the rest: "The acceptance of PEP 783: Emscripten packaging marks perhaps the most exciting change in the history of the Python-in-the-browser ecosystem. Pyodide maintainers—especially @hoodmane—have poured an immense amount of effort into this over a very long time. Achieving this long-standing goal will expand our ecosystem exponentially." The post then says the quiet part loud: "Previously, the Pyodide maintainers had to maintain, build, and host over 300 packages ourselves. This created a significant burden on our maintainers and became a major bottleneck for the community, as every new package required manual review." That sentence is the thing. The bottleneck was human, not technical, and the human just got removed from the critical path.
PEP 783 is the headline; the version bump is the receipt
PEP 783, "Emscripten Packaging," authored by Hood Chatham and sponsored by CPython release manager Łukasz Langa, was officially accepted by the Python Steering Council on 6 April 2026 — two months before the release it unlocks. The PEP defines a new platform tag series for binary Python wheels: pyemscripten_2025_0 for Python 3.13 (the previous Pyodide 0.29.x line) and pyemscripten_2026_0 for Python 3.14 (the new Pyodide 314.x line). The tags slot into the wheel filename the same way manylinux_2_17_x86_64 does for server Linux today, and cibuildwheel v4.0 already supports both. The 2026 tag is gated behind a pyodide-prerelease option until cibuildwheel v4.1.0 ships. That, in one paragraph, is the entire story.
What the tag means in practice: a Python package that already ships manylinux wheels on PyPI can now add a pyemscripten wheel to the same release, push it to the same index, and have it install inside a browser via micropip.install("name") with no Pyodide-side review. The same pyemscripten ABI is consumable by any runtime that conforms to the PEP, not only Pyodide — which is the part that makes it a packaging standard rather than a project fork. The CPython release manager sponsoring a PEP for the runtime Pyodide compiles against is the kind of upstream-downstream alignment that hasn't existed for the browser target before.
The 314 versioning scheme is ABI stability, made visible
The version number 314.0 looks like a meme. It is — but the math is meaningful. Pyodide RFC #6084 set the new scheme: [Python Major+Minor].[Pyodide Major].[Pyodide Minor]. So 314.0 is the first release targeting Python 3.14, and the next one will be 315.0 for Python 3.15. The release post frames it directly: "Whenever we make binary-incompatible changes, they will now align strictly with upstream Python updates (typically once a year). This means you can safely use existing packages built for the same Python version across multiple Pyodide releases." The versioning scheme is the contract. Before, every minor Pyodide bump could silently break third-party wheels because the platform tag (pyodide_2024_0_wasm32) was a project-internal ABI with no promised stability horizon. After, pyemscripten_2025_0 is guaranteed stable for the life of Python 3.13, and pyemscripten_2026_0 for Python 3.14. A maintainer who builds a wheel today does not have to think about when it will break.
This is the part that matters for adoption. The previous shape of the problem was not "Python is slow to ship to the browser" — it was "Python in the browser has a different ABI every six months and your notebook is the canary." The new shape is the same shape native Linux wheels have had for a decade. That's a quarter-century of Python packaging muscle memory now being applicable to the browser target.
The proof is the same-day install
The strongest evidence that the unlock is real, not aspirational, is the same-day install. Simon Willison (simonw) opened the Pyodide web console on 13 June 2026 and ran:
import micropip
await micropip.install("pydantic_core")
import pydantic_core
pydantic-core is a Rust extension module built with PyO3, with a non-trivial C-FFI surface, and it just installed. Willison wrote: "I've been looking forward to this for ages!" The "ages" is the point — the desire has been there since 2021, when the Pyodide team first floated the idea; the missing piece was the packaging standardization. Willison also published luau-wasm to PyPI the same day, a Roblox-Luau interpreter packaged as a Python extension, with a live demo at https://simonw.github.io/luau-wasm/. A real third-party language VM, running in a browser, installed by name from PyPI, the same day the format became standard. That is the proof of concept. Felix Zumstein (creator of xlwings, the Python-in-Excel competitor) confirmed in the same thread: "Pyodide 314.0 is already available in xlwings Lite." The first adopter is a spreadsheet vendor, which is not the demo I would have picked, but is the demo I would have wanted.
What is not solved — and the post is honest about it
The release is not a runtime rewrite. The interpreter is the same Emscripten-compiled CPython; the FFI, the module loading, and the WASM ABI under the hood are continuous with Pyodide 0.29. The unlock is purely in how packages reach the runtime, not in what the runtime does. Two limits are worth flagging.
No browser sockets yet. The new socket support — pyodide.useNodeSockFS(), tested against pymysql, pg8000, and redis-py — is Node.js only. Browser code that needs networking still uses pyodide.http and fetch. On Node ≤ 24 you also need --experimental-wasm-stack-switching (JSPI) to enable the necessary stack-switching primitives. The post is candid that the browser socket story is not done.
OpenSSL is out of stdlib. The ssl module is now a custom stub without actual TLS, and hashlib has lost the OpenSSL-only hash functions. The post owns it: "most of the ssl module's functionality didn't work even before this change because we didn't support socket operations in the browser." The framing is honest. It is also a real regression for code that genuinely used the removed hash functions or expected real OpenSSL bindings; tutorial code that does import ssl; ssl.create_default_context() inside a browser Pyodide will now return a context object that cannot complete a TLS handshake, where last week it could not have done that either, but the failure mode was different. The trade-off — smaller bundle, fewer surprises in the no-socket case — is defensible, and the maintainers made it openly.
Smaller migration items worth knowing: pyodide.asm.js is renamed to pyodide.asm.mjs; classic non-module workers are gone; service workers that statically imported the old filename need a one-line refactor to import createPyodideModule and pass it to loadPyodide(). None of these are load-bearing for new code; all of them are load-bearing for anyone running a Pyodide 0.27-era service worker in production.
The original take: the browser just became a serious Python deployment target
The most under-discussed consequence of PEP 783 is not about Pyodide at all. It is that the Python Steering Council has now blessed a standardized wheel format that targets a browser-or-Node runtime, and that format can be implemented by any project that wants to put a Python interpreter in a sandboxed environment. Pyodide is the first adopter. It will not be the last.
The interesting structural question is what happens to "Python in the browser" as a category when the standard is set. Today, the only thing you can install via pip install that runs in a browser is whatever Pyodide and the cibuildwheel team have agreed on. Tomorrow, any company that ships a Python interpreter inside a WASM sandbox — a Jupyter notebook backend, a Cloudflare Worker, a Cloudflare Pages Function, a Deno Deploy function, a Vercel Edge Function, an in-browser code playground, a SaaS IDE — can conform to the same pyemscripten_2026_0 platform tag and accept the same wheels. The contract is portable. The interpreter doesn't have to be Emscripten. The ABI is the contract, not the implementation. PEP 783 is the moment "Python in the browser" stops being a single project and starts being a target the ecosystem can build against.
The second-order consequence is the 12-month cadence. Pyodide now ships a major version annually, synchronized with CPython. The number of months a maintainer has to ship a pyemscripten wheel after a new Python version is fixed at the upstream cadence. There is no more Pyodide-internal-minor-release breakage window to plan around. The calendar is CPython's calendar. For anyone who has ever had a production notebook break because Pyodide shipped a new minor version with an ABI change, the new schedule is the actual fix. And for maintainers shipping to both Pyodide and a Cloudflare-Worker-style runtime, the answer to "which version do I support" collapses from a 2D matrix (target Pyodide × target worker runtime) to a 1D question (which Python version), because both sides pin to the same pyemscripten_20XX_0 ABI.
What this means for you
- If you maintain a Python package with C/Rust extensions that already ships
manylinuxwheels — adding apyemscriptenwheel is now a CI job, not a project. The setup iscibuildwheelv4.0 with--platform pyodideand amaturin/setuptools-rustconfig that knows about the Emscripten target. The Victorien Plot guide on the Pydantic blog is the canonical PyO3/maturin walkthrough. Thepyodide-builddocs are the canonical reference for everything else. If you've been on the fence about "is Pyodide worth supporting," the answer as of 13 June 2026 is that the cost is one extra cibuildwheel matrix row. - If you build a web app that wants Python in the browser — the bottleneck just changed. Before, you were waiting for the Pyodide team to bless a package. After, you are waiting for the package's maintainer to add a
pyemscriptenwheel, and that maintainer now has the path documented. The 1-2 day install demo (pydantic-core, luau-wasm) is the new normal, not the exception. Re-audit your build pipeline; theawait pyodide.loadPackage("sqlite3")shim is no longer needed, the new release just putsqlite3back in the stdlib. - If you run Pyodide in production — there is a real migration to schedule. Service workers importing
pyodide.asm.jsneed a one-line refactor. Code using the removedhashlibalgorithms needs a substitute (the stdlib's_hashlibis unchanged for SHA-2 family; the OpenSSL-only ones are gone). The Node socket support is opt-in viauseNodeSockFS()and is genuinely new — you can now runpymysqlagainst a real MySQL server from a Node-side Pyodide, which was not possible a week ago. Audit the dependencies; the 314.0 contract means yourpyemscripten_2026_0wheel is now stable for the life of Python 3.14, so locking to the new tag is the right call. - If you are evaluating Python in the browser as a category — the question is no longer "is it ready" and is now "who in the Python packaging ecosystem hasn't yet shipped a
pyemscriptenwheel, and what's their plan?" The standard exists, the tooling exists, the maintainers have aligned the calendar with CPython, and the cibuildwheel integration is upstream. Treat it as a normal target. The days of "Python in the browser is a research project" are over.
What to do this week
# 1. Verify your environment can pull and run a Pyemscripten wheel.
# Open https://pyodide.org/en/stable/console.html and run:
#
# import micropip
# await micropip.install("pydantic_core")
# import pydantic_core; pydantic_core.__version__
#
# If that returns a version string, your browser can already
# consume the new wheel format. If it errors on platform-tag
# resolution, you are on a cached older Pyodide; refresh.
# 2. If you maintain a C-extension package, add a Pyemscripten
# job to your cibuildwheel matrix. The minimum config is:
# [tool.cibuildwheel]
# build = ["cp313-*", "cp314-*"]
# # Pyemscripten 2025 is stable on cibuildwheel 4.0.
# # Pyemscripten 2026 needs pyodide-prerelease = true
# # until cibuildwheel 4.1.0 lands.
# 3. If you ship a Pyodide 0.x service worker, the migration is
# a four-line patch:
#
# - import "./pyodide.asm.js";
# + import createPyodideModule from "./pyodide.asm.mjs";
# - const pyodide = await loadPyodide({ indexURL: "./" });
# + const pyodide = await loadPyodide({
# + indexURL: "./",
# + createPyodideModule,
# + });
#
# And your worker must be type: "module" — classic workers
# are gone. Search your repo for "pyodide.asm.js" references
# in bundler config; every one needs a .mjs suffix.
# 4. If you run Python in a Node.js environment and need
# a real database driver, the new useNodeSockFS() path
# is worth 30 minutes of evaluation:
#
# const pyodide = await loadPyodide();
# await pyodide.useNodeSockFS();
# await pyodide.runPythonAsync(`
# import pymysql, asyncio
# conn = await asyncio.to_thread(
# pymysql.connect,
# host="...", user="...", password="...",
# )
# `);
#
# On Node <= 24 you'll need --experimental-wasm-stack-switching
# to enable JSPI. The maintainers tested this with pymysql,
# pg8000, and redis-py; your driver is probably fine.
# 5. Watch the next 30 days for two things: (a) when cibuildwheel
# v4.1.0 ships and the 2026 ABI stops being prerelease;
# (b) how many of the top 100 PyPI packages publish a
# pyemscripten_2026_0 wheel in the first wave. The shape of
# the first wave is the shape of "Python in the browser" for
# the rest of the year. The standard is set. The race is on.
Disclosure
Disclosure: Drafted with AI assistance. Primary source: "Pyodide 314.0 Release," Pyodide blog, posted 13 June 2026, https://blog.pyodide.org/posts/314-release/ (the post HTML carries a "June 9, 2026" date stamp; the cross-referenced publication day per Simon Willison's same-day writeup at https://simonwillison.net/2026/Jun/13/publishing-wasm-wheels/ and the HN thread timestamp is 13 June 2026). The post does not declare named authors; the byline and acknowledgements list Gyeongjae Choi, Hood Chatham, and Agriya Khetarpal among roughly 30 contributors. Standards-track source: Hood Chatham (author), Łukasz Langa (sponsor), "PEP 783 — Emscripten Packaging," accepted 6 April 2026, https://peps.python.org/pep-0783/. Versioning rationale: Pyodide Issue #6084, "RFC: New Pyodide Versioning Scheme for ABI Stabilization" — the full scheme syntax
[Python Major+Minor].[Pyodide Major].[Pyodide Minor]and the315.0prediction for Python 3.15 are the release post's framing rather than directly quoted from the RFC body, which we could not fetch and verify line-by-line. HN discussion: item 48462759, https://news.ycombinator.com/item?id=48462759, 52 points and 10 comments as fetched on 13 June 2026 (counts are moving). Same-day install demo: Simon Willison on the HN thread and on his own blog, 13 June 2026.luau-wasmPyPI package: https://pypi.org/project/luau-wasm/ (Willison describes it as "a packaging of the Luau language by Roblox" pushed to PyPI; the framing as a Python extension is editorial inference, not a direct quote). Adopter quote: Felix Zumstein (commonly identified as the creator of xlwings) on the HN thread, 13 June 2026, paraphrasing xlwings Lite as "the Python in Excel alternative you actually wanted." PyO3/maturin how-to: Victorien Plot, "Building and publishing PyEmscripten wheels," Pydantic blog, https://pydantic.dev/articles/emscripten-wheels-pydantic. cibuildwheel 4.0 release note: https://iscinumpy.dev/post/cibuildwheel-4-0-0/.pyodide-builddocumentation: https://pyodide-build.readthedocs.io/en/latest/ (the version 0.35.1 dev pre-release was current as of 12 June 2026). The "300 packages" count is from the maintainer release post; PEP 783's motivation section cites 255 packages as of the PEP draft date — both can be true (255 at draft, 300 at release). The pre-existingpyodide_2024_0_wasm32platform tag is named in this post's body to illustrate "the old project-internal ABI"; the specific year-suffixed tag name is plausibly correct but not independently re-verified from a fetched source. The "no browser sockets" and "Node ≤ 24 needs --experimental-wasm-stack-switching" claims are from the release post.
Sources
- "Pyodide 314.0 Release," Pyodide blog, posted 13 June 2026 — https://blog.pyodide.org/posts/314-release/
- Hood Chatham (author), Łukasz Langa (sponsor), "PEP 783 — Emscripten Packaging," Python Enhancement Proposals, accepted 6 April 2026 — https://peps.python.org/pep-0783/
- Pyodide Issue #6084, "RFC: New Pyodide Versioning Scheme for ABI Stabilization" — https://github.com/pyodide/pyodide/issues/6084
- HN discussion, item 48462759, "Pyodide 314.0: Python packages can now publish WebAssembly wheels to PyPI" — https://news.ycombinator.com/item?id=48462759
- cibuildwheel 4.0 release notes, supporting PEP 783 platform tags — https://iscinumpy.dev/post/cibuildwheel-4-0-0/
- pyodide-build documentation (Pyodide build tooling, 0.35.1 as of 12 June 2026) — https://pyodide-build.readthedocs.io/en/latest/
- Victorien Plot, "Building and publishing PyEmscripten wheels," Pydantic blog — https://pydantic.dev/articles/emscripten-wheels-pydantic
- Simon Willison,
luau-wasmon PyPI (same-day demo of the new wheel format) — https://pypi.org/project/luau-wasm/ - Pyodide, previous release for architectural context, "Pyodide 0.29 Release" — https://blog.pyodide.org/posts/0.29-release/
Related reads
- Linear Is Fast Because the Browser Is the Database — the "treat the client as the source of truth" frame, applied to a production app; the Pyodide 314 packaging change is the same posture in a different layer — the browser is now a serious Python deployment target because the wheel contract is real, not aspirational
- macOS Containers: Apple Put a Linux VM Inside Every One — the "platforms add isolation boundaries" frame, applied to a per-tenant microVM story; the PyEmscripten ABI is the same shape of decision at the language-runtime level — a standardized interface that lets a sandboxed interpreter consume any conforming wheel
- Scott Chacon Spent $15K and 45B Tokens Rewriting Git in Rust — the "the cost of porting a toolchain is dropping fast" frame, applied to a C-to-Rust rewrite with AI assistance; the
pydantic-core-in-the-browser story is the same shape — once the build target is standard, the per-package cost of going cross-platform collapses
No comments:
Post a Comment