How to Use the Python Runtime with Pyodide in workerd: A Complete Configuration Guide

workerd supports a built-in Pyodide runtime that executes pure Python code inside the same V8 isolate as JavaScript using experimental compatibility flags and Cap'n Proto configuration files.

The cloudflare/workerd repository ships a native Python runtime powered by Pyodide, allowing Workers to run Python modules alongside JavaScript without external containers. This integration bundles the Pyodide WebAssembly binary directly into the workerd runtime and wires it through a C++/Cap'n Proto glue layer for seamless package management and cold-start optimization.

Enabling the Python Runtime

To activate the Python runtime with Pyodide in workerd, you must enable experimental compatibility flags in your worker configuration. The runtime is not active by default and requires explicit opt-in through the %PYTHON_FEATURE_FLAGS flag group.

Add the following to your .wd-test or worker configuration file:

compatibilityFlags = [
  %PYTHON_FEATURE_FLAGS,           // Enables the Pyodide runtime
  "disable_python_no_global_handlers"
];

According to the source code in src/workerd/api/pyodide/pyodide.h, these flags trigger the initialization of PyodideBundleManager and PyodidePackageManager, which handle the pre-bundled Pyodide assets and wheel management.

Architecture of the Pyodide Integration

The Python runtime in workerd operates through four distinct layers that manage asset loading, package resolution, and execution context.

Bundle Registration with PyodideBundleManager

The PyodideBundleManager class (defined in src/workerd/api/pyodide/pyodide.h) loads the pre-bundled Pyodide WebAssembly and JavaScript assets at startup. During the build process, workerd fetches a pyodide-lock.json file from an R2 bucket and compiles it into the binary. This lock file enumerates every available wheel package that can be served through the getPythonPackageFiles function.

Package Resolution via PyodidePackageManager

The PyodidePackageManager stores the pre-downloaded wheels in memory and serves them on demand when your worker imports a module. When a Worker declares Python requirements, the runtime matches them against the lock file entries and streams the exact wheel files from storage.

Metadata Parsing with PyodideMetadataReader

The PyodideMetadataReader class (implemented in src/workerd/api/pyodide/pyodide.c++ lines 79-115) parses the Worker bundle to discover Python source files, requirements, and snapshot options. It analyzes the modules field in your configuration to distinguish between pythonModule entries (your source code) and pythonRequirement entries (PyPI packages).

Emscripten Bridge Setup

The SetupEmscripten function (located in src/workerd/api/pyodide/setup-emscripten.c++) injects the pyodide.asm.js, pyodide.asm.wasm binary, and python_stdlib.zip into the V8 isolate. It creates an ArrayBuffer for the WebAssembly binary and calls the internal pyodide bootstrap function, passing the isWorkerd flag to indicate the runtime context.

Memory Snapshot Support

For cold-start optimization, the ArtifactBundler and SimplePythonLimiter classes handle optional memory snapshots. When createSnapshot or createBaselineSnapshot is enabled (lines 460-522 of pyodide.c++), the runtime captures the fully initialized Python interpreter state after module imports. Subsequent starts restore this snapshot via ArtifactBundler::storeMemorySnapshot instead of re-initializing Pyodide.

Configuration Examples

Minimal Python Worker Setup

Create a .wd-test file that defines a Python module and exposes it as a service:

using Workerd = import "/workerd/workerd.capnp";

const unitTests :Workerd.Config = (
  services = [
    ( name = "python-worker",
      worker = (
        modules = [
          (name = "worker.py", pythonModule = embed "worker.py")
        ],
        compatibilityFlags = [
          %PYTHON_FEATURE_FLAGS,
          "disable_python_no_global_handlers"
        ],
      )
    ),
  ],
);

Source: src/workerd/server/tests/python/dont-snapshot-pyodide/dont-snapshot-pyodide.wd-test

Installing PyPI Packages

Add wheels to your worker by specifying pythonRequirement entries in the modules array:

