Signals Helper
The signals() helper provides server-side access to reactive signals sent from the frontend. It allows you to read, validate, and manipulate signal data in your Laravel controllers, with built-in support for locked signal security and file storage.
Basic Usage
The signals() helper returns a HyperSignal instance that reads signals from the current request.
use function Dancycodes\Hyper\signals;
public function increment()
{
$count = signals('count'); // Get single signal
return hyper()->signals(['count' => $count + 1]);
}Reading Signals
signals()
Get a specific signal with an optional default value.
signals(string $key = null, mixed $default = null): mixedGet single signal:
$username = signals('username');With default value:
$role = signals('role', 'user');Get all signals:
$allSignals = signals()->all();get()
Get a specific signal value (same as calling signals($key, $default)).
get(string $key, mixed $default = null): mixed$email = signals()->get('email');
$status = signals()->get('status', 'pending');Supports dot notation:
$city = signals()->get('user.address.city');all()
Get all signals as an associative array.
all(): array$signals = signals()->all();
// Example result:
// [
// 'username' => 'John',
// 'email' => 'john@example.com',
// 'age' => 25
// ]only()
Get only specific signals.
only(array $keys): array$credentials = signals()->only(['email', 'password']);
// Result:
// [
// 'email' => 'john@example.com',
// 'password' => 'secret'
// ]has()
Check if a signal exists.
has(string $key): boolif (signals()->has('userId')) {
$userId = signals('userId');
}collect()
Get signals as a Laravel Collection for fluent manipulation.
collect(): \Illuminate\Support\Collection$filtered = signals()
->collect()
->filter(fn($value) => !is_null($value))
->map(fn($value) => strtoupper($value));Validation
validate()
Validate signals using Laravel's validation system.
validate(array $rules, array $messages = [], array $attributes = []): arrayParameters:
$rules- Laravel validation rules$messages- Custom error messages (optional)$attributes- Custom attribute names (optional)
Returns: Validated data array
Throws: HyperValidationException on validation failure
Basic validation:
public function store()
{
$validated = signals()->validate([
'email' => 'required|email|unique:users',
'password' => 'required|min:8|confirmed'
]);
User::create($validated);
return hyper()->signals(['success' => true]);
}With custom messages:
$validated = signals()->validate(
[
'email' => 'required|email',
'name' => 'required|min:3'
],
[
'email.required' => 'Please provide your email address.',
'email.email' => 'Email format is invalid.',
'name.min' => 'Name must be at least 3 characters.'
]
);With custom attribute names:
$validated = signals()->validate(
['user_email' => 'required|email'],
[],
['user_email' => 'email address']
);
// Error message: "The email address field is required."Automatic error handling:
When validation fails, Hyper automatically:
- Creates an
errorssignal containing Laravel's error bag - Clears previous errors for validated fields
- Sends the
errorssignal to the frontend
Display errors in your Blade template using the data-error attribute:
<input data-bind="email" />
<div data-error="email"></div>
<!-- Automatically shows first error for 'email' field -->File Storage
The signals() helper provides methods for handling base64-encoded files from file inputs.
store()
Store a base64-encoded file to disk.
store(string $signalKey, string $directory = '', string $disk = 'public', ?string $filename = null): stringParameters:
$signalKey- Signal containing the file (from<input type="file" data-bind="avatar">)$directory- Storage directory path$disk- Laravel storage disk (default:'public')$filename- Custom filename (optional, auto-generated if not provided)
Returns: Storage path
Basic usage:
public function uploadAvatar()
{
signals()->validate([
'avatar' => 'required|b64image|b64max:2048'
]);
$path = signals()->store('avatar', 'avatars', 'public');
auth()->user()->update(['avatar' => $path]);
return hyper()->signals(['avatarPath' => $path]);
}With custom filename:
$userId = auth()->id();
$path = signals()->store('avatar', 'avatars', 'public', "user-{$userId}.jpg");File signal structure:
When a user selects a file, Datastar creates a signal with this structure:
{
"avatar": {
"name": "profile.jpg",
"size": 45678,
"type": "image/jpeg",
"lastModified": 1672531200000,
"data": "data:image/jpeg;base64,/9j/4AAQSkZJRg..."
}
}storeAsUrl()
Store a file and return its public URL.
storeAsUrl(string $signalKey, string $directory = '', string $disk = 'public', ?string $filename = null): stringReturns: Public URL to the stored file
public function uploadDocument()
{
signals()->validate([
'document' => 'required|b64file|b64mimes:pdf,doc,docx'
]);
$url = signals()->storeAsUrl('document', 'documents', 'public');
return hyper()->signals([
'documentUrl' => $url,
'message' => 'Document uploaded successfully!'
]);
}storeMultiple()
Store multiple files at once.
storeMultiple(array $mapping, string $disk = 'public'): arrayParameters:
$mapping- Array mapping signal keys to directories:['signalKey' => 'directory']$disk- Laravel storage disk
Returns: Array of storage paths
public function uploadFiles()
{
signals()->validate([
'avatar' => 'required|b64image',
'resume' => 'required|b64file|b64mimes:pdf'
]);
$paths = signals()->storeMultiple([
'avatar' => 'avatars',
'resume' => 'resumes'
], 'public');
// Result:
// [
// 'avatar' => 'avatars/abc123.jpg',
// 'resume' => 'resumes/def456.pdf'
// ]
return hyper()->signals(['uploadedPaths' => $paths]);
}Locked Signals
Locked signals (ending with _) are server-protected signals that cannot be tampered with on the frontend. They're encrypted in the session and validated on every request.
Creating Locked Signals
Use the @signals Blade directive with the trailing underscore naming convention:
<div @signals(['userId_' => auth()->id(), 'role_' => auth()->user()->role])>
<!-- userId_ and role_ are now locked signals -->
</div>Important: Locked signals must be created using @signals directive, not data-signals attribute:
<!-- ❌ Does NOT provide security -->
<div data-signals="{userId_: 123}">
<!-- ✅ Provides encryption and validation -->
<div @signals(['userId_' => auth()->id()])>Reading Locked Signals
Read locked signals the same way as regular signals:
public function updateProfile()
{
$userId = signals('userId_'); // Automatically validated
// If tampered with, HyperSignalTamperedException is thrown
// before your code runs
}How Locked Signals Work
First Call (view rendered with
@signals):- Value encrypted and stored in session
- Signal sent to frontend
Subsequent Calls (Hyper requests):
- Frontend sends signal back to server
- Server retrieves encrypted value from session
- Compares received value with stored value
- Throws
HyperSignalTamperedExceptionif mismatch detected
Deleting Locked Signals
Use hyper()->forget() to delete locked signals:
return hyper()->forget('userId_');
// Clears from frontend AND session storageUse Cases for Locked Signals
- User IDs and authentication state
- Permission levels and roles
- Pricing information
- Sensitive configuration
- Any data that shouldn't change client-side
Example:
<div @signals([
'productId_' => $product->id,
'price_' => $product->price,
'canEdit_' => auth()->user()->can('edit', $product)
])>
<button data-on:click="@postx('/cart/add')"
data-show="$canEdit_">
Add to Cart (${{ $price_ }})
</button>
</div>public function addToCart()
{
$productId = signals('productId_');
$price = signals('price_');
$canEdit = signals('canEdit_');
// These values are guaranteed to be authentic
// User cannot manipulate price or permissions
if (!$canEdit) {
abort(403);
}
Cart::add($productId, $price);
}Validation Rules for Files
When validating file signals, use Hyper's base64-specific validation rules:
Available Rules
b64file- Validates a base64-encoded fileb64image- Validates a base64-encoded imageb64max:size- Maximum file size in kilobytesb64dimensions:constraints- Image dimension validationb64mimes:extensions- File extension validationb64mimetypes:types- MIME type validation
See Validation Rules for complete reference.
Example:
signals()->validate([
'avatar' => [
'required',
'b64image',
'b64max:2048', // Max 2MB
'b64dimensions:min_width=100,min_height=100,max_width=500',
'b64mimes:jpg,png'
]
]);Common Patterns
Form Submission with Validation
public function store()
{
$validated = signals()->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:contacts',
'phone' => 'nullable|string'
]);
$contact = Contact::create($validated);
return hyper()
->signals(['errors' => [], 'message' => 'Contact created!'])
->navigate('/contacts');
}Conditional Processing Based on Signals
public function update()
{
$data = signals()->all();
if (signals()->has('deleteAvatar') && signals('deleteAvatar')) {
Storage::delete(auth()->user()->avatar);
$data['avatar'] = null;
}
if (signals()->has('newAvatar')) {
$data['avatar'] = signals()->store('newAvatar', 'avatars');
}
auth()->user()->update($data);
return hyper()->signals(['success' => true]);
}Filtering and Transforming Signals
public function search()
{
$filters = signals()
->collect()
->only(['category', 'priceMin', 'priceMax', 'search'])
->filter(fn($value) => !is_null($value) && $value !== '')
->toArray();
$products = Product::query()
->when($filters['category'] ?? null, fn($q, $cat) => $q->where('category', $cat))
->when($filters['priceMin'] ?? null, fn($q, $min) => $q->where('price', '>=', $min))
->when($filters['priceMax'] ?? null, fn($q, $max) => $q->where('price', '<=', $max))
->when($filters['search'] ?? null, fn($q, $term) => $q->where('name', 'like', "%{$term}%"))
->get();
return hyper()->fragment('products.index', 'product-list', ['products' => $products]);
}File Upload with Validation
public function uploadDocuments()
{
signals()->validate([
'resume' => 'required|b64file|b64mimes:pdf,doc,docx|b64max:5120',
'coverLetter' => 'nullable|b64file|b64mimes:pdf,doc,docx|b64max:5120'
]);
$paths = signals()->storeMultiple([
'resume' => 'resumes',
'coverLetter' => 'cover-letters'
]);
Application::create([
'user_id' => auth()->id(),
'resume_path' => $paths['resume'],
'cover_letter_path' => $paths['coverLetter'] ?? null
]);
return hyper()->signals([
'errors' => [],
'message' => 'Application submitted successfully!'
]);
}Protected Operations with Locked Signals
// In Blade view
@php
$isAdmin_ = auth()->user()->isAdmin();
@endphp
<div @signals(['userId_' => auth()->id(), 'isAdmin_' => $isAdmin_])>
<button data-on:click="@deletex('/users/{{ $user->id }}')"
data-show="$isAdmin_">
Delete User
</button>
</div>// In Controller
public function destroy(User $user)
{
$userId = signals('userId_');
$isAdmin = signals('isAdmin_');
// These values are guaranteed authentic
// User cannot fake admin permissions
if (!$isAdmin) {
abort(403, 'Unauthorized');
}
if ($userId === $user->id) {
abort(400, 'Cannot delete yourself');
}
$user->delete();
return hyper()
->signals(['message' => 'User deleted'])
->navigate('/users');
}Request Object Integration
The signals() helper is also available as a request macro:
// These are equivalent:
signals('count');
request()->signals('count');
signals()->all();
request()->signals()->all();
signals()->validate($rules);
request()->signals()->validate($rules);See Request Macros for more information.
Related Documentation
- HyperResponse - Server response builder
- Request Macros - Request helper methods
- Validation Rules - File validation rules
- Blade Directives -
@signalsdirective - Validation - Validation patterns

