# Datastar Docs
Read the full-page docs at [data-star.dev/docs](https://data-star.dev/docs) for the best experience.
## Guide
### Getting Started
Datastar simplifies frontend development, allowing you to build backend-driven, interactive UIs using a [hypermedia-first](https://hypermedia.systems/hypermedia-a-reintroduction/) approach that extends and enhances HTML.
Datastar provides backend reactivity like [htmx](https://htmx.org/) and frontend reactivity like [Alpine.js](https://alpinejs.dev/) in a lightweight frontend framework that doesn’t require any npm packages or other dependencies. It provides two primary functions:
. Modify the DOM and state by sending events from your backend.
. Build reactivity into your frontend using standard `data-*` HTML attributes.
> Other useful resources include an AI-generated [deep wiki](https://deepwiki.com/starfederation/datastar), LLM-ingestible [code samples](https://context7.com/websites/data-star_dev), and [single-page docs](https://data-star.dev/docs).
## Installation
The quickest way to use Datastar is to include it using a `script` tag that fetches it from a CDN.
```
```
If you prefer to host the file yourself, download the [script](https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.7/bundles/datastar.js) or create your own bundle using the [bundler](https://data-star.dev/bundler), then include it from the appropriate path.
```
```
To import Datastar using a package manager such as npm, Deno, or Bun, you can use an import statement.
```
// @ts-expect-error (only required for TypeScript projects)
import 'https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.7/bundles/datastar.js'
```
## `data-*`
At the core of Datastar are `data-*` HTML attributes (hence the name). They allow you to add reactivity to your frontend and interact with your backend in a declarative way.
> The Datastar [VSCode extension](https://marketplace.visualstudio.com/items?itemName=starfederation.datastar-vscode) and [IntelliJ plugin](https://plugins.jetbrains.com/plugin/26072-datastar-support) provide autocompletion for all available `data-*` attributes.
The [`data-on`](https://data-star.dev/reference/attributes#data-on) attribute can be used to attach an event listener to an element and execute an expression whenever the event is triggered. The value of the attribute is a [Datastar expression](https://data-star.dev/guide/datastar_expressions) in which JavaScript can be used.
```
```
Demo
Open the pod bay doors, HAL.
We’ll explore more data attributes in the [next section of the guide](https://data-star.dev/guide/reactive_signals).
## Patching Elements
With Datastar, the backend *drives* the frontend by **patching** (adding, updating and removing) HTML elements in the DOM.
Datastar receives elements from the backend and manipulates the DOM using a morphing strategy (by default). Morphing ensures that only modified parts of the DOM are updated, and that only data attributes that have changed are [reapplied](https://data-star.dev/reference/attributes#attribute-evaluation-order), preserving state and improving performance.
Datastar provides [actions](https://data-star.dev/reference/actions#backend-actions) for sending requests to the backend. The [`@get()`](https://data-star.dev/reference/actions#get) action sends a `GET` request to the provided URL using a [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) request.
```
```
> Actions in Datastar are helper functions that have the syntax `@actionName()`. Read more about actions in the [reference](https://data-star.dev/reference/actions).
If the response has a `content-type` of `text/html`, the top-level HTML elements will be morphed into the existing DOM based on the element IDs.
```
I’m sorry, Dave. I’m afraid I can’t do that.
```
We call this a “Patch Elements” event because multiple elements can be patched into the DOM at once.
Demo
Open the pod bay doors, HAL. `Waiting for an order...`
In the example above, the DOM must contain an element with a `hal` ID in order for morphing to work. Other [patching strategies](https://data-star.dev/reference/sse_events#datastar-patch-elements) are available, but morph is the best and simplest choice in most scenarios.
If the response has a `content-type` of `text/event-stream`, it can contain zero or more [SSE events](https://data-star.dev/reference/sse_events). The example above can be replicated using a `datastar-patch-elements` SSE event.
```
event: datastar-patch-elements
data: elements
data: elements I’m sorry, Dave. I’m afraid I can’t do that.
data: elements
```
Because we can send as many events as we want in a stream, and because it can be a long-lived connection, we can extend the example above to first send HAL’s response and then, after a few seconds, reset the text.
```
event: datastar-patch-elements
data: elements
data: elements I’m sorry, Dave. I’m afraid I can’t do that.
data: elements
event: datastar-patch-elements
data: elements
data: elements Waiting for an order...
data: elements
```
Demo
Open the pod bay doors, HAL. `Waiting for an order...`
Here’s the code to generate the SSE events above using the SDKs.
```
;; Import the SDK's api and your adapter
(require
'[starfederation.datastar.clojure.api :as d*]
'[starfederation.datastar.clojure.adapter.http-kit :refer [->sse-response on-open]])
;; in a ring handler
(defn handler [request]
;; Create an SSE response
(->sse-response request
{on-open
(fn [sse]
;; Patches elements into the DOM
(d*/patch-elements! sse
"
I’m sorry, Dave. I’m afraid I can’t do that.
")
(Thread/sleep 1000)
(d*/patch-elements! sse
"
Waiting for an order...
"))}))
```
```
using StarFederation.Datastar.DependencyInjection;
// Adds Datastar as a service
builder.Services.AddDatastar();
app.MapGet("/", async (IDatastarService datastarService) =>
{
// Patches elements into the DOM.
await datastarService.PatchElementsAsync(@"
");
});
```
```
import (
"github.com/starfederation/datastar-go/datastar"
time
)
// Creates a new `ServerSentEventGenerator` instance.
sse := datastar.NewSSE(w,r)
// Patches elements into the DOM.
sse.PatchElements(
`
`
)
```
```
import starfederation.datastar.utils.ServerSentEventGenerator;
// Creates a new `ServerSentEventGenerator` instance.
AbstractResponseAdapter responseAdapter = new HttpServletResponseAdapter(response);
ServerSentEventGenerator generator = new ServerSentEventGenerator(responseAdapter);
// Patches elements into the DOM.
generator.send(PatchElements.builder()
.data("
")
.build()
);
```
```
val generator = ServerSentEventGenerator(response)
generator.patchElements(
elements = """
I’m sorry, Dave. I’m afraid I can’t do that.
""",
)
Thread.sleep(ONE_SECOND)
generator.patchElements(
elements = """
Waiting for an order...
""",
)
```
```
use starfederation\datastar\ServerSentEventGenerator;
// Creates a new `ServerSentEventGenerator` instance.
$sse = new ServerSentEventGenerator();
// Patches elements into the DOM.
$sse->patchElements(
'
I’m sorry, Dave. I’m afraid I can’t do that.
'
);
sleep(1);
$sse->patchElements(
'
Waiting for an order...
'
);
```
```
from datastar_py import ServerSentEventGenerator as SSE
from datastar_py.sanic import datastar_response
@app.get('/open-the-bay-doors')
@datastar_response
async def open_doors(request):
yield SSE.patch_elements('
')
```
```
require 'datastar'
# Create a Datastar::Dispatcher instance
datastar = Datastar.new(request:, response:)
# In a Rack handler, you can instantiate from the Rack env
# datastar = Datastar.from_rack_env(env)
# Start a streaming response
datastar.stream do |sse|
# Patches elements into the DOM.
sse.patch_elements %(
I’m sorry, Dave. I’m afraid I can’t do that.
)
sleep 1
sse.patch_elements %(
Waiting for an order...
)
end
```
```
use async_stream::stream;
use datastar::prelude::*;
use std::thread;
use std::time::Duration;
Sse(stream! {
// Patches elements into the DOM.
yield PatchElements::new("
").into();
})
```
```
// Creates a new `ServerSentEventGenerator` instance (this also sends required headers)
ServerSentEventGenerator.stream(req, res, (stream) => {
// Patches elements into the DOM.
stream.patchElements(`
I’m sorry, Dave. I’m afraid I can’t do that.
`);
setTimeout(() => {
stream.patchElements(`
Waiting for an order...
`);
}, 1000);
});
```
> In addition to your browser’s dev tools, the [Datastar Inspector](https://data-star.dev/datastar_pro#datastar-inspector) can be used to monitor and inspect SSE events received by Datastar.
We’ll cover event streams and [SSE events](https://data-star.dev/reference/sse_events) in more detail [later in the guide](https://data-star.dev/guide/backend_requests), but as you can see, they are just plain text events with a special syntax, made simpler by the [SDKs](https://data-star.dev/reference/sdks).
### Reactive Signals
In a hypermedia approach, the backend drives state to the frontend and acts as the primary source of truth. It’s up to the backend to determine what actions the user can take next by patching appropriate elements in the DOM.
Sometimes, however, you may need access to frontend state that’s driven by user interactions. Click, input and keydown events are some of the more common user events that you’ll want your frontend to be able to react to.
Datastar uses *signals* to manage frontend state. You can think of signals as reactive variables that automatically track and propagate changes in and to [Datastar expressions](https://data-star.dev/guide/datastar_expressions). Signals are denoted using the `$` prefix.
## Data Attributes
Datastar allows you to add reactivity to your frontend and interact with your backend in a declarative way using [custom `data-*` attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/data-*).
> The Datastar [VSCode extension](https://marketplace.visualstudio.com/items?itemName=starfederation.datastar-vscode) and [IntelliJ plugin](https://plugins.jetbrains.com/plugin/26072-datastar-support) provide autocompletion for all available `data-*` attributes.
### `data-bind`
The [`data-bind`](https://data-star.dev/reference/attributes#data-bind) attribute sets up two-way data binding on any HTML element that receives user input or selections. These include `input`, `textarea`, `select`, `checkbox` and `radio` elements, as well as web components whose value can be made reactive.
```
```
This creates a new signal that can be called using `$foo`, and binds it to the element’s value. If either is changed, the other automatically updates.
You can accomplish the same thing passing the signal name as a *value*. This syntax can be more convenient to use with some templating languages.
```
```
According to the [HTML spec](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*), all [`data-*`](https://developer.mozilla.org/en-US/docs/Web/HTML/How_to/Use_data_attributes) attributes are case-insensitive. When Datastar processes these attributes, hyphenated names are automatically converted to camel case by removing hyphens and uppercasing the letter following each hyphen. For example, `data-bind:foo-bar` creates a signal named `$fooBar`.
```
```
Read more about [attribute casing](https://data-star.dev/reference/attributes#attribute-casing) in the reference.
### `data-text`
The [`data-text`](https://data-star.dev/reference/attributes#data-text) attribute sets the text content of an element to the value of a signal. The `$` prefix is required to denote a signal.
```
```
Demo
```
```
The value of the `data-text` attribute is a [Datastar expression](https://data-star.dev/guide/datastar_expressions) that is evaluated, meaning that we can use JavaScript in it.
```
```
Demo
```
```
### `data-computed`
The [`data-computed`](https://data-star.dev/reference/attributes#data-computed) attribute creates a new signal that is derived from a reactive expression. The computed signal is read-only, and its value is automatically updated when any signals in the expression are updated.
```
```
This results in the `$repeated` signal’s value always being equal to the value of the `$fooBar` signal repeated twice. Computed signals are useful for memoizing expressions containing other signals.
Demo
```
```
### `data-show`
The [`data-show`](https://data-star.dev/reference/attributes#data-show) attribute can be used to show or hide an element based on whether an expression evaluates to `true` or `false`.
```
```
This results in the button being visible only when the input value is *not* an empty string. This could also be shortened to `data-show="$fooBar"`.
Demo
Save
Since the button is visible until Datastar processes the `data-show` attribute, it’s a good idea to set its initial style to `display: none` to prevent a flash of unwanted content.
```
```
### `data-class`
The [`data-class`](https://data-star.dev/reference/attributes#data-class) attribute allows us to add or remove an element’s class based on an expression.
```
```
If the expression evaluates to `true`, the `success` class is added to the element, otherwise it is removed.
Demo
Save
Unlike the `data-bind` attribute, in which hyphenated names are converted to camel case, the `data-class` attribute converts the class name to kebab case. For example, `data-class:font-bold` adds or removes the `font-bold` class.
```
```
The `data-class` attribute can also be used to add or remove multiple classes from an element using a set of key-value pairs, where the keys represent class names and the values represent expressions.
```
```
Note how the `font-bold` key must be wrapped in quotes because it contains a hyphen.
### `data-attr`
The [`data-attr`](https://data-star.dev/reference/attributes#data-attr) attribute can be used to bind the value of any HTML attribute to an expression.
```
```
This results in a `disabled` attribute being given the value `true` whenever the input is an empty string.
Demo
Save
The `data-attr` attribute also converts the attribute name to kebab case, since HTML attributes are typically written in kebab case. For example, `data-attr:aria-hidden` sets the value of the `aria-hidden` attribute.
```
```
The `data-attr` attribute can also be used to set the values of multiple attributes on an element using a set of key-value pairs, where the keys represent attribute names and the values represent expressions.
```
```
Note how the `aria-hidden` key must be wrapped in quotes because it contains a hyphen.
### `data-signals`
Signals are globally accessible from anywhere in the DOM. So far, we’ve created signals on the fly using `data-bind` and `data-computed`. If a signal is used without having been created, it will be created automatically and its value set to an empty string.
Another way to create signals is using the [`data-signals`](https://data-star.dev/reference/attributes#data-signals) attribute, which patches (adds, updates or removes) one or more signals into the existing signals.
```
```
Signals can be nested using dot-notation.
```
```
Like the `data-bind` attribute, hyphenated names used with `data-signals` are automatically converted to camel case by removing hyphens and uppercasing the letter following each hyphen.
```
```
The `data-signals` attribute can also be used to patch multiple signals using a set of key-value pairs, where the keys represent signal names and the values represent expressions. Nested signals can be created using nested objects.
```
```
### `data-on`
The [`data-on`](https://data-star.dev/reference/attributes#data-on) attribute can be used to attach an event listener to an element and run an expression whenever the event is triggered.
```
```
This results in the `$foo` signal’s value being set to an empty string whenever the button element is clicked. This can be used with any valid event name such as `data-on:keydown`, `data-on:mouseover`, etc.
Demo
Reset
Custom events can also be used. Like the `data-class` attribute, the `data-on` attribute converts the event name to kebab case. For example, `data-on:custom-event` listens for the `custom-event` event.
```
```
These are just *some* of the attributes available in Datastar. For a complete list, see the [attribute reference](https://data-star.dev/reference/attributes).
## Frontend Reactivity
Datastar’s data attributes enable declarative signals and expressions, providing a simple yet powerful way to add reactivity to the frontend.
Datastar expressions are strings that are evaluated by Datastar [attributes](https://data-star.dev/reference/attributes) and [actions](https://data-star.dev/reference/actions). While they are similar to JavaScript, there are some important differences that are explained in the [next section of the guide](https://data-star.dev/guide/datastar_expressions).
```
```
Demo
HAL, do you read me?
```
```
See if you can figure out what the code below does based on what you’ve learned so far, *before* trying the demo below it.
```
What do you put in a toaster?
You answered “”.
That is correct ✅
The correct answer is “
” 🤷
```
Demo
What do you put in a toaster?
BUZZ
You answered “”. That is correct ✅ The correct answer is “bread” 🤷
> The [Datastar Inspector](https://data-star.dev/datastar_pro#datastar-inspector) can be used to inspect and filter current signals and view signal patch events in real-time.
## Patching Signals
Remember that in a hypermedia approach, the backend drives state to the frontend. Just like with elements, frontend signals can be **patched** (added, updated and removed) from the backend using [backend actions](https://data-star.dev/reference/actions#backend-actions).
```
```
If a response has a `content-type` of `application/json`, the signal values are patched into the frontend signals.
We call this a “Patch Signals” event because multiple signals can be patched (using [JSON Merge Patch RFC 7396](https://datatracker.ietf.org/doc/rfc7396/)) into the existing signals.
```
{"hal": "Affirmative, Dave. I read you."}
```
Demo
HAL, do you read me?
Reset
If the response has a `content-type` of `text/event-stream`, it can contain zero or more [SSE events](https://data-star.dev/reference/sse_events). The example above can be replicated using a `datastar-patch-signals` SSE event.
```
event: datastar-patch-signals
data: signals {hal: 'Affirmative, Dave. I read you.'}
```
Because we can send as many events as we want in a stream, and because it can be a long-lived connection, we can extend the example above to first set the `hal` signal to an “affirmative” response and then, after a second, reset the signal.
```
event: datastar-patch-signals
data: signals {hal: 'Affirmative, Dave. I read you.'}
// Wait 1 second
event: datastar-patch-signals
data: signals {hal: '...'}
```
Demo
HAL, do you read me?
Here’s the code to generate the SSE events above using the SDKs.
```
;; Import the SDK's api and your adapter
(require
'[starfederation.datastar.clojure.api :as d*]
'[starfederation.datastar.clojure.adapter.http-kit :refer [->sse-response on-open]])
;; in a ring handler
(defn handler [request]
;; Create an SSE response
(->sse-response request
{on-open
(fn [sse]
;; Patches signal.
(d*/patch-signals! sse "{hal: 'Affirmative, Dave. I read you.'}")
(Thread/sleep 1000)
(d*/patch-signals! sse "{hal: '...'}"))}))
```
```
using StarFederation.Datastar.DependencyInjection;
// Adds Datastar as a service
builder.Services.AddDatastar();
app.MapGet("/hal", async (IDatastarService datastarService) =>
{
// Patches signals.
await datastarService.PatchSignalsAsync(new { hal = "Affirmative, Dave. I read you" });
await Task.Delay(TimeSpan.FromSeconds(3));
await datastarService.PatchSignalsAsync(new { hal = "..." });
});
```
```
import (
"github.com/starfederation/datastar-go/datastar"
)
// Creates a new `ServerSentEventGenerator` instance.
sse := datastar.NewSSE(w, r)
// Patches signals
sse.PatchSignals([]byte(`{hal: 'Affirmative, Dave. I read you.'}`))
time.Sleep(1 * time.Second)
sse.PatchSignals([]byte(`{hal: '...'}`))
```
```
import starfederation.datastar.utils.ServerSentEventGenerator;
// Creates a new `ServerSentEventGenerator` instance.
AbstractResponseAdapter responseAdapter = new HttpServletResponseAdapter(response);
ServerSentEventGenerator generator = new ServerSentEventGenerator(responseAdapter);
// Patches signals.
generator.send(PatchSignals.builder()
.data("{\"hal\": \"Affirmative, Dave. I read you.\"}")
.build()
);
Thread.sleep(1000);
generator.send(PatchSignals.builder()
.data("{\"hal\": \"...\"}")
.build()
);
```
```
val generator = ServerSentEventGenerator(response)
generator.patchSignals(
signals = """{"hal": "Affirmative, Dave. I read you."}""",
)
Thread.sleep(ONE_SECOND)
generator.patchSignals(
signals = """{"hal": "..."}""",
)
```
```
use starfederation\datastar\ServerSentEventGenerator;
// Creates a new `ServerSentEventGenerator` instance.
$sse = new ServerSentEventGenerator();
// Patches signals.
$sse->patchSignals(['hal' => 'Affirmative, Dave. I read you.']);
sleep(1);
$sse->patchSignals(['hal' => '...']);
```
```
from datastar_py import ServerSentEventGenerator as SSE
from datastar_py.sanic import datastar_response
@app.get('/do-you-read-me')
@datastar_response
async def open_doors(request):
yield SSE.patch_signals({"hal": "Affirmative, Dave. I read you."})
await asyncio.sleep(1)
yield SSE.patch_signals({"hal": "..."})
```
```
require 'datastar'
# Create a Datastar::Dispatcher instance
datastar = Datastar.new(request:, response:)
# In a Rack handler, you can instantiate from the Rack env
# datastar = Datastar.from_rack_env(env)
# Start a streaming response
datastar.stream do |sse|
# Patches signals
sse.patch_signals(hal: 'Affirmative, Dave. I read you.')
sleep 1
sse.patch_signals(hal: '...')
end
```
```
use async_stream::stream;
use datastar::prelude::*;
use std::thread;
use std::time::Duration;
Sse(stream! {
// Patches signals.
yield PatchSignals::new("{hal: 'Affirmative, Dave. I read you.'}").into();
thread::sleep(Duration::from_secs(1));
yield PatchSignals::new("{hal: '...'}").into();
})
```
```
// Creates a new `ServerSentEventGenerator` instance (this also sends required headers)
ServerSentEventGenerator.stream(req, res, (stream) => {
// Patches signals.
stream.patchSignals({'hal': 'Affirmative, Dave. I read you.'});
setTimeout(() => {
stream.patchSignals({'hal': '...'});
}, 1000);
});
```
> In addition to your browser’s dev tools, the [Datastar Inspector](https://data-star.dev/datastar_pro#datastar-inspector) can be used to monitor and inspect SSE events received by Datastar.
We’ll cover event streams and [SSE events](https://data-star.dev/reference/sse_events) in more detail [later in the guide](https://data-star.dev/guide/backend_requests), but as you can see, they are just plain text events with a special syntax, made simpler by the [SDKs](https://data-star.dev/reference/sdks).
### Datastar Expressions
Datastar expressions are strings that are evaluated by `data-*` attributes. While they are similar to JavaScript, there are some important differences that make them more powerful for declarative hypermedia applications.
## Datastar Expressions
The following example outputs `1` because we’ve defined `foo` as a signal with the initial value `1`, and are using `$foo` in a `data-*` attribute.
```
```
A variable `el` is available in every Datastar expression, representing the element that the attribute is attached to.
```
```
When Datastar evaluates the expression `$foo`, it first converts it to the signal value, and then evaluates that expression in a sandboxed context. This means that JavaScript can be used in Datastar expressions.
```
```
JavaScript operators are also available in Datastar expressions. This includes (but is not limited to) the ternary operator `?:`, the logical OR operator `||`, and the logical AND operator `&&`. These operators are helpful in keeping Datastar expressions terse.
```
// Output one of two values, depending on the truthiness of a signal
// Show a countdown if the signal is truthy or the time remaining is less than 10 seconds
Countdown
// Only send a request if the signal is truthy
```
Multiple statements can be used in a single expression by separating them with a semicolon.
```
```
Expressions may span multiple lines, but a semicolon must be used to separate statements. Unlike JavaScript, line breaks alone are not sufficient to separate statements.
```
```
## Using JavaScript
Most of your JavaScript logic should go in `data-*` attributes, since reactive signals and actions only work in [Datastar expressions](https://data-star.dev/guide/datastar_expressions).
> Caution: if you find yourself trying to do too much in Datastar expressions, **you are probably overcomplicating it™**.
Any JavaScript functionality you require that cannot belong in `data-*` attributes should be extracted out into [external scripts](#external-scripts) or, better yet, [web components](#web-components).
> Always encapsulate state and send **props down, events up**.
### External Scripts
When using external scripts, you should pass data into functions via arguments and return a result. Alternatively, listen for custom events dispatched from them (props down, events up).
In this way, the function is encapsulated – all it knows is that it receives input via an argument, acts on it, and optionally returns a result or dispatches a custom event – and `data-*` attributes can be used to drive reactivity.
```
```
```
function myfunction(data) {
return `You entered: ${data}`;
}
```
If your function call is asynchronous then it will need to dispatch a custom event containing the result. While asynchronous code *can* be placed within Datastar expressions, Datastar will *not* await it.
```
```
```
async function myfunction(element, data) {
const value = await new Promise((resolve) => {
setTimeout(() => resolve(`You entered: ${data}`), 1000);
});
element.dispatchEvent(
new CustomEvent('mycustomevent', {detail: {value}})
);
}
```
See the [sortable example](https://data-star.dev/examples/sortable).
### Web Components
[Web components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) allow you create reusable, encapsulated, custom elements. They are native to the web and require no external libraries or frameworks. Web components unlock [custom elements](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements) – HTML tags with custom behavior and styling.
When using web components, pass data into them via attributes and listen for custom events dispatched from them (*props down, events up*).
In this way, the web component is encapsulated – all it knows is that it receives input via an attribute, acts on it, and optionally dispatches a custom event containing the result – and `data-*` attributes can be used to drive reactivity.
```
```
```
class MyComponent extends HTMLElement {
static get observedAttributes() {
return ['src'];
}
attributeChangedCallback(name, oldValue, newValue) {
const value = `You entered: ${newValue}`;
this.dispatchEvent(
new CustomEvent('mycustomevent', {detail: {value}})
);
}
}
customElements.define('my-component', MyComponent);
```
Since the `value` attribute is allowed on web components, it is also possible to use `data-bind` to bind a signal to the web component’s value. Note that a `change` event must be dispatched so that the event listener used by `data-bind` is triggered by the value change.
See the [web component example](https://data-star.dev/examples/web_component).
## Executing Scripts
Just like elements and signals, the backend can also send JavaScript to be executed on the frontend using [backend actions](https://data-star.dev/reference/actions#backend-actions).
```
```
If a response has a `content-type` of `text/javascript`, the value will be executed as JavaScript in the browser.
```
alert('This mission is too important for me to allow you to jeopardize it.')
```
Demo
What are you talking about, HAL?
If the response has a `content-type` of `text/event-stream`, it can contain zero or more [SSE events](https://data-star.dev/reference/sse_events). The example above can be replicated by including a `script` tag inside of a `datastar-patch-elements` SSE event.
```
event: datastar-patch-elements
data: elements
data: elements
data: elements
```
If you *only* want to execute a script, you can `append` the script tag to the `body`.
```
event: datastar-patch-elements
data: mode append
data: selector body
data: elements
```
Most SDKs have an `ExecuteScript` helper function for executing a script. Here’s the code to generate the SSE event above using the Go SDK.
```
sse := datastar.NewSSE(writer, request)
sse.ExecuteScript(`alert('This mission is too important for me to allow you to jeopardize it.')`)
```
Demo
What are you talking about, HAL?
We’ll cover event streams and [SSE events](https://data-star.dev/reference/sse_events) in more detail [later in the guide](https://data-star.dev/guide/backend_requests), but as you can see, they are just plain text events with a special syntax, made simpler by the [SDKs](https://data-star.dev/reference/sdks).
### Backend Requests
Between [attributes](https://data-star.dev/reference/attributes) and [actions](https://data-star.dev/reference/actions), Datastar provides you with everything you need to build hypermedia-driven applications. Using this approach, the backend drives state to the frontend and acts as the single source of truth, determining what actions the user can take next.
## Sending Signals
By default, all signals (except for local signals whose keys begin with an underscore) are sent in an object with every backend request. When using a `GET` request, the signals are sent as a `datastar` query parameter, otherwise they are sent as a JSON body.
By sending **all** signals in every request, the backend has full access to the frontend state. This is by design. It is **not** recommended to send partial signals, but if you must, you can use the [`filterSignals`](https://data-star.dev/reference/actions#filterSignals) option to filter the signals sent to the backend.
### Nesting Signals
Signals can be nested, making it easier to target signals in a more granular way on the backend.
Using dot-notation:
```
```
Using object syntax:
```
```
Using two-way binding:
```
```
A practical use-case of nested signals is when you have repetition of state on a page. The following example tracks the open/closed state of a menu on both desktop and mobile devices, and the [toggleAll()](https://data-star.dev/reference/actions#toggleAll) action to toggle the state of all menus at once.
```
```
## Reading Signals
To read signals from the backend, JSON decode the `datastar` query param for `GET` requests, and the request body for all other methods.
All [SDKs](https://data-star.dev/reference/sdks) provide a helper function to read signals. Here’s how you would read the nested signal `foo.bar` from an incoming request.
```
using StarFederation.Datastar.DependencyInjection;
// Adds Datastar as a service
builder.Services.AddDatastar();
public record Signals
{
[JsonPropertyName("foo")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public FooSignals? Foo { get; set; } = null;
public record FooSignals
{
[JsonPropertyName("bar")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Bar { get; set; }
}
}
app.MapGet("/read-signals", async (IDatastarService datastarService) =>
{
Signals? mySignals = await datastarService.ReadSignalsAsync();
var bar = mySignals?.Foo?.Bar;
});
```
```
import ("github.com/starfederation/datastar-go/datastar")
type Signals struct {
Foo struct {
Bar string `json:"bar"`
} `json:"foo"`
}
signals := &Signals{}
if err := datastar.ReadSignals(request, signals); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
```
```
@Serializable
data class Signals(
val foo: String,
)
val jsonUnmarshaller: JsonUnmarshaller = { json -> Json.decodeFromString(json) }
val request: Request =
postRequest(
body =
"""
{
"foo": "bar"
}
""".trimIndent(),
)
val signals = readSignals(request, jsonUnmarshaller)
```
```
use starfederation\datastar\ServerSentEventGenerator;
// Reads all signals from the request.
$signals = ServerSentEventGenerator::readSignals();
```
```
from datastar_py.fastapi import datastar_response, read_signals
@app.get("/updates")
@datastar_response
async def updates(request: Request):
# Retrieve a dictionary with the current state of the signals from the frontend
signals = await read_signals(request)
```
```
# Setup with request
datastar = Datastar.new(request:, response:)
# Read signals
some_signal = datastar.signals[:some_signal]
```
## SSE Events
Datastar can stream zero or more [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) (SSE) from the web server to the browser. There’s no special backend plumbing required to use SSE, just some special syntax. Fortunately, SSE is straightforward and [provides us with some advantages](https://data-star.dev/essays/event_streams_all_the_way_down), in addition to allowing us to send multiple events in a single response (in contrast to sending `text/html` or `application/json` responses).
First, set up your backend in the language of your choice. Familiarize yourself with [sending SSE events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#sending_events_from_the_server), or use one of the backend [SDKs](https://data-star.dev/reference/sdks) to get up and running even faster. We’re going to use the SDKs in the examples below, which set the appropriate headers and format the events for us.
The following code would exist in a controller action endpoint in your backend.
```
;; Import the SDK's api and your adapter
(require
'[starfederation.datastar.clojure.api :as d*]
'[starfederation.datastar.clojure.adapter.http-kit :refer [->sse-response on-open]])
;; in a ring handler
(defn handler [request]
;; Create an SSE response
(->sse-response request
{on-open
(fn [sse]
;; Patches elements into the DOM
(d*/patch-elements! sse
"
What do you put in a toaster?
")
;; Patches signals
(d*/patch-signals! sse "{response: '', answer: 'bread'}"))}))
```
```
using StarFederation.Datastar.DependencyInjection;
// Adds Datastar as a service
builder.Services.AddDatastar();
app.MapGet("/", async (IDatastarService datastarService) =>
{
// Patches elements into the DOM.
await datastarService.PatchElementsAsync(@"
What do you put in a toaster?
");
// Patches signals.
await datastarService.PatchSignalsAsync(new { response = "", answer = "bread" });
});
```
```
import ("github.com/starfederation/datastar-go/datastar")
// Creates a new `ServerSentEventGenerator` instance.
sse := datastar.NewSSE(w,r)
// Patches elements into the DOM.
sse.PatchElements(
`
What do you put in a toaster?
`
)
// Patches signals.
sse.PatchSignals([]byte(`{response: '', answer: 'bread'}`))
```
```
import starfederation.datastar.utils.ServerSentEventGenerator;
// Creates a new `ServerSentEventGenerator` instance.
AbstractResponseAdapter responseAdapter = new HttpServletResponseAdapter(response);
ServerSentEventGenerator generator = new ServerSentEventGenerator(responseAdapter);
// Patches elements into the DOM.
generator.send(PatchElements.builder()
.data("
""",
)
generator.patchSignals(
signals = """{"response": "", "answer": "bread"}""",
)
```
```
use starfederation\datastar\ServerSentEventGenerator;
// Creates a new `ServerSentEventGenerator` instance.
$sse = new ServerSentEventGenerator();
// Patches elements into the DOM.
$sse->patchElements(
'
What do you put in a toaster?
'
);
// Patches signals.
$sse->patchSignals(['response' => '', 'answer' => 'bread']);
```
```
from datastar_py import ServerSentEventGenerator as SSE
from datastar_py.litestar import DatastarResponse
async def endpoint():
return DatastarResponse([
SSE.patch_elements('
What do you put in a toaster?
'),
SSE.patch_signals({"response": "", "answer": "bread"})
])
```
```
require 'datastar'
# Create a Datastar::Dispatcher instance
datastar = Datastar.new(request:, response:)
# In a Rack handler, you can instantiate from the Rack env
# datastar = Datastar.from_rack_env(env)
# Start a streaming response
datastar.stream do |sse|
# Patches elements into the DOM
sse.patch_elements %(
What do you put in a toaster?
)
# Patches signals
sse.patch_signals(response: '', answer: 'bread')
end
```
```
use datastar::prelude::*;
use async_stream::stream;
Sse(stream! {
// Patches elements into the DOM.
yield PatchElements::new("
What do you put in a toaster?
").into();
// Patches signals.
yield PatchSignals::new("{response: '', answer: 'bread'}").into();
})
```
```
// Creates a new `ServerSentEventGenerator` instance (this also sends required headers)
ServerSentEventGenerator.stream(req, res, (stream) => {
// Patches elements into the DOM.
stream.patchElements(`
What do you put in a toaster?
`);
// Patches signals.
stream.patchSignals({'response': '', 'answer': 'bread'});
});
```
The `PatchElements()` function updates the provided HTML element into the DOM, replacing the element with `id="question"`. An element with the ID `question` must *already* exist in the DOM.
The `PatchSignals()` function updates the `response` and `answer` signals into the frontend signals.
With our backend in place, we can now use the `data-on:click` attribute to trigger the [`@get()`](https://data-star.dev/reference/actions#get) action, which sends a `GET` request to the `/actions/quiz` endpoint on the server when a button is clicked.
```
You answered “”.
That is correct ✅
The correct answer is “” 🤷
```
Now when the `Fetch a question` button is clicked, the server will respond with an event to modify the `question` element in the DOM and an event to modify the `response` and `answer` signals. We’re driving state from the backend!
Demo
...
Fetch a question BUZZ
You answered “”. That is correct ✅ The correct answer is “” 🤷
### `data-indicator`
The [`data-indicator`](https://data-star.dev/reference/attributes#data-indicator) attribute sets the value of a signal to `true` while the request is in flight, otherwise `false`. We can use this signal to show a loading indicator, which may be desirable for slower responses.
```
```
Demo
...
Fetch a question

## Backend Actions
We’re not limited to sending just `GET` requests. Datastar provides [backend actions](https://data-star.dev/reference/actions#backend-actions) for each of the methods available: `@get()`, `@post()`, `@put()`, `@patch()` and `@delete()`.
Here’s how we can send an answer to the server for processing, using a `POST` request.
```
```
One of the benefits of using SSE is that we can send multiple events (patch elements and patch signals) in a single response.
```
(d*/patch-elements! sse "
');
stream.patchSignals({'answer': '...', 'prize': '...'});
```
> In addition to your browser’s dev tools, the [Datastar Inspector](https://data-star.dev/datastar_pro#datastar-inspector) can be used to monitor and inspect SSE events received by Datastar.
Read more about SSE events in the [reference](https://data-star.dev/reference/sse_events).
## Congratulations
You’ve actually read the entire guide! You should now know how to use Datastar to build reactive applications that communicate with the backend using backend requests and SSE events.
Feel free to dive into the [reference](https://data-star.dev/reference) and explore the [examples](https://data-star.dev/examples) next, to learn more about what you can do with Datastar.
### The Tao of Datastar
Datastar is just a tool. The Tao of Datastar, or “the Datastar way” as it is often referred to, is a set of opinions from the core team on how to best use Datastar to build maintainable, scalable, high-performance web apps.
Ignore them at your own peril!

## State in the Right Place
Most state should live in the backend. Since the frontend is exposed to the user, the backend should be the source of truth for your application state.
## Start with the Defaults
The default configuration options are the recommended settings for the majority of applications. Start with the defaults, and before you ever get tempted to change them, stop and ask yourself, [well... how did I get here?](https://youtu.be/5IsSpAOD6K8)
## Patch Elements & Signals
Since the backend is the source of truth, it should *drive* the frontend by **patching** (adding, updating and removing) HTML elements and signals.
## Use Signals Sparingly
Overusing signals typically indicates trying to manage state on the frontend. Favor fetching current state from the backend rather than pre-loading and assuming frontend state is current. A good rule of thumb is to *only* use signals for user interactions (e.g. toggling element visibility) and for sending new state to the backend (e.g. by binding signals to form input elements).
## In Morph We Trust
Morphing ensures that only modified parts of the DOM are updated, preserving state and improving performance. This allows you to send down large chunks of the DOM tree (all the way up to the `html` tag), sometimes known as “fat morph”, rather than trying to manage fine-grained updates yourself. If you want to explicitly ignore morphing an element, place the [`data-ignore-morph`](https://data-star.dev/reference/attributes#data-ignore-morph) attribute on it.
## SSE Responses
[SSE](https://html.spec.whatwg.org/multipage/server-sent-events.html) responses allow you to send `0` to `n` events, in which you can [patch elements](https://data-star.dev/guide/getting_started/#patching-elements), [patch signals](https://data-star.dev/guide/reactive_signals#patching-signals), and [execute scripts](https://data-star.dev/guide/datastar_expressions#executing-scripts). Since event streams are just HTTP responses with some special formatting that [SDKs](https://data-star.dev/reference/sdks) can handle for you, there’s no real benefit to using a content type other than [`text/event-stream`](https://data-star.dev/reference/actions#response-handling).
## Compression
Since SSE responses stream events from the backend and morphing allows sending large chunks of DOM, compressing the response is a natural choice. Compression ratios of 200:1 are not uncommon when compressing streams using Brotli. Read more about compressing streams in [this article](https://andersmurphy.com/2025/04/15/why-you-should-use-brotli-sse.html).
## Backend Templating
Since your backend generates your HTML, you can and should use your templating language to [keep things DRY](https://data-star.dev/how_tos/keep_datastar_code_dry) (Don’t Repeat Yourself).
## Page Navigation
Page navigation hasn't changed in 30 years. Use the [anchor element](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/a) (``) to navigate to a new page, or a [redirect](https://data-star.dev/how_tos/redirect_the_page_from_the_backend) if redirecting from the backend. For smooth page transitions, use the [View Transition API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API).
## Browser History
Browsers automatically keep a history of pages visited. As soon as you start trying to manage browser history yourself, you are adding complexity. Each page is a resource. Use anchor tags and let the browser do what it is good at.
## CQRS
[CQRS](https://martinfowler.com/bliki/CQRS.html), in which commands (writes) and requests (reads) are segregated, makes it possible to have a single long-lived request to receive updates from the backend (reads), while making multiple short-lived requests to the backend (writes). It is a powerful pattern that makes real-time collaboration simple using Datastar. Here’s a basic example.
```