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>
Important
Never use $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>

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, 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>

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>

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>

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>

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
$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>

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>

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
})

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>

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>
// 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();
}

On this page