HTTP Magics
HTTP magics are Alpine.js magic properties that make server requests effortless. Every magic automatically serializes your component state, makes an SSE request, and patches the response back into your application - all in one line of code.
Available Magics
Gale provides two sets of HTTP magics: standard and CSRF-protected.
| Standard | CSRF-Protected | HTTP Method | Use Case |
|---|---|---|---|
$get |
- | GET | Read data, navigation |
$post |
$postx |
POST | Create resources |
$patch |
$patchx |
PATCH | Partial updates |
$put |
$putx |
PUT | Full replacements |
$delete |
$deletex |
DELETE | Remove resources |
CSRF Protection
Laravel requires CSRF tokens for state-changing requests. Always use the
x-suffixed magics ($postx, $patchx, etc.)
for Laravel backends. These magics automatically:
- Inject the CSRF token from your page's meta tag
- Include credentials for session handling
- Handle token refresh when sessions expire
<!-- Your layout should include the CSRF meta tag -->
<meta name="csrf-token" content="XQVWKNqh3v2y1Jfztlanu04PDPr0gbwR7fTGAWiw">
<!-- Use $postx for Laravel backends -->
<button @click="$postx('/save')">Save</button>
<!-- Your layout should include the CSRF meta tag -->
<meta name="csrf-token" content="XQVWKNqh3v2y1Jfztlanu04PDPr0gbwR7fTGAWiw">
<!-- Use $postx for Laravel backends -->
<button @click="$postx('/save')">Save</button>
Important
$post for Laravel routes with CSRF protection enabled. Always use $postx (or other x-suffixed variants) to prevent "419 Page Expired" errors.
Basic Usage
Each magic takes a URL and optional options object. When called, it serializes the component state and sends it to the server.
<div x-data="{ count: 0 }">
<!-- GET request - for reading data -->
<button @click="$get('/refresh')">Refresh</button>
<!-- POST request with CSRF - for Laravel backends -->
<button @click="$postx('/increment')">Increment</button>
<span x-text="count"></span>
</div>
<div x-data="{ count: 0 }">
<!-- GET request - for reading data -->
<button @click="$get('/refresh')">Refresh</button>
<!-- POST request with CSRF - for Laravel backends -->
<button @click="$postx('/increment')">Increment</button>
<span x-text="count"></span>
</div>
The controller receives the state in the request body:
public function increment(Request $request)
{
$count = $request->input('count', 0);
return gale()->state('count', $count + 1);
}
public function increment(Request $request)
{
$count = $request->input('count', 0);
return gale()->state('count', $count + 1);
}
Filtering State
By default, all serializable state is sent to the server. Use include
or exclude options to filter what gets sent.
Include (Whitelist)
Only send specific properties:
<div x-data="{
name: '',
email: '',
password: '',
confirmPassword: '',
uiState: 'form'
}">
<!-- Only send name and email -->
<button @click="$postx('/save', { include: ['name', 'email'] })">
Save Profile
</button>
</div>
<div x-data="{
name: '',
email: '',
password: '',
confirmPassword: '',
uiState: 'form'
}">
<!-- Only send name and email -->
<button @click="$postx('/save', { include: ['name', 'email'] })">
Save Profile
</button>
</div>
Exclude (Blacklist)
Send everything except specified properties:
<div x-data="{
formData: { name: '', email: '' },
isLoading: false,
error: null,
showModal: false
}">
<!-- Send formData but exclude UI state -->
<button @click="$postx('/submit', { exclude: ['isLoading', 'error', 'showModal'] })">
Submit
</button>
</div>
<div x-data="{
formData: { name: '', email: '' },
isLoading: false,
error: null,
showModal: false
}">
<!-- Send formData but exclude UI state -->
<button @click="$postx('/submit', { exclude: ['isLoading', 'error', 'showModal'] })">
Submit
</button>
</div>
Browser-Only State (Underscore Prefix)
Properties prefixed with an underscore (_) are automatically excluded
from serialization. This is perfect for UI state that should never be sent to the server.
<div x-data="{
// These ARE sent to server
name: '',
email: '',
// These are NOT sent (underscore prefix)
_isEditing: false,
_selectedTab: 'general',
_tooltipVisible: false
}">
<!-- Only name and email are sent -->
<button @click="$postx('/save')">Save</button>
</div>
<div x-data="{
// These ARE sent to server
name: '',
email: '',
// These are NOT sent (underscore prefix)
_isEditing: false,
_selectedTab: 'general',
_tooltipVisible: false
}">
<!-- Only name and email are sent -->
<button @click="$postx('/save')">Save</button>
</div>
Including Component State
Use includeComponents to send state from named components registered
with the x-component directive. This is powerful for cross-component
communication.
<!-- Cart component in header -->
<div x-component="cart" x-data="{ items: [], total: 0 }">
...
</div>
<!-- Checkout form elsewhere -->
<div x-data="{ shipping: '', billing: '' }">
<!-- Include cart state in checkout submission -->
<button @click="$postx('/checkout', {
includeComponents: ['cart']
})">
Place Order
</button>
</div>
<!-- Cart component in header -->
<div x-component="cart" x-data="{ items: [], total: 0 }">
...
</div>
<!-- Checkout form elsewhere -->
<div x-data="{ shipping: '', billing: '' }">
<!-- Include cart state in checkout submission -->
<button @click="$postx('/checkout', {
includeComponents: ['cart']
})">
Place Order
</button>
</div>
The server receives the component state under _components:
public function checkout(Request $request)
{
// Local component state
$shipping = $request->input('shipping');
// Cart component state
$cartItems = $request->input('_components.cart.items');
$cartTotal = $request->input('_components.cart.total');
// Process order...
}
public function checkout(Request $request)
{
// Local component state
$shipping = $request->input('shipping');
// Cart component state
$cartItems = $request->input('_components.cart.items');
$cartTotal = $request->input('_components.cart.total');
// Process order...
}
Selective Component Inclusion
You can include specific properties from components using an object format:
// Include only specific properties from cart
$postx('/checkout', {
includeComponents: {
'cart': ['items', 'total'] // Only these properties
}
})
// Exclude sensitive properties
$postx('/save', {
includeComponents: {
'user': { exclude: ['password', 'token'] }
}
})
// Rename component in request
$postx('/submit', {
includeComponents: {
'cart': { as: 'shopping_cart' }
}
})
// Include only specific properties from cart
$postx('/checkout', {
includeComponents: {
'cart': ['items', 'total'] // Only these properties
}
})
// Exclude sensitive properties
$postx('/save', {
includeComponents: {
'user': { exclude: ['password', 'token'] }
}
})
// Rename component in request
$postx('/submit', {
includeComponents: {
'cart': { as: 'shopping_cart' }
}
})
Include Components by Tag
Include all components with a specific tag using includeComponentsByTag:
<!-- Form field components with 'form-field' tag -->
<div x-component="name-field" data-tags="form-field" x-data="{ value: '' }">...</div>
<div x-component="email-field" data-tags="form-field" x-data="{ value: '' }">...</div>
<!-- Include all form-field components -->
<button @click="$postx('/submit', {
includeComponentsByTag: ['form-field']
})">
Submit
</button>
<!-- Form field components with 'form-field' tag -->
<div x-component="name-field" data-tags="form-field" x-data="{ value: '' }">...</div>
<div x-component="email-field" data-tags="form-field" x-data="{ value: '' }">...</div>
<!-- Include all form-field components -->
<button @click="$postx('/submit', {
includeComponentsByTag: ['form-field']
})">
Submit
</button>
Request Options
All HTTP magics accept these options:
| Option | Type | Default | Description |
|---|---|---|---|
include |
string[] | null | Whitelist of properties to send |
exclude |
string[] | [] | Blacklist of properties to skip |
includeComponents |
string[] | object | null | Named components to include |
includeComponentsByTag |
string[] | null | Tags to include components from |
headers |
object | {} | Additional HTTP headers |
retryInterval |
number | 1000 | Initial retry interval (ms) |
retryScaler |
number | 2 | Exponential backoff multiplier |
retryMaxWaitMs |
number | 30000 | Max retry wait time (ms) |
retryMaxCount |
number | 10 | Max retry attempts |
Custom Headers
Add custom headers to requests:
<button @click="$postx('/api/resource', {
headers: {
'X-Custom-Header': 'value',
'Accept-Language': 'en-US'
}
})">
Request with custom headers
</button>
<button @click="$postx('/api/resource', {
headers: {
'X-Custom-Header': 'value',
'Accept-Language': 'en-US'
}
})">
Request with custom headers
</button>
Automatic Features
Automatic Serialization
HTTP magics automatically handle complex data types:
- Arrays - Serialized as JSON arrays
- Objects - Serialized as JSON objects (nested supported)
- Dates - Converted to ISO 8601 strings
- Maps - Converted to plain objects
- Sets - Converted to arrays
- Functions - Automatically excluded
- DOM Elements - Automatically excluded
- Alpine internals - Automatically excluded (
$and_prefix)
Automatic Retry
Requests automatically retry on network failures using exponential backoff. The default configuration retries up to 10 times with increasing delays.
// Custom retry configuration
$postx('/important-action', {
retryInterval: 500, // Start with 500ms
retryScaler: 1.5, // 1.5x each retry
retryMaxWaitMs: 10000, // Max 10 seconds
retryMaxCount: 5 // Only 5 attempts
})
// Custom retry configuration
$postx('/important-action', {
retryInterval: 500, // Start with 500ms
retryScaler: 1.5, // 1.5x each retry
retryMaxWaitMs: 10000, // Max 10 seconds
retryMaxCount: 5 // Only 5 attempts
})
Automatic File Detection
When file inputs with the x-files directive contain files, HTTP magics
automatically switch from JSON to FormData, enabling seamless file uploads.
<div x-data="{ title: '' }">
<input type="text" x-model="title">
<input type="file" name="document" x-files>
<!-- Automatically uses FormData when files are selected -->
<button @click="$postx('/upload')">Upload</button>
</div>
<div x-data="{ title: '' }">
<input type="text" x-model="title">
<input type="file" name="document" x-files>
<!-- Automatically uses FormData when files are selected -->
<button @click="$postx('/upload')">Upload</button>
</div>
Complete Example
<div x-data="{
// Data to send
name: '',
email: '',
preferences: { theme: 'light', notifications: true },
// Browser-only UI state (never sent)
_isSubmitting: false,
_showSuccess: false
}">
<form @submit.prevent="_isSubmitting = true; $postx('/profile', {
include: ['name', 'email', 'preferences']
})">
<input type="text" x-model="name" placeholder="Name">
<span x-message="name" class="error"></span>
<input type="email" x-model="email" placeholder="Email">
<span x-message="email" class="error"></span>
<button type="submit" :disabled="_isSubmitting">
<span x-show="!_isSubmitting">Save Profile</span>
<span x-show="_isSubmitting">Saving...</span>
</button>
</form>
<div x-show="_showSuccess" class="success">
Profile saved successfully!
</div>
</div>
<div x-data="{
// Data to send
name: '',
email: '',
preferences: { theme: 'light', notifications: true },
// Browser-only UI state (never sent)
_isSubmitting: false,
_showSuccess: false
}">
<form @submit.prevent="_isSubmitting = true; $postx('/profile', {
include: ['name', 'email', 'preferences']
})">
<input type="text" x-model="name" placeholder="Name">
<span x-message="name" class="error"></span>
<input type="email" x-model="email" placeholder="Email">
<span x-message="email" class="error"></span>
<button type="submit" :disabled="_isSubmitting">
<span x-show="!_isSubmitting">Save Profile</span>
<span x-show="_isSubmitting">Saving...</span>
</button>
</form>
<div x-show="_showSuccess" class="success">
Profile saved successfully!
</div>
</div>
// Controller
public function updateProfile(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email',
]);
$request->user()->update($validated);
return gale()
->state([
'_isSubmitting' => false,
'_showSuccess' => true,
])
->clearMessages();
}
// Controller
public function updateProfile(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email',
]);
$request->user()->update($validated);
return gale()
->state([
'_isSubmitting' => false,
'_showSuccess' => true,
])
->clearMessages();
}