Datastar SDK

Datastar is the browser client. stario.datastar is the Python side. Attribute names and client behaviour live in the Datastar reference. Write the Python. The SDK emits the attributes. Why the split looks this way: Go-to architecture.

python
from stario.datastar import ModuleScript, SSE, at, data, read_signals

The Datastar reference is the API map.

Load the client

python
from stario.datastar import ModuleScript
from stario.markup import html as h
 
h.Head(
    ModuleScript(),
    # or a self-hosted file:
    # h.Script({"type": "module", "src": DATASTAR_JS}),
)

Self-host the file in production. ModuleScript() defaults to a CDN pin for experiments.

Views in Python

data.* returns attribute fragments. at.* returns the action string that goes inside them. Prefer at.fetch(route) when the endpoint is a Route — method and URL stay on one object.

python
from stario import Route
from stario.datastar import at, data
from stario.markup import html as h
 
SUBSCRIBE = Route.get("/subscribe")
PAINT = Route.post("/click")
 
 
def board():
    return h.Div(
        data.signals({"user_id": ""}, if_missing=True),
        data.init(at.fetch(SUBSCRIBE, retry="always")),
        h.Button(data.on("click", at.fetch(PAINT)), "Paint"),
    )

The browser sees data-init, data-on:click, @get, @post. at.get / at.post remain for raw URL strings.

Bind a field with data.bind. Show a signal with data.text.

python
h.Input(data.bind("email"), {"type": "email"})
h.Output(data.text("$count"))

Read what the page sent

Datastar sends one JSON object on each action. read_signals(req) is json.loads on that blob. Treat it as untrusted. Signal names on the wire are Python snake_case.

python
from stario.datastar import SSE, read_signals
 
 
async def increment(c, w):
    signals = await read_signals(c.req)
    count = int(signals.get("count") or 0)
    SSE(w).patch_signals({"count": count + 1})

Typed reads and file signals: Reading and writing signals.

Patch HTML on the stream

One SSE(w) per response. patch_elements morphs HTML. patch_signals merges JSON. Do not mix SSE with a completed response on the same writer.

python
from stario.datastar import SSE
 
 
async def subscribe(c, w):
    sse = SSE(w)
    sse.open()
    async with relay.subscribe("board") as live:
        async for _, _nudge in c.alive(live):
            sse.patch_elements(board_view(game), selector="#board")

A command returns 204 and publishes. Go-to architecture. Realtime tiles.

Use data-star.dev for an attribute modifier, a Pro feature, or the client runtime.