Core Concepts
Now that you've built your first Hyper application, let's consolidate your understanding by exploring the fundamental concepts that make Hyper work. This guide will help you develop a mental model for building reactive Laravel applications.
Signals: The Foundation of Reactivity
Signals are reactive variables that automatically update your user interface when their values change. Think of signals as the single source of truth for your application's state.
How Signals Work (Datastar)
When you create a signal in your HTML:
<div data-signals="{count: 0}">
<span data-text="$count"></span>
</div>Datastar creates a reactive variable called count with an initial value of 0. Any element that references this signal (using the $ prefix) automatically updates when the signal's value changes.
This reactivity is powered by Datastar's signal system. When you write $count = $count + 1, Datastar:
- Updates the signal's value in its internal store
- Identifies all elements watching that signal
- Automatically updates those elements with the new value
The $ prefix tells Datastar you're referencing a signal, not a regular JavaScript variable.
Signal Scope (Datastar)
Signals are globally scoped once created. This means a signal created anywhere on the page is accessible everywhere on that page:
<!-- Signal created in one div -->
<div data-signals="{username: 'John'}">
<p data-text="$username"></p>
</div>
<!-- Same signal accessible in another div -->
<div>
<p>Welcome back, <span data-text="$username"></span>!</p>
</div>Both elements will display "John" and update together if username changes. This global scope makes it easy to share state across your interface without prop drilling or complex state management.
Types of Signals
Hyper and Datastar support three types of signals, each with different behavior:
Regular Signals (Datastar)
Regular signals are sent to the server with every request:
<div data-signals="{count: 0, username: 'John'}">
<!-- These signals are sent to the server -->
</div>When you make a request using @postx, @patchx, or other HTTP actions, Datastar automatically includes all regular signals in the request payload. Your Laravel controller receives them and can access them using signals('count').
Local Signals (Datastar)
Local signals (prefixed with _) stay in the frontend and are never sent to the server:
<div data-signals="{count: 0, _showDetails: false}">
<!-- count is sent to server, _showDetails stays in browser -->
</div>Use local signals for UI state that doesn't need server processing:
- Accordion open/closed states
- Which tab is currently active
- Whether a dropdown menu is visible
- Form field focus states
Local signals improve performance by reducing the data sent over the network. However, the server can still update local signals using hyper()->signals(['_showDetails' => true]) if needed.
Locked Signals (Hyper Extension)
Locked signals (suffixed with _) are sent to the server and validated to detect tampering. Important: Locked signals can only be created using the @signals directive, not data-signals:
<div @signals(['userId_' => 123, 'role_' => 'admin'])>
<!-- These signals are protected from client-side modification -->
</div>When you create locked signals with @signals, Hyper:
- Stores their values securely in the session (encrypted)
- Validates they haven't changed on subsequent requests
- Throws a
HyperSignalTamperedExceptionif tampering is detected
This security only works through the @signals directive because it hooks into the PHP-side processing. Using data-signals with the _ suffix won't provide this protection.
Use locked signals for:
- User IDs and authentication data
- Pricing information
- Permission levels
- Any data that should only change through server actions
Security Deep Dive
Locked signals provide tamper protection for sensitive data. Learn the complete security model and session storage mechanism.
The Request Cycle
Understanding how data flows between your frontend and Laravel helps you build applications confidently.
Frontend to Server
When a user interacts with your application:
- User Action: User clicks a button with
data-on:click="@postx('/increment')" - Signal Collection: Datastar gathers all regular signals (not local
_signals) - Request: Datastar sends a POST request to
/incrementwith signals as JSON payload - CSRF Token: Hyper's
@postxautomatically includes the CSRF token in headers
The request looks like this:
POST /increment HTTP/1.1
X-CSRF-TOKEN: abc123...
Content-Type: application/json
{
"count": 5,
"errors": []
}Server Processing
In your Laravel controller:
public function increment()
{
// Read signals sent from frontend
$count = signals('count', 0);
// Process business logic
$newCount = $count + 1;
// Return updates
return hyper()->signals(['count' => $newCount]);
}The signals() helper (provided by Hyper) reads signals from the request, similar to how request() reads form data.
Server to Frontend
Hyper returns Server-Sent Events (SSE) that tell Datastar what to update:
HTTP/1.1 200 OK
Content-Type: text/event-stream
event: datastar-signal
data: {"count": 6}Datastar receives this event and automatically updates the count signal. Any element displaying $count updates instantly.
Multiple Updates
You can chain multiple operations in a single response:
return hyper()
->signals(['count' => 6, 'errors' => []])
->view('counter-status', $data)
->js('console.log("Updated!")');This sends multiple SSE events:
- Update signals
- Replace DOM elements
- Execute JavaScript
All operations happen atomically—the user sees a complete, consistent update.
Datastar Attributes vs Hyper Attributes
Understanding which attributes come from Datastar and which are Hyper extensions helps you understand where to look for documentation and what each tool provides.
Datastar Attributes
These attributes are part of Datastar and handle core reactivity:
State Management:
data-signals: Create reactive signalsdata-computed: Create computed values from signalsdata-effect: Run code when signals change
Display & Binding:
data-text: Display signal values as textdata-bind: Two-way bind input values to signalsdata-show: Show/hide elements based on conditionsdata-class: Toggle CSS classes reactivelydata-style: Apply styles reactivelydata-attr: Set attributes reactively
Events:
data-on:click: Handle click eventsdata-on:[event]: Handle any DOM eventdata-init: Run code when element loadsdata-on-interval: Run code on a timer
Backend Communication:
@get,@post,@put,@patch,@delete: HTTP actions@fetch: Custom fetch requests
References:
data-ref: Create references to DOM elementsdata-intersect: React to element visibility
Learn more about Datastar attributes in the Datastar Attributes reference.
Hyper Attributes
These attributes are Hyper's Laravel-specific extensions:
Enhanced Backend Actions:
@postx,@putx,@patchx,@deletex: HTTP actions with automatic CSRF tokens
Laravel Integration:
data-error: Display Laravel validation errors for specific fieldsdata-navigate: Client-side navigation with Laravel routesdata-if: Conditional rendering (Hyper's optimization)data-for: Loop rendering (Hyper's optimization)
Learn more in the Hyper Attributes reference.
Blade Directives
Hyper provides several Blade directives that make working with signals and fragments more natural in Laravel.
@hyper
Includes Hyper's JavaScript and CSRF token:
<head>
@hyper
</head>Expands to:
<meta name="csrf-token" content="your-token">
<script type="module" src="/vendor/hyper/js/hyper.js"></script>@signals
Initialize signals from PHP data:
<div @signals(['count' => $initialCount, 'max' => 100])>
<!-- Signals are now available -->
</div>This is cleaner than manually writing JSON and handles encoding automatically. You can use the spread operator to expand arrays:
<div @signals(...$userData, ['_localState' => false])>
<!-- All $userData keys become signals, plus _localState -->
</div>@fragment / @endfragment
Define reusable sections for partial updates:
@fragment('user-stats')
<div id="stats">
<p>Posts: {{ $postCount }}</p>
<p>Followers: {{ $followerCount }}</p>
</div>
@endfragmentFrom your controller, render just this fragment:
return hyper()->fragment('users.profile', 'user-stats', [
'postCount' => 42,
'followerCount' => 1337
]);Fragments help you avoid creating separate component files for small, related sections. Learn more in Backend Patterns: Fragments.
Helpers
Hyper provides two main helpers that mirror Laravel's familiar patterns.
signals()
Read signals from requests, similar to request():
// Get a specific signal with default
$count = signals('count', 0);
// Get all signals
$allSignals = signals()->all();
// Check if a signal exists
if (signals()->has('username')) {
// ...
}
// Validate signals (returns validated data or throws exception)
$validated = signals()->validate([
'email' => 'required|email',
'age' => 'integer|min:18'
]);hyper()
Build reactive responses, similar to response():
// Update signals
return hyper()->signals(['count' => 5]);
// Render a view (targets by ID)
return hyper()->view('counter-status', $data);
// Render a fragment
return hyper()->fragment('users.profile', 'stats', $data);
// Manipulate DOM
return hyper()
->append('#list', view('item', $item))
->remove('#old-item')
->html('<p>New content</p>', ['selector' => '#target']);
// Execute JavaScript
return hyper()->js('alert("Done!")');
// Chain multiple operations
return hyper()
->signals(['count' => 5, 'errors' => []])
->view('status', $data)
->when($milestone, fn($h) => $h->js('celebrate()'));The fluent interface lets you compose complex responses by chaining methods.
Validation
Hyper integrates Laravel's validation system with signals, making validation feel natural.
Validating Signals
Use signals()->validate() exactly like request()->validate():
public function store()
{
$validated = signals()->validate([
'title' => 'required|string|max:255',
'email' => 'required|email',
'age' => 'integer|min:18'
]);
// Use validated data...
}If validation fails, Hyper automatically:
- Creates an
errorssignal with Laravel's error structure - Returns it to the frontend
- Displays errors using
data-errorattributes
Displaying Errors
Use the data-error attribute (Hyper extension) to display validation errors:
<div data-signals="{title: '', errors: []}">
<input data-bind="title" />
<div data-error="title"></div>
</div>When validation fails, data-error="title" automatically shows the first error message for the title field. When validation passes or you clear errors, the element becomes hidden.
Complete Validation Guide
Laravel's entire validation system works seamlessly with Hyper, including custom rules, file validation, and real-time validation. See Essentials: Validation for comprehensive examples and patterns.
Mental Model
Here's how to think about Hyper applications:
- Signals are your state: All dynamic data lives in signals
- Datastar provides reactivity: Changes to signals automatically update the UI
- Laravel processes logic: Controllers handle business rules, validation, and data
- Hyper bridges the gap: Helpers and directives make Laravel and Datastar work together seamlessly
- SSE delivers updates: Server-Sent Events push changes to the frontend instantly
You write Blade templates with reactive attributes, handle interactions in Laravel controllers, and let Hyper manage the communication. The result feels like a single-page application but maintains Laravel's server-driven architecture.
Common Patterns
Form Submission
<form data-on:submit__prevent="@postx('/contacts')">
<input data-bind="name" />
<div data-error="name"></div>
<button type="submit">Save</button>
</form>public function store()
{
$validated = signals()->validate([
'name' => 'required|string|max:255'
]);
Contact::create($validated);
return hyper()
->signals(['name' => '', 'errors' => []])
->js("showToast('Contact created!')");
}Inline Editing
<div id="todo-{{ $todo->id }}">
@if($editing)
<input data-bind="title" />
<button data-on:click="@postx('/todos/{{ $todo->id }}')">Save</button>
@else
<span data-text="$title"></span>
<button data-on:click="$editing = true">Edit</button>
@endif
</div>Live Search
<input data-bind="search"
data-on:input="@get('/search')" />
<div id="results">
<!-- Results updated by server -->
</div>public function search()
{
$results = User::where('name', 'like', '%' . signals('search') . '%')
->limit(10)
->get();
return hyper()->view('search-results', compact('results'));
}What's Next?
You now understand Hyper's core concepts. Continue learning with:
- Essentials: Signals: Deep dive into signal management
- Essentials: Blade Integration: Master Blade directives and fragments
- Essentials: Actions: Learn all the ways to trigger server requests
- Essentials: Responses: Explore the full power of the
hyper()response builder
Each section builds on these concepts, showing you practical patterns for building real applications.

