Build Status Code Coverage npm version npm monthly downloads npm downloads

Naja. a genus of venomous elapid snakes comprising the true cobras. (Also, German for "Well" at the beginning of a sentence.)

Naja is a full-featured JS client-side AJAX library for Nette Framework.

It is written using modern-day JavaScript, but compiled into ES5 build that comes bundled with a few polyfills and thus should work on all modern browsers including IE 10 and above.

Sauce Test Status


npm install naja

Naja comes in the form of an npm package. You can install and import it into your code at will.

If you're not friends with npm, you can download the archive from Github and load the bundled script from dist directory into your site.

If you like to learn by example, @f3l1x has created a demo which shows how to use Naja in a Nette Framework application with Webpack.


Once you load naja, you need to initialize it. You should make sure this happens after the DOM is loaded, e.g. like this:

document.addEventListener('DOMContentLoaded', naja.initialize.bind(naja));

The initialize() method loads all Naja's core components plus all registered userland extensions (see below).

Dispatching a request

You can dispatch an AJAX request by calling Naja's makeRequest() method. It takes the following arguments:

naja.makeRequest(method, url, data = null, options = {})
  • method: string is the request method, usually GET or POST. It is case-insensitive.
  • url: string is the target URL.
  • data: ?mixed can be pretty much anything: array, object, string, ArrayBuffer, Blob, FormData, …
  • options: ?Object can be used to alter the behavior of some components or extensions (see below). On top of that, it carries the options for the underlying AJAX library, qwest. Please refer to its docs for reference.

The makeRequest method returns a Promise which either resolves to the response object containing the parsed response body, or is rejected with the thrown error.

Default options

You can also provide default options for your extensions or Naja's core components:

