Prerequisites
Before You Start
- Chirp installed
- Python 3.14+ available
Scaffold a Project
The fastest way to start is thechirp newcommand:
chirp new myapp
cd myapp
python app.py
Open http://127.0.0.1:8000in your browser.
chirp new myappnow generates an auth-ready v2 layout:
app.pywith sessions, auth, CSRF, and security headers middlewaremodels.pywith a demo user model + password hashingpages/filesystem routes (/,/login,/dashboard)static/style.csstests/with auth flow tests
The scaffold runs in development mode by default and readsCHIRP_SECRET_KEY.
Before production, set a strong secret and run with production settings.
export CHIRP_SECRET_KEY="$(python - <<'PY'
import secrets
print(secrets.token_urlsafe(48))
PY
)"
For an even smaller starting point:
chirp new myapp --minimal
This generates only app.py and templates/index.html.
Hello World (Manual)
You can also create a project by hand. Create a file calledapp.py:
from chirp import App
app = App()
@app.route("/")
def index():
return "Hello, World!"
app.run()
Run it:
python app.py
Open http://127.0.0.1:8000in your browser. Five lines to hello world.
Add Templates
Create atemplates/ directory and add base.html:
<!DOCTYPE html>
<html>
<head><title>{{ title }}</title></head>
<body>
{% block content %}{% endblock %}
</body>
</html>
Add templates/index.html:
{% extends "base.html" %}
{% block content %}
<h1>{{ title }}</h1>
<p>Welcome to my Chirp app.</p>
{% endblock %}
Update app.py:
from chirp import App, Template
app = App()
@app.route("/")
def index():
return Template("index.html", title="Home")
app.run()
Route functions return values. Template tells the framework to render index.htmlwith the given context via kida.
Fragment Rendering
This is where Chirp diverges from Flask. Add a search feature that works as both a full page and an htmx fragment.
Addtemplates/search.html:
{% extends "base.html" %}
{% block content %}
<h1>Search</h1>
<input type="search" name="q"
hx-get="/search" hx-target="#results" hx-trigger="input changed delay:300ms">
{% block results %}
<div id="results">
{% for item in results %}
<p>{{ item }}</p>
{% endfor %}
</div>
{% endblock %}
{% endblock %}
Update app.py:
from chirp import App, Template, Fragment, Request
app = App()
ITEMS = ["apple", "banana", "cherry", "date", "elderberry"]
@app.route("/")
def index():
return Template("index.html", title="Home")
@app.route("/search")
def search(request: Request):
q = request.query.get("q", "")
results = [i for i in ITEMS if q.lower() in i.lower()] if q else ITEMS
if request.is_fragment:
return Fragment("search.html", "results", results=results)
return Template("search.html", title="Search", results=results)
app.run()
Full page navigation renders everything. An htmx request renders just the resultsblock. Same template, same data, different scope.
Add htmx
To make the fragment rendering work, include htmx in yourbase.html:
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
Now the search input sends requests to /search via htmx, and Chirp responds with just the resultsblock -- no full page reload, no separate partials directory, no JavaScript.
Live Updates in 5 Minutes
Add real-time updates with SSE and view transitions:
- 1
Install Chirp
If not already installed:
pip install bengal-chirp - 2
Extend the boost layout
Add the layout and SSE scope to your template:
{% extends "chirp/layouts/boost.html" %} {% block content %} <ol> {% for item in items %} <li>{{ item.title }}</li> {% endfor %} </ol> {% endblock %} {% block sse_scope %} {% from "chirp/sse.html" import sse_scope %} {{ sse_scope("/events") }} {% endblock %} - 3
Stream fragments from your route
from chirp import EventStream, Fragment @app.route("/events") def events(): def stream(): yield Fragment("my_template.html", "live_block", items=...) return EventStream(stream) - 4
Run chirp check
Run
chirp check myapp:appto catch SSE scope violations before opening the browser.
See View Transitions + OOB for the full pattern.
Next Steps
- Return Values -- All the types you can return
- Fragments -- Deep dive into fragment rendering
- Streaming HTML -- Progressive page rendering
- Server-Sent Events -- Real-time updates