modules = [
  (name = "worker.py", pythonModule = embed "worker.py"),
  (name = "numpy", pythonRequirement = "numpy==1.26.0")
],

The PyodidePackageManager resolves these requirements against the bundled pyodide-lock.json and loads the matching wheels into the isolate before execution begins.

Enabling Memory Snapshots for Faster Cold Starts

To reduce initialization latency, enable snapshot creation in your compatibility flags:

compatibilityFlags = [
  %PYTHON_FEATURE_FLAGS,
  "disable_python_no_global_handlers",
  "snapshot_python"
];

When snapshot_python is active and PyodideMetadataReader::shouldSnapshotToDisk returns true, the runtime invokes ArtifactBundler::storeMemorySnapshot to cache the interpreter state after the first run.

Interoperating with JavaScript

Python Workers can accept requests from JavaScript Workers or clients within the same isolate. The Python module exposes a handle function that processes HTTP requests:


# worker.py

import numpy as np

def handle(request):
    data = request.json()
    result = eval(data['cmd'])  # Example: 'np.arange(5).tolist()'

    return Response.json({"result": result})

Call this from a JavaScript Worker using standard fetch:

async function callPython() {
  const resp = await fetch('http://python-worker', {
    method: 'POST',
    body: JSON.stringify({ cmd: 'np.arange(5).tolist()' }),
  });
  const data = await resp.json();
  console.log(data.result);  // [0, 1, 2, 3, 4]
}

Advanced: Customizing the Package Lock

For specific Pyodide versions or additional wheels, rebuild the lock file using the external pyodide-build-scripts repository:

git clone https://github.com/cloudflare/pyodide-build-scripts
cd pyodide-build-scripts

# Generate pyodide-lock.json for your target Pyodide release

# Upload assets to an R2 bucket and update build/pyodide_bucket.bzl

Documentation in docs/pyodide.md details the R2 bucket structure and lock file generation process.

Summary

  • workerd integrates Pyodide via PyodideBundleManager and SetupEmscripten to run Python inside V8 isolates.
  • Enable the runtime with %PYTHON_FEATURE_FLAGS and disable_python_no_global_handlers compatibility flags.
  • Define Python modules using pythonModule and dependencies using pythonRequirement in Cap'n Proto configuration.
  • The PyodideMetadataReader (lines 79-115 of pyodide.c++) handles metadata parsing, while ArtifactBundler manages optional memory snapshots.
  • Reduce cold starts by enabling snapshot_python compatibility flag to leverage storeMemorySnapshot.

Frequently Asked Questions

How do I enable the Python runtime in my workerd configuration?

Add the %PYTHON_FEATURE_FLAGS compatibility flag to your worker definition in the Cap'n Proto configuration file. You must also include disable_python_no_global_handlers to allow Python's global interpreter lock handlers. Without these flags, the PyodideBundleManager will not initialize and Python modules will fail to load.

Can I install PyPI packages like NumPy or Pandas in workerd?

Yes. Use the pythonRequirement field in your module configuration to specify package names and versions (e.g., pythonRequirement = "numpy==1.26.0"). The PyodidePackageManager matches these against the bundled pyodide-lock.json file and loads the corresponding wheels from memory or R2 storage during worker initialization.

What is the memory snapshot feature and how does it improve performance?

Memory snapshots capture the initialized state of the Python interpreter after your modules load. When snapshot_python is enabled, ArtifactBundler::storeMemorySnapshot saves this state to disk. Subsequent cold starts restore the snapshot instead of re-executing Pyodide's boot sequence, significantly reducing startup latency for Python Workers.

Where is the Pyodide integration implemented in the workerd source code?

The core implementation spans src/workerd/api/pyodide/pyodide.h (bundle and package managers), src/workerd/api/pyodide/pyodide.c++ (metadata parsing and snapshots), and src/workerd/api/pyodide/setup-emscripten.c++ (WebAssembly injection). Documentation and build instructions reside in src/pyodide/README.md and docs/pyodide.md.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →