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
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>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
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
@serverfunctions become validated JSON endpoints- Sends only the values browser code reads
GET /
→ runs Home(), renders 0.5 KB of HTML
POST /__pyweb/rpc/sign
{"args": {"name": "Ada"}}
→ {"result": ["Ada"]}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
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.
Everything around the compiler
publish() on the server, subscribe() in the page, over Server-Sent Events.Read the guide →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.mdand a starter test - The docs ship as llms-full.txt
pip install pyweb-stack
claude mcp add pyweb -- pyweb mcp{
"mcpServers": {
"pyweb": { "command": "pyweb", "args": ["mcp"] }
}
}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 →
| Approach | You write | Trade-off |
|---|---|---|
| Django / Flask / FastAPI + React | A Python API and a JavaScript app | Two languages, two builds, a hand-written API in between |
| Templates + htmx | Views, templates, an endpoint per interaction | Every interaction is a round trip; client state is awkward |
| Reflex, NiceGUI, Streamlit | Python only | UI state lives on the server; clicks round-trip over a WebSocket |
| PyScript / Pyodide | Python only | A multi-megabyte runtime before anything is interactive |
| PyWeb | One Python file | Browser 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.