Skip to content
Alpine AJAX Menu



You can use Alpine AJAX by either including it from a <script> tag or installing it via NPM.


Include the CDN build of Alpine AJAX as a <script> tag, just make sure to include it before Alpine's core JS file.

<script defer src=""></script>
<script defer src=""></script>


Install Alpine AJAX from NPM for use inside your bundle like so:

npm i @imacrayon/alpine-ajax

Then initialize it from your bundle:

import Alpine from 'alpinejs'
import ajax from '@imacrayon/alpine-ajax'

window.Alpine = Alpine


It’s good practice to start building your UI without Alpine AJAX. Make your entire website work as it would if Alpine AJAX were not available, then sprinkle in AJAX functionality at the end. Working in this way will ensure that your AJAX interactions degrade gracefully when JavaScript is not available: Links and forms will continue to work as normal, they simply won't fire AJAX requests. This is known as Progressive Enhancement, and it allows a wider audience to use your website.


Add the x-target attribute to forms or links to enable AJAX behavior. The value of x-target should equal the id of an element on the page that the form or link should target.

Take a look at the following comment list markup, notice the x-target="comments" attribute on the <form>:

<ul id="comments">
<li>Comment #1</li>
<form x-init x-target="comments" method="post" action="/comment">
<input aria-label="Comment text" name="text" required />

When the form is submitted a POST request is issued to /comment and the #comments list will be replaced with the element that has id="comments" in the AJAX request's response.

Multiple Targets

x-target can replace multiple elements from a single server response by separating ids with a space.

In this comment list example note that the x-target attribute on the <form> targets two elements:

<h2>Comments (<span id="comments_count">1</span>)</h2>
<ul id="comments">
<li>Comment #1</li>
<form x-init x-target="comments comments_count" method="post" action="/comment">
<input name="comment" required />

Now, when the form is submitted, both the #comments list, and the #comments_count indicator will be updated.

Target shorthand

In cases when a form or link targets itself, you may leave the value of x-target blank, however the form or link must still have an id:

<form x-init x-target id="star_repo" method="post" action="/repos/1/star">
<button>Star Repository</button>

Handling redirects

AJAX requests issued by x-target will transparently follow redirects without reloading the browser window. You may use the x-target.nofollow modifier to force the browser to reload when the server responds with a redirect. Notice the nofollow modifier used on this form for creating a new blog post:

<form x-init x-target.nofollow id="create_post" method="post" action="/posts">
<label for="content">Post title</label>
<input id="title" name="title" required />
<label for="content">Post content</label>
<input id="content" name="content" required />

When this form is submitted a POST request is issued to /posts. If the server responds with a 302 redirect to the newly created blog post at /posts/1, the browser will preform a full page reload and navigate to /posts/1.

You can change the default way that x-target handles redirects using the followRedirects global configuration option.

History & URL Support

Use the x-target.replace modifier to replace the URL in the browser's navigation bar when an AJAX request is issued.

Use the x-target.push modifier to push a new history entry onto the browser's session history stack when an AJAX request is issued.

replace simply changes the browser’s URL without adding a new entry to the browser’s session history stack, where as push creates a new history entry allowing your users to navigate back to the previous URL using the browser’s "Back" button.

Disable AJAX per submit button

In cases where you have a form with multiple submit buttons, you may not always want all submit buttons to trigger an AJAX request. Add the formnoajax attribute to a submit element to instruct the form to make a standard full-page request instead of an AJAX request.

<form id="checkout" x-init x-target method="post" action="/checkout">
<button name="procedure" value="increment">Increment quantity</button>
<button name="procedure" value="decrement">Decrement quantity</button>
<button formnoajax name="procedure" value="purchase">Complete checkout</button>

In this example clicking "Increment" or "Decrement" will issue an AJAX request. Clicking "Complete Checkout" will perform a standard form submission.


Use x-headers to add additional request headers:

<form method="post" action="/comments" x-target="comments comments_count" x-headers="{'Custom-Header': 'Shmow-zow!'}">

Alpine AJAX adds two default headers to every request: X-Alpine-Request which is always true, and X-Alpine-Target which contains a space-separated list of target IDs. The previous form example would include these headers with its request:

