HTTP Requests with $action

The $action magic is the standard way to send HTTP requests from Alpine components in Gale. It serializes state, includes CSRF protection, and applies server responses with SSE-style updates — without leaving your page.

v2.0 Breaking Change: x-sync is Required
Gale sends no state by default. Add x-sync to the element with x-data (or use include per request) to send data.

The $action Magic

$action is the primary HTTP magic with automatic CSRF protection. It supports method chaining for different HTTP verbs:

Syntax HTTP Method CSRF Protected Use Case
$action(url) POST Yes Default - most common pattern
$action.get(url) GET N/A Read data, navigation
$action.post(url) POST Yes Create resources (explicit)
$action.put(url) PUT Yes Full resource replacements
$action.patch(url) PATCH Yes Partial resource updates
$action.delete(url) DELETE Yes Remove resources
Default Behavior
$action(url) defaults to POST with CSRF protection - the most common pattern for Laravel applications.

Automatic CSRF Protection

Laravel requires CSRF tokens for state-changing requests. The $action magic automatically handles CSRF protection for you:

  • Automatically injects the CSRF token from your page's meta tag
  • Includes credentials for session handling
  • Handles token refresh when sessions expire
<!-- Your layout should include the CSRF meta tag -->
<meta name="csrf-token" content="rlomJbab7QGq1oAlOH6njxVUUdtJKqBLruLpuKfH">

<!-- $action automatically includes CSRF token -->
<button @click="$action('/save')">Save</button>

Basic Usage

$action takes a URL and an optional options object. Gale only sends state when you opt in with x-sync (component-wide) or include (per request).

<div x-data="{ count: 0 }" x-sync>
    <!-- GET request - for reading data -->
    <button @click="$action.get('/refresh')">Refresh</button>

    <!-- POST request with CSRF (default) -->
    <button @click="$action('/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);
}

Filtering State

By default, no state is sent. Use x-sync for component-wide sync, or include/exclude per request to control exactly what gets serialized.

Include (Whitelist)

Send only specific properties for this request:

<div x-data="{
    name: '',
    email: '',
    password: '',
    confirmPassword: '',
    uiState: 'form'
}" x-sync>

    <!-- Only send name and email -->
    <button @click="$action('/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
}" x-sync>

    <!-- Send formData but exclude UI state -->
    <button @click="$action('/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="$action('/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 }" x-sync>
    ...
</div>

<!-- Checkout form elsewhere -->
<div x-data="{ shipping: '', billing: '' }" x-sync>
    <!-- Include cart state in checkout submission -->
    <button @click="$action('/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...
}

Selective Component Inclusion

You can include specific properties from components using an object format:

// Include only specific properties from cart
$action('/checkout', {
    includeComponents: {
        'cart': ['items', 'total']  // Only these properties
    }
})

// Exclude sensitive properties
$action('/save', {
    includeComponents: {
        'user': { exclude: ['password', 'token'] }
    }
})

// Rename component in request
$action('/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: '' }" x-sync>...</div>
<div x-component="email-field" data-tags="form-field" x-data="{ value: '' }" x-sync>...</div>

<!-- Include all form-field components -->
<button @click="$action('/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="$action('/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
$action('/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: '' }" x-sync>
    <input type="text" x-name="title">
    <input type="file" name="document" x-files>

    <!-- Automatically uses FormData when files are selected -->
    <button @click="$action('/upload')">Upload</button>
</div>

Complete Example

<form
    x-data="{
        name: '',
        email: '',
        _showSuccess: false
    }"
    x-sync
    @submit.prevent="$action('/profile')"
>

    <input type="text" x-name="name" placeholder="Name" required>
    <div x-message="name" class="error"></div>

    <input type="email" x-name="email" placeholder="Email" required>
    <div x-message="email" class="error"></div>

    <button type="submit" x-loading.attr="disabled">
        <span x-loading.remove>Save Profile</span>
        <span x-loading class="hidden">Saving...</span>
    </button>

    <div x-show="_showSuccess" class="success">
        Profile saved successfully!
    </div>
</form>
// Controller
public function updateProfile(Request $request)
{
    $validated = $request->validateState([
        'name' => 'required|string|max:255',
        'email' => 'required|email',
    ]);

    $request->user()->update($validated);

    return gale()
        ->state('_showSuccess', true)
        ->clearMessages();
}

On this page