GitHub
New 0.4.2: npm packages, layouts, streaming AI, live queries →

Full-stack web apps in one Python file.

Server-rendered pages, reactive UI compiled from Python, and typed calls to server functions. No JavaScript toolchain, no WebSocket per user, no runtime download.

$pip install pyweb-stack
app.pyweb
from pyweb import App, server app = App(title="Guestbook")ENTRIES = []  @serverdef sign(name: str) -> list:    ENTRIES.append(name.strip() or "anonymous")    return ENTRIES  @app.page("/")def Home():    entries = list(ENTRIES)     # computed on the server per request    name = ""                   # bound to the input: browser state     def submit():               # compiled to JavaScript        entries = sign(name)    # typed RPC to the server        name = ""     <main>        <h1>Guestbook ({len(entries)})</h1>        <form onsubmit={submit}>            <input bind={name} placeholder="Your name" />            <button>Sign</button>        </form>        <ul>            for entry in entries:                <li>{entry}</li>        </ul>    </main>
Runs in the browserRuns on the serverRendered on the server, updated in the browser
14.6 KBshared runtime, gzip, cached
300 Bpage code for a counter
0 BJavaScript on static pages
53 µsto server-render the todo page
How it works

One file. The compiler puts each line where it belongs.

Write pages as Python functions with markup. PyWeb splits them: handlers become a tiny browser module, everything else stays on your server. This is the output for the app above.

In the browser

A 443 B module (gzip) plus the shared runtime.

  • Variables your handlers change become signals
  • Handlers compile to JavaScript with Python semantics
  • Hydrates the server's HTML in place: nothing is rebuilt
Home.js · compiled from submit()
async function submit() {
  entries((await $rpc("sign", {"name": name()})));
  name("");
}

On the server

Plain Python, per request: your database, files and secrets stay here.

  • Renders complete HTML with real data
  • @server functions become validated JSON endpoints
  • Sends only the values browser code reads
HTTP · a real request to the app above
GET /
  → runs Home(), renders 0.5 KB of HTML

POST /__pyweb/rpc/sign
  {"args": {"name": "Ada"}}
  → {"result": ["Ada"]}
Live demo

This is a real PyWeb app

The todo example (started with three items), compiled by PyWeb and running right here: 1110 bytes of page code.

  • Add items, tick them off, switch filters
  • Everything updates locally, with no network round trips
  • The same file renders the first paint on the server
See its source and generated JavaScript →
localhost:8000
Why PyWeb

The good parts of modern web stacks, in Python

Interactions run in the browser

Handlers and expressions compile to small JavaScript modules. The network is only used when you call a @server function: no WebSocket per user, no Python runtime download.

Every page is server-rendered

Complete HTML on first paint with real data from your database, then hydrated in place. Good for search engines, slow devices and links that just work.

No API layer to write

@server functions get endpoints, argument validation, typed errors and generated browser calls.

Variables are state

The compiler sees which variables your handlers change and makes exactly those reactive. Updates touch only the DOM nodes that read them.

Checked boundaries

Database handles, imports and secrets can't reach browser code: it's a compile error with a line number. pyweb inspect explains every decision.

Data that keeps up

live(db, "select ...") keeps a page in step with the database in every open window, and server functions that yield stream to the page: AI replies with a Stop that really stops.

Real multi-page apps

Shared layouts that keep their state, links that load without a full reload, typed query parameters, per-page titles and social tags.

npm without Node

pyweb add chart.js/auto vendors a package for browser code, pinned in pyweb.lock. No node_modules, no bundler.

Boring to operate

Stateless servers, signed-cookie sessions, plain JSON over HTTP. Run pyweb serve, uvicorn or the generated Dockerfile behind any load balancer.

Tooling

Everything around the compiler

AI assistants

Built for building with AI

One file, plain Python, and a compiler that answers mistakes with a line number and a fix. The built-in MCP server lets Claude Code, Cursor and other assistants scaffold apps, check them, see what runs where, render pages, call server functions, look at the result in a real browser and run the tests.

  • New projects include AGENTS.md, CLAUDE.md and a starter test
  • The docs ship as llms-full.txt
Set it up →
Claude Code
pip install pyweb-stack
claude mcp add pyweb -- pyweb mcp
Cursor · Claude Desktop · VS Code
{
  "mcpServers": {
    "pyweb": { "command": "pyweb", "args": ["mcp"] }
  }
}
Where it fits

Made for Python developers who ship web UIs

Internal tools, dashboards, CRUD apps and small products, without adopting a JavaScript stack. For large client-heavy single-page apps, use a JavaScript framework; for scientific Python in the browser, use Pyodide. Read the full comparison →

ApproachYou writeTrade-off
Django / Flask / FastAPI + ReactA Python API and a JavaScript appTwo languages, two builds, a hand-written API in between
Templates + htmxViews, templates, an endpoint per interactionEvery interaction is a round trip; client state is awkward
Reflex, NiceGUI, StreamlitPython onlyUI state lives on the server; clicks round-trip over a WebSocket
PyScript / PyodidePython onlyA multi-megabyte runtime before anything is interactive
PyWebOne Python fileBrowser code is a compiled subset of Python

Build something in the next five minutes

Install it, scaffold an app, and open it in your browser. Or skip the install and use the playground.