X-Alpine-Request: true
X-Alpine-Target: comments comments_count
Custom-Header: Shmow-zow!


By default incoming HTML from the server will replace a targeted element. You can add x-merge to a targeted element to change how it merges incoming content. For example, if you wanted to append new items to a list of messages, you would add x-merge="append" to the list:

<ul id="messages" x-merge="append">
<li>First message</li>

New HTML sent from the server might look like this:

<ul id="messages">
<li>Second message</li>

And after the HTML is merged, you'll have a list with two items:

<ul id="messages" x-merge="append">
<li>First message</li>
<li>Second message</li>

There are a total of seven merge strategies you can use, replace is the default strategy:

Strategy Description
before Inserts the content of the incoming element before the target element.
replace (Default) Replaces the target element with the incoming element.
update Updates the target element's content with the incoming element's content.
prepend Prepends the target element's content with the incoming element's content.
append Appends the target element's content with the incoming element's content.
after Inserts the content of the incoming element after the target element.

You can change the default merge strategy for all AJAX requests using the mergeStrategy global configuration option.


Alpine AJAX supports using the Alpine Morph Plugin as a merge strategy for when you want to update content and preserve UI state in a more fine-grained way.

To enable the morph strategy, install the Morph Plugin before installing Alpine AJAX.

Via CDN:

<script defer src=""></script>
<script defer src=""></script>
<script defer src=""></script>

Or via NPM:

npm i @alpinejs/morph
import Alpine from 'alpinejs'
import morph from '@alpinejs/morph'
import ajax from '@imacrayon/alpine-ajax'

window.Alpine = Alpine

With the Morph Plugin installed you can use x-merge="morph" to morph content changes on the page.

View transitions & animations

You can animate transitions between different DOM states using the View Transitions API. This API is still in active development and is currently only supported in Chrome browsers. Alpine AJAX provides support for View Transitions and gracefully falls back to no animations if the API is not available in a browser.

To enable View Transitions on an element use the x-merge.transition modifier. When enabled in a supported browser, you should see content automatically animate as it is merged onto the page. You can customize any transition animation via CSS by following Chrome's documentation for customizing transitions.


When AJAX requests change content on the page it's important that you control keyboard focus to maintain meaningful sequencing and focus order. x-autofocus will restore a user's keyboard focus when the content they were focused on is changed by an AJAX request.

Notice the x-autofocus attribute on this email <input>:

<input type="email" name="email" x-autofocus />

This input will steal keyboard focus whenever it is inserted into the page by Alpine AJAX. Check out the Toggle Button and Inline Edit examples to see x-autofocus in action.

Disabling autofocus

You may use the nofocus modifier on x-target to disable autofocus behavior. This can be useful in situations where you may need to hand over focus control to a third-party script. In the following example we've disabled focus so that our dialog component can handle focus instead:

<a href="/preview/1" x-target.nofocus="dialog_content" @ajax:before="$dispatch('dialog:open')">Open preview</a>

The standard autofocus attribute

Alpine AJAX will also respect the standard autofocus attribute and treat it like x-autofocus. When AJAX content contains elements with both x-autofocus and autofocus. The element with x-autofocus will win focus.

Using morph and focus

It's worth noting that x-merge="morph" is another way to preserve keyboard focus between content changes. However, there are cases when the DOM is transformed so much that the Morph algorithm is unable to reliably preserve focus state, so x-autofocus is a lot more predictable in most situations.


Elements with the x-sync attribute are updated whenever the server sends a matching element, even if the element isn't targeted with x-target.

x-sync elements must have a unique id. Any element sent from the server with a matching id will replace the existing x-sync element.

Use cases for this attribute are unread message counters or notification flashes. These elements often live in the base layout, outside of the content area that is being replaced.

Consider this list of notifications:

<div aria-live="polite">
<ul x-sync id="notifications"></ul>

Every server response that includes an element with id="notifications" will replace the existing list of notifications inside the aria-live region. Take a look at the Notifications example for a complete demonstration of this UI pattern.


The $ajax magic helper is for finer-grained AJAX control. Use it to programmatically issue AJAX requests in response to events. Here we've wired it up to an input's change event to perform some server-side validation for an email:

