Plugins

Plugins extend Ajax's request and swap lifecycle. A plugin is a plain object with optional hooks, registered globally via ajax.use() or per-registration via the plugins array on ajax.register().

Plugin interface

type Plugin = {
  key?: string    // optional identifier — per-registration plugins with the same key
                  // override the global plugin of the same key

  // Called once when an element is matched and attached to a registration
  attach?: (element: HTMLElement, config: AjaxConfig) => void

  // Wraps the fetch. Call next() to continue. Modify ctx.headers or ctx.body here.
  request?: (ctx: AjaxContext, next: () => Promise<void>) => Promise<void> | void

  // Wraps the DOM swap. Call next() to run the default swap (or the next plugin).
  // Omit next() to fully replace the default swap.
  swap?: (ctx: AjaxContext, next: () => Promise<void>) => Promise<void> | void

  // Called when an error is thrown anywhere in the pipeline
  error?: (error: unknown, ctx: AjaxContext) => void
}

Global plugin

ajax.use(myPlugin)

Per-registration plugin

Pass plugins in the plugins array on a registration. A per-registration plugin with a matching key replaces the global plugin of the same key for that request:

ajax.register({
  target: "#form",
  plugins: [myPlugin],
  swaps: [{ replace: "#result" }],
})

AjaxContext

The ctx object is passed to every request, swap, and error hook. It carries everything known about the current request, and can be mutated by plugins to influence what happens next.

type AjaxContext = {
  trigger: string        // DOM event name that fired (e.g. 'click', 'submit')
  element: HTMLElement   // the element the user interacted with
  url: string            // URL being fetched — from href or form action
  method: MethodType     // 'GET' for links; the form's method attribute for forms
  body?: FormData        // only present for form submissions
  headers: Record<string, string>  // mutate in a request hook to set custom headers

  config: AjaxConfig     // the full registration config for this request

  // populated after the fetch completes (available inside swap hooks):
  response?: Response
  nextDocument?: Document

  // populated after the swap runs:
  swappedElements: Element[]
}

The typical read/write pattern by hook:

Hook Typical reads Typical writes
request url, method, body headers, body
swap nextDocument, swappedElements nextDocument (to modify before swap)
error url, config

Writing a plugin

const timingPlugin = {
  async request(ctx, next) {
    const start = performance.now()
    await next()
    console.log(`${ctx.url} fetched in ${performance.now() - start}ms`)
  },
}

ajax.use(timingPlugin)

Lifecycle events

Ajax dispatches custom events on document at each stage. These are emitted by the built-in events plugin and are available to any listener on the page:

Event Detail
ajax:attach { element, config }
ajax:before-request AjaxContext
ajax:after-request AjaxContext
ajax:before-swap AjaxContext
ajax:after-swap AjaxContext
ajax:error { error, context }