The Format
Python server logic and React UI live in the same .pyxl file. The compiler splits them apart. You never think about it.
from pyxle import __version__
@server
async def load_dashboard(request):
user = await get_current_user(request)
stats = await fetch_stats(user.id)
return {"user": user.name, "stats": stats}
@action
async def update_settings(request):
body = await request.json()
await save_settings(request.state.user_id, body)
return {"ok": True, "message": "Settings saved"}
import React from 'react';
import { Head, useAction } from 'pyxle/client';
export default function Dashboard({ data }) {
const save = useAction('update_settings');
return (
<>
<Head>
<title>My Dashboard</title>
</Head>
<h1>Welcome, {data.user}</h1>
<StatsGrid stats={data.stats} />
<SettingsForm onSave={save} />
</>
);
}
@serverAsync Python function that fetches data. Receives a Starlette Request, returns a dict. It becomes React props.
@actionServer mutation callable from the browser. useAction() sends a POST, Python processes it, result comes back as JSON.
export defaultStandard React component. Receives { data } from the loader. Server-rendered, then hydrated on the client.
<Head>Controls the document head. Drop it anywhere in your component tree — title, meta, link, script. Dynamic values use normal JSX interpolation.
Live Demo
The @server loader runs on every request. Python fetches data, React renders it. Refresh to see it change.
@server
async def load_playground(request):
views = increment_playground_views()
reactions = get_reactions()
return {
"serverTime": datetime.now().isoformat(),
"pythonVersion": platform.python_version(),
"requestId": uuid.uuid4().hex[:8],
"renderMs": render_ms,
"totalViews": views,
"reactions": reactions,
}
This data came from Python, rendered by React, in one file.
Live Demo
Click a reaction. It hits Python, writes to SQLite, and returns. Every count is real and persistent.
Every click is a POST to Python. No API routes needed.
@action
async def react_emoji(request):
body = await request.json()
emoji = body.get("emoji", "")
if emoji not in VALID_EMOJIS:
raise ActionError("Invalid reaction")
new_count = increment_reaction(emoji)
return {"ok": True, "emoji": emoji, "count": new_count}
import React, { useState } from 'react';
import { useAction } from 'pyxle/client';
export default function Reactions({ data }) {
const react = useAction('react_emoji');
const [counts, setCounts] = useState(data.reactions);
async function handleClick(emoji) {
const result = await react({ emoji });
if (result.ok) {
setCounts(prev => ({ ...prev, [result.emoji]: result.count }));
}
}
return (
<div>{/* emoji buttons + counts */}</div>
);
}
Live Demo
A second @action in the same file. Type text, pick a transform — Python processes it on the server.
@action
async def transform_text(request):
body = await request.json()
text = (body.get("text", "") or "")[:500]
mode = body.get("mode", "upper")
transforms = {
"upper": str.upper,
"lower": str.lower,
"title": str.title,
"reverse": lambda t: t[::-1],
"word_count": lambda t: str(len(t.split())) + " words",
"char_count": lambda t: str(len(t)) + " characters",
}
return {"ok": True, "result": transforms[mode](text)}
Navigation
Click any link below. No reload, no white flash. Pyxle fetches only the data and swaps React props.
View page source (Ctrl+U) — the HTML is already there. Server-rendered by Python.
Routing
Drop a .pyxl file in pages/. It's a route. Dynamic segments, catch-all routes, layouts — zero configuration.
Features
Everything you need to build production-ready full-stack apps.
Every page server-rendered, then hydrated. Fast first paint, great SEO.
Enabled by default. Double-submit cookie pattern. Zero config.
error.pyxl files catch errors at any directory level.
Compose UIs with layout.pyxl. Each level wraps the level below.
Dynamic <title>, meta tags via HEAD variable or <Head> component.
Application-level and route-level hooks. Full Starlette integration.
Pure Python endpoints under pages/api/. Any HTTP method.
PYXLE_PUBLIC_ prefix for safe client injection. Server secrets stay secret.
JSX type checking via Vite. Full Python-to-React type bridge on the roadmap.
Pre-configured with dark mode. Built-in watcher in dev.
<Link> for instant SPA-style routing. Hover prefetch built in.
Python + JSX changes reflect instantly. Vite HMR built in.
Ecosystem
The core framework is stable and battle-tested. These tools are in active development to grow the ecosystem.
Drop-in authentication and session management. OAuth providers, email/password, magic links — all pre-wired.
Database toolkit with migrations, query builder, and connection pooling. SQLite to Postgres — same API.
VS Code extension with .pyxl syntax highlighting, completions, hover, go-to-definition, diagnostics, and formatting.
Install from VS Code MarketplaceBuilt-in error overlay, request inspector, and performance profiling. See exactly what your server does.
Get started in under a minute.