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

> Learn to run Python with Pyodide in workerd using its new built-in runtime. This guide covers configuration for seamless JS and Python execution within V8 isolates.

- Repository: [Cloudflare/workerd](https://github.com/cloudflare/workerd)
- Tags: how-to-guide
- Published: 2026-03-18

---

**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:

```capnp
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`](https://github.com/cloudflare/workerd/blob/main/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`](https://github.com/cloudflare/workerd/blob/main/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`](https://github.com/cloudflare/workerd/blob/main/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`](https://github.com/cloudflare/workerd/blob/main/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:

```capnp
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:

```capnp
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`](https://github.com/cloudflare/workerd/blob/main/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:

```capnp
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:

```python

# 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:

```javascript
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:

```bash
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`](https://github.com/cloudflare/workerd/blob/main/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`](https://github.com/cloudflare/workerd/blob/main/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`](https://github.com/cloudflare/workerd/blob/main/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`](https://github.com/cloudflare/workerd/blob/main/src/pyodide/README.md) and [`docs/pyodide.md`](https://github.com/cloudflare/workerd/blob/main/docs/pyodide.md).