<div id="email_field" x-data="{email : ''}" @change="$ajax('/validate-email', {
method: 'post',
body: { email },

<label for="email">Email</label>
<input type="email" name="email" id="email" x-model="email">

In this example we make a POST request with the email value to the /validate-email endpoint. See the Inline Validation example for a complete demonstration.

Note: Since $ajax is intended to be used in side effects it doesn't emit any events or target x-sync elements like x-target. However, you can change these defaults using the $ajax options.

$ajax options

Option Default Description
method 'GET' The request method.
target '' The request target. If this is empty x-target is used.
targets [] Same as target, but specified as an array of strings. If this is empty target is used.
body {} The request body.
events false Setting this to true will fire AJAX request events.
focus false Setting this to true will enable `x-autofocus` & `autofocus` behavior.
sync false Setting this to true will include x-sync targets in the request.
followRedirects true Switch this to false and AJAX requests will reload the browser window when they encounter a redirect response.
headers {} Additional request headers as key/value pairs.


You can listen for events to perform additional actions during the lifecycle of an AJAX request:

Name Description
ajax:before Fired before an network request is made. If this event is canceled using $event.preventDefault() the request will be aborted.
ajax:success Fired when an network request completes. $event.detail contains the server response data.
ajax:error Fired when an network request responds with a `400` or `500` status code. $event.detail contains the server response data.
ajax:after Fired after every successful or unsuccessful network request.
ajax:missing Fired if a matching target is not found in the response body. $event.detail contains the server response data. You may cancel this event using $event.preventDefault() to override the default behavior.
ajax:merge Fired when new content is being merged into $ You may override a merge using $event.preventDefault(). $event.detail contains the server response data, the content to merge, and a merge() method to continue the merge.
ajax:merged Fired after new content was merged into $

Here's an example of aborting a form request when the user cancels a dialog prompt:

<form id="delete_user" x-init x-target @ajax:before="confirm('Are you sure?') || $event.preventDefault()">
<button>Delete User</button>

Note: The ajax:success and ajax:error events only convey the status code of an AJAX request. You'll probably find that Server Events are better for triggering actions based on your server's response.

Loading states

While an AJAX request is in progress there are a few loading states to be aware of:

  • If a form submission triggered the request, the form's submit button is automatically disabled, this prevents users from triggering additional network requests by accidentally double clicking the submit button.
  • During an AJAX request, aria-busy="true" is set on all targets of the request. This attribute can be used in CSS to provide a loading indicator, check out the Loading Indicator example for more details.


You have the option to configure the default behavior of Alpine AJAX when importing it in your code:

import ajax from '@imacrayon/alpine-ajax'

followRedirects: false,
headers: { 'X-CSRF-Token': 'mathmatical!' }
mergeStrategy: 'morph',

Here are the configuration options and there defaults:

Option Default Description
followRedirects true Switch this to false and AJAX requests will reload the browser window when they encounter a redirect response. You can then follow redirects case-by-case using the follow modifier on x-target.
headers {} Additional request headers, as key/value pairs, included in every AJAX request.
mergeStrategy 'replace' Set the default merge strategy used when new content is merged onto the page.

Creating demos

Use the mock server script included with Alpine AJAX when you need to build a quick prototype or demonstrate a bug, without a server. The mock server script adds a global route helper function for mocking server endpoints on the frontend:

Include the typical required scripts before the mock server:
<script defer src=""></script>
<script defer src=""></script>

<script src=""></script>

route('POST', '/update-quantity', (request) => {
return `<output id="current_quantity">${Number(request.quantity)}</output>`

<label for="current_quantity">Current quantity</label>
<output id="current_quantity">0</output>
<form x-init x-target="current_quantity" method="POST" action="/update-quantity">
<label form="quantity">New quantity</label>
<input type="number" id="quantity" name="quantity">

Now, instead of issuing a real POST request to /update-quantity, Alpine AJAX will use the HTML returned in our route definition as the response. Note that any form data included in the AJAX request is made available too you in the route function.

Mock server example on CodePen

See the Pen Alpine AJAX Demo by Christian Taylor (@imacrayon) on CodePen.

Important: The mock server should only be used for demos and testing, this utility is not designed for production environments.