Inertia.js Adapter for Python Web Frameworks
A Python adapter for using Inertia.js with FastAPI and Django.
[!CAUTION] This library is still in active development so it might not be fully stable yet.
📚 Documentation | 🚀 Quick Start | 🗺️ Roadmap
Features
- ✅ Full Inertia.js protocol support
- ✅ Vite integration (dev & production)
- ✅ Auto-detection of Vite entry point from vite.config.ts/js
- ✅ Asset versioning for cache busting
- ✅ Validation error handling (
props.errors) - ✅ History encryption for sensitive data
- ✅ External redirects (OAuth, payments, etc.)
- ✅ Partial reloads & shared data
- ✅ Merging props (infinite scroll support)
- ✅ View data (server-side template variables)
- ✅ TypeScript support
Installation
# Install from PyPI using uv (recommended)
uv pip install cross-inertia
# Or using pip
pip install cross-inertia
# Or install from source
uv pip install -e .
Try the Demo
We have a full-featured cat adoption demo app in examples/fastapi/:
# Using just (recommended)
just demo-install # Install dependencies
just demo-fastapi # Run the demo
# Or manually
cd examples/fastapi
bun install
./run-dev.sh
Visit http://127.0.0.1:8000 to see Inertia.js + FastAPI in action!
Quick Start
1. Basic Setup
from fastapi import FastAPI
from cross_inertia.fastapi import InertiaDep
app = FastAPI()
@app.get("/")
async def home(inertia: InertiaDep):
return inertia.render(
"Home",
{
"message": "Hello from Inertia!"
}
)
2. Custom Configuration
If you need to customize the Inertia configuration (e.g., different template directory or Vite settings):
from fastapi import FastAPI, Request, Depends
from cross_inertia.fastapi import InertiaResponse, Inertia
# Create custom InertiaResponse instance
inertia_response = InertiaResponse(
template_dir="my_templates",
vite_dev_url="http://localhost:5173",
manifest_path="dist/.vite/manifest.json",
vite_entry="src/main.tsx", # Optional: auto-detected from vite.config
vite_config_path="vite.config.ts" # Optional: defaults to vite.config.ts
)
app = FastAPI()
def get_custom_inertia(request: Request) -> Inertia:
from cross_web import StarletteRequestAdapter
adapter = StarletteRequestAdapter(request)
return Inertia(request, adapter, inertia_response)
@app.get("/")
async def home(inertia: Inertia = Depends(get_custom_inertia)):
return inertia.render("Home", {"message": "Hello!"})
Configuration Options
InertiaResponse Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
template_dir |
str |
"templates" |
Directory containing your root HTML template |
vite_dev_url |
str |
"http://localhost:5173" |
Vite dev server URL |
manifest_path |
str |
"static/build/.vite/manifest.json" |
Path to Vite manifest file (production) |
vite_entry |
str | None |
None |
Vite entry point (auto-detected from config if None) |
vite_config_path |
str |
"vite.config.ts" |
Path to vite.config.ts/js for auto-detection |
Auto-Detection of Vite Entry
By default, the adapter will attempt to read your vite.config.ts (or .js) file and extract the entry point from:
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
input: "frontend/app.tsx", // ← Auto-detected
},
},
});
This means you don't need to specify vite_entry manually - it will match your Vite configuration automatically!
Root Template
Create a template file (default: templates/app.html):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
{{ vite()|safe }}
</head>
<body>
<script data-page="app" type="application/json">
{{ page|safe }}
</script>
<div id="app"></div>
</body>
</html>
The {{ vite() }} function will automatically include:
- React Fast Refresh scripts (dev mode)
- Vite client scripts (dev mode)
- Your entry point script
- Built CSS and JS files (production mode)
Inertia.js v3 reads this JSON script format by default:
createInertiaApp({
// ...
});
Using Custom Entry Points
<!-- Use default entry (from config/auto-detection) -->
{{ vite()|safe }}
<!-- Use custom entry point -->
{{ vite('admin/app.js')|safe }}
Backward compatibility: The old {{ vite_tags|safe }} variable is still supported.
Validation Errors
Validation errors are automatically handled:
@app.post("/users")
async def create_user(inertia: InertiaDep):
errors = validate_user(request_data)
if errors:
# Returns 200 with props.errors for Inertia requests
return inertia.render(
"Users/Create",
{"user": request_data},
errors=errors
)
# Create user...
return inertia.render("Users/Show", {"user": new_user})
External Redirects
Use inertia.location() to redirect to external websites or non-Inertia pages:
@app.get("/auth/github")
async def github_oauth(inertia: InertiaDep):
"""Redirect to GitHub OAuth"""
oauth_url = f"https://github.com/login/oauth/authorize?client_id={CLIENT_ID}"
return inertia.location(oauth_url)
@app.get("/shelter/{id}/directions")
async def get_directions(id: int, inertia: InertiaDep):
"""Redirect to Google Maps"""
shelter = get_shelter(id)
maps_url = f"https://maps.google.com/?q={shelter.address}"
return inertia.location(maps_url)
This returns a 409 Conflict response with X-Inertia-Location header, which the client automatically follows with a full page navigation.
History Encryption
Protect sensitive data in browser history by encrypting page state. This prevents users from viewing sensitive information after logging out by pressing the back button.
# Encrypt sensitive pages
@app.get("/account/transactions")
async def transactions(inertia: InertiaDep):
inertia.encrypt_history() # Enable encryption
return inertia.render("Transactions", {
"balance": user.balance,
"transactions": user.get_transactions()
})
# Clear encrypted history on logout
@app.post("/logout")
async def logout(inertia: InertiaDep):
clear_user_session()
inertia.clear_history() # Rotate encryption keys
return inertia.render("Login", {})
How it works:
- Uses browser's Web Crypto API (AES-GCM encryption)
- Encryption keys stored in sessionStorage
clear_history()rotates keys, making old history unreadable- Only works over HTTPS (except localhost)
Use cases: Banking apps, healthcare (HIPAA), admin panels, any sensitive data
Development vs Production
The adapter automatically detects whether Vite dev server is running:
- Dev mode: Includes Vite dev server scripts and React Fast Refresh
- Production mode: Reads from manifest.json and includes built assets
No configuration changes needed - it just works!
Feature Status
| Feature | FastAPI | Django |
|---|---|---|
| Basic protocol | ✅ | ✅ |
| Vite development and production | ✅ | ✅ |
| Asset version mismatch | ✅ | ✅ |
| Partial reloads and shared data | ✅ | ✅ |
| Redirects and history encryption | ✅ | ✅ |
| Merge, deferred, and once props | ✅ | ✅ |
| Error bags and prefetching | ✅ | ✅ |
| SSR | ✅ | ✅ |
See ROADMAP.md for detailed implementation plans and progress tracking.
Current Status
Version: v0.20.0
This adapter implements all production-critical Inertia features and is ready for production use.
Production-ready features:
- ✅ Basic page rendering
- ✅ Form submissions with validation
- ✅ Navigation between pages
- ✅ Development with Vite HMR
- ✅ Asset version mismatch handling (409 Conflict)
- ✅ Partial reloads for performance
- ✅ Shared data (auth, flash messages)
- ✅ External redirects (OAuth, payments)
- ✅ History encryption (sensitive data protection)
- ✅ Merging props (infinite scroll)
- ✅ View data (server-side template variables)
Contributing
Contributions are very welcome! This adapter aims to match the Laravel adapter's feature set.
How to contribute:
- Check GitHub Issues for open tasks
- Pick a feature (look for
good first issueorhigh-prioritylabels) - Read the linked Inertia.js documentation
- Implement following existing patterns
- Write tests and update documentation
- Submit a PR!
See ROADMAP.md for the full project roadmap and milestone planning.
License
MIT
Release files for cross-inertia 0.22.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| cross_inertia-0.22.0.tar.gz | 2.6 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cross_inertia-0.22.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 2.6 MB
Release files / cross_inertia-0.22.0.tar.gz
| Download URL | cross_inertia-0.22.0.tar.gz |
|---|---|
| Size | 2.6 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
146732212e3d02dd3b42c1da0d067eaba8833798b61fac35051a207425886170
|
|
BLAKE2b-256 checksum How to use checksums |
bab78fbdaaeb3ac5e3e213c04624d8bb4fb804115a3847a6a449f4920090f094
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / cross_inertia-0.22.0-py3-none-any.whl
| Download URL | cross_inertia-0.22.0-py3-none-any.whl |
|---|---|
| Size | 59.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
358446d6910f030fb745994d585ba589a529c36cf3195f3ecfe8cd4418e23b89
|
|
BLAKE2b-256 checksum How to use checksums |
405761e9ddd9979fc4995b35db43ee81728b93e3c449541be14ddefb96afb6ae
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|