naja.defaultOptions = {
	history: false,
	myCustomOption: 42

Core components

You don't need to dispatch most of the requests manually though. Naja does lots of things automatically through the system of its core components and extensions:


UIHandler binds Naja's AJAX handler to all links, forms and submit inputs marked with ajax class.

Custom selector

You can customize the bound selector easily, changing it to a data attribute, inverting it, or even disabling it entirely (see below for further notes):

naja.uiHandler.selector = '[data-naja]';
// or
naja.uiHandler.selector = ':not(.synchronous)';
// or
naja.uiHandler.selector = '';
Allowed origins

Note that if you change the selector to an opt-out (:not(.synchronous), empty string, etc.), all links will become asynchronous. Naja prevents you from shooting yourself in the foot and does not dispatch AJAX requests for external URLs unless you explicitly allow them:


The current origin is allowed by default, i.e. it does not matter whether the href in the link points to a relative path or an absolute one.

Manual dispatch

Since version 1.4.0, UIHandler exposes two helper methods for dispatching UI-bound requests manually. This is especially useful if you need to submit a form programmatically, because form.submit() does not trigger the form's submit event.


Neither element nor form have to be bound to Naja via the configured selector. However, the aforementioned allowed origin rules still apply, and the interaction event (see below) is triggered with originalEvent set to undefined.


RedirectHandler performs a redirection if there is redirect key in the response payload.

Note that if the target URL is local (same hostname), the redirect is actually done via another AJAX request. To prevent this and make a proper HTTP redirect, you can add forceRedirect: true to the payload.


SnippetHandler redraws snippets that come in the response payload's snippets key. You can mark the snippet with data-naja-snippet-prepend or data-naja-snippet-append attribute to prepend or append the snippet content, respectively, instead of replacing it. (data-ajax-prepend and data-ajax-append are supported as well.)

Snippet update events

While Naja exposes various events in the request's lifecycle to provide a flexible way of extending its behavior (see below), sometimes a more granular approach is handy. You can therefore attach event listeners to two SnippetHandler's events: beforeUpdate and afterUpdate. Both are dispatched for each updated snippet: the former is dispatched right before the snippet's content is updated, the latter after that. Both give you access to the HTML element in event.snippet and the content from payload in event.content.

You can use them like this:

naja.snippetHandler.addEventListener('afterUpdate', (event) => {
	if ( === 'snippet--alert') {

The beforeUpdate event listener might also prevent the snippet from updating by calling event.preventDefault(). In such case, the afterUpdate event is not dispatched for that snippet.


FormsHandler integrates Naja with nette-forms script if it is loaded. It initializes forms added to the page via snippets and prevents their submission if they fail to validate.


HistoryHandler synchronizes AJAX requests with the browser's History API. When a request finishes, HistoryHandler pushes the new state including the document's title, URL, and a snapshot of snippets content into the browser's history.

P/R/G considerations

Sometimes, in cases where you would employ the Post/Redirect/Get scheme, this behavior can add unneeded things to the URL. To prevent this, you need to hint Naja on what the target URL should be by adding postGet: true and url: <targetUrl> to the response payload. Generally, where you would do this:

if ($this->isAjax()) {

} else {

you should now do this instead:

if ($this->isAjax()) {
	$this->payload->postGet = TRUE;
	$this->payload->url = $this->link('this');

} else {
UI cache

HistoryHandler caches the UI state (content of all snippets) in the history entry's state and reapplies it when navigating through the history. (And does so cleverly, so that -prepend and -append snippets do not break.)

You might, however, want to disable the cache for specific snippets (e.g. the shopping cart) so that their content is not reverted when the user navigates through the history. This can be done by adding the data-naja-history-nocache attribute to the snippet element.

Replace instead of push

If you want to replace the current state instead of pushing a new one, which makes sense for certain types of signals, you can add history: 'replace' to the options object (see above) when calling makeRequest manually, or add data-naja-history="replace" attribute to the .ajax element.

Disabling history

Similarly, you can keep a request off-the-record and not alter the browser's history at all by adding history: false to the options or data-naja-history="off" attribute to the element.


ScriptLoader executes scripts that are dynamically added to the page via snippets. If you have scripts in your snippets, be aware that they might be executed multiple times as the user navigates the site. In many cases, you can also implement the desired behavior using extensions (see below).


Naja comes pre-packaged with two handy extensions (those are registered by default) and provides an API for you to implement your own extensions with ease.


This extension allows the user to cancel the pending request by pressing Esc. You can disable this behavior for a single request by passing abort: false in the options object (see above) when calling makeRequest manually, or by adding data-naja-abort="off" attribute to the .ajax element.


This extension disallows multiple requests to run concurrently. When you dispatch a new request, the old one is aborted. Again, you can disable this by passing unique: false in the options or adding the data-naja-unique="off" attribute.

Custom extensions

The true power of Naja is in how easy you can implement your own extensions to integrate your web application with the flow of the AJAX requests. You already know how to do it, since Naja class implements the same EventTarget interface as many DOM elements do. Specifically, it dispatches the following events:

  • init: This event is dispatched when naja.initialize() is called. It can be used to initialize all the necessities of the extension. If you've followed the instructions, the DOM is already loaded by the time this event is dispatched, so that you can access DOM elements in the listener. The init event has no properties.
  • load: This event is dispatched after init and then after every request, be it successful or not. It can be used to reload things, re-add event listeners, etc. It has no properties.
  • interaction: This event is dispatched when the user interacts with a DOM element that has the Naja's listener bound to it. It has the following properties:
    • element: HTMLElement, the element the user interacted with,
    • originalEvent: ?Event, the original UI event, or undefined if the request was dispatched via UIHandler.clickElement() or UIHandler.submitForm() (see above),
    • options: Object, an empty object that can be populated with options based on the element's attributes.
  • before: This event is dispatched when the XMLHttpRequest object is created but not yet sent. At this point, you can call the event's preventDefault() method to cancel the request. The event has the following properties:
    • xhr: XMLHttpRequest, the XHR object,
    • method: string, the requested HTTP method,
    • url: string, the requested URL,
    • data: mixed, the data to be sent along with the request,
    • options: Object.
  • start: This event is dispatched right after the request is sent. It has the following properties:
    • request: Promise, a Promise that resolves or rejects when the request is completed,
    • xhr: XMLHttpRequest, the XHR object.
  • abort: This event is dispatched if the request is aborted. Aborting the request does not trigger error handling because it is not an error per se, but it might be useful to react to it. This event has the following properites:
    • xhr: XMLHttpRequest, the aborted XHR object.
  • success: This event is dispatched when the request successfully finishes. It has the following properties:
    • xhr: XMLHttpRequest, the XHR object,
    • response: Object, the parsed response payload,
    • options: Object.
  • error: This event is dispatched when the request finishes with errors. It has the following properties:
    • error: Error, an object describing the error,
    • xhr: XMLHttpRequest, the XHR object,
    • response: ?Object, if provided,
    • options: Object.
  • complete: This event is dispatched when the request finishes, regardless of whether it succeeded or failed. It has the following properties:
    • error: ?Error, an object describing the error, if one occurred,
    • xhr: XMLHttpRequest, the XHR object,
    • response: ?Object, if provided,
    • options: Object.

Extension implementation

The extension receives in its constructor the instance of Naja and also additional arguments if provided. The constructor is where you should bind your event listeners. The rest of the implementation is entirely up to you.

For reference, this is an example implementation of an extension that shows and hides a loader element as the request is sent and completed:

class LoaderExtension {
    constructor(naja, loaderSelector) {
        naja.addEventListener('init', () => {
            this.loader = document.querySelector(loaderSelector);

        naja.addEventListener('start', this.showLoader.bind(this));
        naja.addEventListener('complete', this.hideLoader.bind(this));

    showLoader() { = 'block';

    hideLoader() { = 'none';

Note that the example above uses ES6. If you don't have the means to compile it, you should be just fine with something like this:

function LoaderExtension(naja, loaderSelector) {
    naja.addEventListener('init', function () {
        this.loader = document.querySelector(loaderSelector);

    naja.addEventListener('start', showLoader.bind(this));
    naja.addEventListener('complete', hideLoader.bind(this));

    function showLoader() { = 'block';

    function hideLoader() { = 'none';

    return this;

The one last step you need to take is to register the extension somewhere before calling naja.initialize(). You can provide as many arguments as you like, all of them are passed to the extension's constructor.

naja.registerExtension(LoaderExtension, '#loader');

Be sure to pass the extension's constructor, not an instantiated object! This is because Naja instantiates the extension in its initialize() method after its core components are loaded.

  • 1.4.0 1.4.0

    • Added UIHandler.clickElement() and UIHandler.submitForm() helper methods (#12) that allow you to dispatch UI-bound events manually. This is especially useful if you need to submit a form programmatically (e.g. on change of select box), because form.submit() does not trigger the form's submit event.
      • ⚠️ The helper methods trigger the interaction event with originalEvent set to undefined. This potentially breaks BC if you choose to use the newly introduced methods!
    • Simplified initial pop detection (#10). It doesn't appear to be a problem anymore, but in browsers where it still is a problem (some ancient Chromes and Safaris), this solution should work too while being elegantly simple. As a side effect, this should also prevent popstate behaviour in HistoryHandler being triggered on hashchange.
      • ⚠️ This also removes a few attributes from HistoryHandler that are no longer necessary: initialUrl, initialState, and popped. They were part of the internal API and were never documented, thus I don't consider their removal to be a BC break.
  • 1.3.2 1.3.2

    • Fixed incorrect data-naja-snippet-prepend and data-naja-snippet-append attributes detection in SnippetHandler
  • 1.3.1 1.3.1

    • Fixed incorrect data-naja-history-nocache attribute detection in HistoryHandler (#8, thanks @dakur)
  • 1.3.0 1.3.0

    • You can provide default options for your extensions or even Naja's core components via naja.defaultOptions.
    • The options object is now passed to the success, error, and complete event handlers. Components and extensions can now use the options object to pass metadata along with the request instead of having to keep track of them internally, which should prevent potential concurrency issues.
    • RedirectHandler reads the forceRedirect flag not only from response payload but also from options.
  • 1.2.0 1.2.0

    • SnippetHandler now exposes events that are dispatched prior to and after every snippet update.
    • I got rid of an unnecessary layer of inheritance in a small internal refactoring.
  • 1.1.0 1.1.0

    • Initialization of core services was refactored to allow for further changes.
    • The Promise returned by naja.makeRequest() is resolved with the parsed response body, or rejected with the thrown error. This allows you to chain custom behavior after the response is processed by Naja when calling makeRequest() manually.
    • Aborted requests are handled in a specific abort event. They no longer trigger error handling, as the reasons for aborting the request usually do not indicate an erroneous state.
    • The bound selector (.ajax) is now customizable via naja.uiHandler.selector.
    • To prevent people from shooting themselves in the foot, UIHandler now checks that the link target matches a set of allowed origins, and does not dispatch the asynchronous request if it does not match. You can configure the allowed origins by pushing them to naja.uiHandler.allowedOrigins.
    • Browser history manipulation can be disabled for specific requests by adding history: false to the request options, or data-naja-history="off" to the .ajax element.
    • Also, you can now decide on the front-end side whether you want to push a new state, or replace the current one, by adding history: 'replace' to the options or data-naja-history="replace" to the element.
    • Tests now run in real browsers via SauceLabs, so that there's a better guarantee that IE 10+ is actually supported. 🎉

Is this addon outdated? Did you find an issue? Please report it.

Componette Componette