How to Use Datastar: Signals, Backend Actions, and SSE — From Zero to Your First Reactive Page
CodexFuse Team · 2026-11-06T13:02:09.507Z · 15 min read min read
How to Use Datastar: Signals, Backend Actions, and SSE — From Zero to Your First Reactive Page
Learning how to use Datastar takes about ten minutes if you start from the current release instead of a stale tutorial. Datastar is a hypermedia framework that lets a backend drive a reactive page through one small script tag, with no build step, no npm install, and no client-side JavaScript of your own.
To use Datastar, add the version 1.0.4 script tag to a page, seed state with data-signals, wire events with data-on attributes, and answer backend requests with server-sent events. The rest of this datastar tutorial follows that arc exactly, from a counter to a live search widget.
All facts in this guide are as of November 2026, and version numbers move fast, so treat the release details as a snapshot. CodexFuse offers no Datastar tool, so this guide teaches the open-source framework itself, step by step, with every version and code sample taken from the official docs and the project repository.
What Is Datastar?
Datastar is a lightweight hypermedia framework for JavaScript developers that builds reactive web applications with server-side rendering and the power of a full-stack single-page application framework. It gives you backend reactivity like htmx and frontend reactivity like Alpine.js in one script tag of about 12 kilobytes. If you know htmx already, our htmx beginner guide covers the sibling approach, while Datastar adds client-side signals to the same server-driven model.
The core idea is that the server owns the state. The browser holds reactive variables called signals, the server sends HTML fragments and signal updates over server-sent events, and the page updates without a reload. You write no client-side JavaScript, because every behaviour is declared with data-* attributes in the HTML, which is why Datastar is often described as a hypermedia framework JavaScript developers can adopt without a build step.
Datastar is young but not tiny. The repository at starfederation/datastar holds 5,222 stars and 222 forks as of 6 November 2026, it carries the MIT licence, and it has shipped 37 releases since it was created on 28 August 2023. Version 1.0.4 is the current release, published on 21 September 2026, which makes it 46 days old on 6 November 2026, and that works out to 23.5 stars per fork.
How Do You Use Datastar?
The whole arc fits in seven steps. Follow them in order and you will have a working reactive widget by the end of the page.
- Add the version 1.0.4 script tag to a plain HTML page.
- Seed a signal with data-signals and show it with data-text.
- Bind an input with data-bind and derive a value with data-computed.
- Call your backend with @get or @post from a data-on attribute.
- Answer with the two server-sent event types, datastar-patch-elements and datastar-patch-signals.
- Combine them into one complete widget, a live search box.
- Pick an SDK for your backend language and ship it.
Each step below teaches one piece and nothing else. By step six you will have the complete widget, and step seven tells you how to run it in production.
Step 1: Add the Script Tag
The fastest install is one line in the head of your page. Use the version 1.0.4 tag exactly, because the official Getting Started guide still prints an older release candidate tag and copying it pins you to a pre-release build.
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/[email protected]/bundles/datastar.js"></script>
That tag is about 12 kilobytes, and there is no build step, no package to install, and no bundler to configure. The docs recommend hosting the file yourself once you ship, and the same bundle is also available as an import for npm, Deno, or Bun.
A separate bundle, datastar-rocket.js, adds the Rocket component system on top of Datastar. Rocket became a free bundle in version 1.0.4, so you do not need a paid tier to use it. The Datastar Pro tier still exists for a small set of extra attributes and actions, and this guide only uses the free ones.
Step 2: Seed Your First Signal and Build a Counter
Datastar signals are reactive variables, and you seed them with the datastar data-signals attribute. A signal called count becomes $count everywhere in your expressions, and any element that reads it updates itself when the value changes.
<div data-signals="{count: 0}">
<div data-text="$count"></div>
<button data-on:click="$count++">Count up</button>
</div>
The button uses data-on:click with the expression $count++. The colon matters, because the current spelling is data-on:click and the older dash spelling data-on-click stopped working in release candidate 6. Many tutorials still show the dash form, so copy the colon form from this guide.
Signals are globally accessible from anywhere in the document, and using a signal that does not exist yet creates it as an empty string. Setting a signal to null deletes it. Signals whose names start with an underscore, such as $_menuOpen, stay local and are never sent to the server.
Step 3: Bind Inputs and Derive Values
Two-way binding connects an input to a signal with data-bind. Type in the box and the signal updates, and update the signal and the box follows. Add data-computed for values that derive from other signals, such as a character count or a formatted total.
<div data-signals="{name: ''}">
<input data-bind:name placeholder="Type your name">
<div data-text="'Hello, ' + $name"></div>
<div data-text="'That is ' + $name.length + ' characters'"></div>
</div>
The data-text attribute renders a signal or expression as text inside an element. It re-renders automatically whenever any signal it reads changes, which is how the counter display stays in sync with zero JavaScript of your own.
Nested signals use dot notation, so data-signals:form.baz creates $form.baz. Hyphenated keys convert to camelCase, so data-signals:foo-bar creates $fooBar. These two rules cover nearly every form you will build.
Step 4: Call the Backend with @get and @post
Backend actions are helper functions that start with @ and live inside data-on expressions. The @get action sends a GET request to a URL, and @post, @put, @patch, and @delete work the same way with their own methods. Version 1.0.4 added @query, which sends a QUERY request with the signals in the body, so you get GET-like safety without URL length limits.
<button data-on:click="@post('/save')">Save</button>
<button data-on:click="@get('/profile')">Load profile</button>
Every backend action automatically sends all of your signals to the server, except local ones that start with an underscore. GET requests carry them as a datastar query parameter, and other methods send them as a JSON body. The request also carries a Datastar-Request header set to true, so your backend can tell a Datastar request from an ordinary fetch.
The response decides what happens next, and Datastar reads the Content-Type header to choose. A text/event-stream response is handled as server-sent events, text/html is morphed into the page by element ID, application/json is merged into your signals, and text/javascript is executed in the browser. An empty body means 204 No Content, and nothing changes.
Step 5: Understand SSE — the Two Events
Server-sent events are the whole backend protocol, and the datastar sse reference lists exactly two event types in version 1.0. The server sends them over one long-lived text/event-stream response, and each event ends with a blank line. Our server-sent events explainer covers the transport itself if SSE is new to you.
| Event | What it carries | How the browser uses it |
|---|---|---|
| datastar-patch-elements | An HTML fragment in the elements data line | Morphs the fragment into the DOM by matching top-level element IDs. Modes: outer (default), inner, replace, prepend, append, before, after, remove. |
| datastar-patch-signals | A signals object in the signals data line | Merges the values into browser signals. Setting a value to null removes that signal. |
The raw format of the two events looks like this. The first event patches an element, and the second event patches a signal, and the blank line between them is part of the protocol.
event: datastar-patch-elements
data: elements <div id="hal">I'm sorry, Dave. I'm afraid I can't do that.</div>
event: datastar-patch-signals
data: signals {hal: 'Affirmative, Dave. I read you.'}
Older event names still circulate in tutorials and auto-generated docs, including datastar-merge-fragments and datastar-execute-script. Those names are pre-1.0 and do not exist in version 1.0.4, so an example that uses them will silently do nothing. Script execution in version 1.0 happens through a datastar-patch-elements event that carries a script element.
Step 6: Build the Live Search Widget
Live search is the widget that shows every piece working together. The input binds to a query signal, each keystroke fires a debounced @get after 300 milliseconds, and the server answers with a patch-elements event that replaces the results list.
<div data-signals="{query: '', searching: false}">
<input data-bind:query
data-indicator:searching
data-on:input__debounce_300ms="@get('/search')">
<div data-show="$searching">Searching...</div>
<div id="results"></div>
</div>
The __debounce_300ms modifier waits 300 milliseconds after the last keystroke before sending, so fast typing sends one request instead of ten. The data-indicator attribute sets a boolean signal to true while a request is in flight, which lets you show a spinner with data-show.
Notice what the server does not send: no JSON, no template, and no client-side rendering code. It sends an HTML fragment and Datastar morphs it into the results element, preserving focus in the input because morphing only updates what changed.
Step 7: Pick an SDK for Your Backend
You do not need an SDK, because any backend that can write text/event-stream works, but an SDK removes the string formatting. The official SDKs page lists nineteen integrations, and the table below covers the ones most readers will reach for first. If you run your backend in a container, our Docker beginner guide shows the setup.
| Language | SDK | Notes |
|---|---|---|
| Go | datastar-go | Maintained by the project creator. NewSSE, then PatchElements. |
| C# / .NET | datastar-dotnet | AddDatastar service, then PatchElementsAsync. |
| Python | datastar-py | ServerSentEventGenerator, available on PyPI. |
| Ruby | datastar gem | Rack and Rails handlers. |
| PHP | datastar PHP SDK | Plus a Craft CMS plugin and a Laravel package. |
| TypeScript | datastar TypeScript SDK | Node, Deno, and Bun. |
| Rust | datastar Rust SDK | Plus the Rama integration. |
The Go example from the official guide is three lines: create the SSE writer, then patch elements. The .NET example registers the service and patches asynchronously. Pick the SDK for the language your backend already uses, and do not switch languages for Datastar alone.
If your language is not in the table, the raw event format from step five is the whole protocol. Any framework that can hold a response open and write text/event-stream lines can drive Datastar, including plain PHP, Python, Node, Rust, and serverless functions.
Datastar vs htmx: The One-Paragraph Difference
The official guide describes Datastar as backend reactivity like htmx and frontend reactivity like Alpine.js in a single lightweight framework. The creator's own essay says he tried to add these ideas to htmx version 2, the community did not accept the change, and he built Datastar so that everything is a plugin from the start.
| Feature | htmx | Datastar |
|---|---|---|
| Client-side reactivity | Add Alpine.js alongside htmx | Built-in signals, one system |
| How the server updates the page | Swaps HTML into the DOM | Streams SSE patch events, morphs by ID |
| What the browser sends up | Form data | All signals as JSON, automatically |
| When to pick it | The page needs no client-side state | The page also needs reactive inputs or live updates |
The honest summary is that htmx is great and the Datastar creator says he will continue to recommend it. Pick htmx when the page needs no client-side state, and pick Datastar when the same page also needs reactive inputs, derived values, or live updates without adding a second library.
The Version Trap: Why Most Tutorials Will Not Work on v1.0.4
This is the section that saves you an afternoon. The field is full of guides that look complete and use syntax that no longer works, because version 1.0.0 release candidate 6 changed the attribute delimiter from a dash to a colon.
Data-on-click became data-on:click, data-signals-foo became data-signals:foo, and data-on-load was renamed to data-init. Any tutorial showing the dash form predates release candidate 6 and will not work on version 1.0.4.
The staleness runs through the whole field. Roger Stringer's Astro guide from 26 September 2026 covers the full arc but cites release candidate 7, the Deep Tech Intuition deep dive covers it too but also cites release candidate 7, and the Bright Coding deep dive was updated on 6 November 2026 yet still shows pre-1.0 syntax and calls version 0.13.1 the latest stable. Our Astro beginner guide covers the framework Stringer pairs with Datastar if you want that stack.
| Version | Date | What changed |
|---|---|---|
| v1.0.0-RC.6 | 21 October 2025 | Colon delimiter, data-on-load renamed to data-init |
| v1.0.0-RC.7 | 16 December 2025 | Last release candidate |
| v1.0.0 | May 2026 | Version 1.0 final ships; launch episode dated 5 May 2026 |
| v1.0.3 | 27 August 2026 | Opt-in CSP mode, no unsafe-eval needed |
| v1.0.4 | 21 September 2026 | Rocket becomes free; @query action added |
The numbers below come from the repository itself, computed on 6 November 2026. The project is 1,166 days old, version 1.0.4 is 46 days old, and the launch episode for version 1.0.0 aired 185 days ago.
Release candidate 6 to release candidate 7 took 56 days, version 1.0.3 to 1.0.4 took 25 days, and the gap from release candidate 6 to version 1.0.4 is 335 days, which is how long the old syntax has been dead. The project also averages 141.1 stars per release across its 37 releases.
from datetime import date
d = lambda y, m, dd: date(y, m, dd)
today = d(2026, 11, 6)
repo_created = d(2023, 8, 28)
v104 = d(2026, 9, 21)
v100_launch = d(2026, 5, 5)
rc6 = d(2025, 10, 21)
rc7 = d(2025, 12, 16)
v103 = d(2026, 8, 27)
print("repo age, days:", (today - repo_created).days)
print("v1.0.4 age, days:", (today - v104).days)
print("days since v1.0 launch:", (today - v100_launch).days)
print("RC.6 to RC.7, days:", (rc7 - rc6).days)
print("v1.0.3 to v1.0.4, days:", (v104 - v103).days)
print("RC.6 to v1.0.4, days:", (v104 - rc6).days)
print("stars per fork:", round(5222 / 222, 1))
print("stars per release:", round(5222 / 37, 1))
One more version fact: version 1.0 moved several formerly free attributes to Datastar Pro, including data-persist and data-replace-url. Check the current docs before relying on an attribute a tutorial shows, because the free-versus-Pro line moved at version 1.0.
Common Datastar Mistakes (and the Fix for Each)
Most first-week Datastar bugs come from one of five mistakes. Each one has a one-line fix.
- Copying data-on-click from an old tutorial. Use data-on:click, the colon form, everywhere.
- Pasting the Getting Started guide's script tag verbatim. It still shows release candidate 6, so change the tag to @v1.0.4.
- Naming a signal with a leading underscore and wondering why the server never sees it. Underscore signals are local by design.
- Expecting datastar-merge-fragments to work. The version 1.0 event is datastar-patch-elements.
- Sending a request on every keystroke. Add __debounce_300ms to the data-on:input attribute.
One more habit to build early: keep the signal names short and the backend endpoints small. A signal per field and an endpoint per widget keeps the page readable when the app grows, and the server stays the single source of truth.
If you want to go further, the official reference pages for actions and server-sent events are the authority for every attribute and option in this guide. The GitHub repository is the second stop, and our GitHub beginner guide covers stars, forks, and releases if that workflow is new to you.
Sources
- Datastar Getting Started guide — https://data-star.dev/guide/getting_started
- Datastar reactive signals reference — https://data-star.dev/guide/reactive_signals
- Datastar backend actions reference — https://data-star.dev/reference/actions
- Datastar server-sent events reference — https://data-star.dev/reference/sse_events
- Datastar SDKs page — https://data-star.dev/reference/sdks
- Datastar, Why another framework? essay — https://data-star.dev/essays/why_another_framework
- Datastar GitHub repository — https://github.com/starfederation/datastar
- Datastar v1.0.0 release notes — https://github.com/starfederation/datastar/releases/tag/v1.0.0
- Datastar releases API — https://api.github.com/repos/starfederation/datastar/releases
- Datastar launch podcast episode — https://creators.spotify.com/pod/profile/datastar/
- Bright Coding, Building Reactive Web Apps with Datastar — https://www.blog.brightcoding.dev/2025/09/14/building-reactive-web-apps-with-datastar-a-deep-dive-into-the-hypermedia-framework
- Deep Tech Intuition, Datastar Deep Intuition — https://www.deeptechintuition.com/articles/datastar-deep-intuition
- Roger Stringer, Guide to datastar with Astro (PDF, 26 September 2026) — https://admin.rogerstringer.com/assets/a69c9214-def8-486b-a526-24bc3361593c
- eliteai.tools, datastar AI agent skill — https://eliteai.tools/agent-skills/datastar
- Datastar Haskell SDK, ExecuteScript docs — https://hackage-content.haskell.org/package/datastar-hs-0.1.0.1/docs/Hypermedia-Datastar-ExecuteScript.html
Frequently Asked Questions
Is Datastar 1.0 out?
Yes. Version 1.0.0 shipped in May 2026, and version 1.0.4 is the current release, published on 21 September 2026. The official Getting Started guide still shows a release candidate tag, so use @v1.0.4 in your script tag.
How is Datastar different from htmx?
Datastar adds built-in client-side signals to htmx-style server-driven HTML, and it streams updates over server-sent events with DOM morphing instead of HTML swaps. The official guide calls it backend reactivity like htmx plus frontend reactivity like Alpine.js in one framework.
Do I need to learn a backend language to use Datastar?
No. The frontend works with any backend that can return HTML or server-sent events, including plain PHP, Python, Node, and serverless functions. SDKs exist for Go, .NET, Python, Ruby, PHP, TypeScript, Rust, and more if you want helpers.
Does Datastar need a build step?
No. One script tag of about 12 kilobytes is the whole install, and there is no npm package or bundler to configure. The docs recommend self-hosting the file once you ship.
Is Datastar free?
Yes. The framework is MIT-licensed and free, and the Rocket component bundle became free in version 1.0.4. A Datastar Pro tier exists for a small set of extra attributes and actions, and this guide only uses the free ones.




