HTMX observability reference

Overview

This topic documents how to get started with the LaunchDarkly observability plugins in an htmx application.

htmx is a lightweight library that lets you build modern web applications using HTML attributes to issue AJAX requests, handle events, and update the DOM, without writing JavaScript. htmx applications render server-provided html and text fragments without a JavaScript framework, so you can use the LaunchDarkly client-side JavaScript SDK directly in htmx applications.

The LaunchDarkly JavaScript SDK supports the following observability plugins:

  • An observability plugin for error monitoring, logging, and tracing.
  • A session replay plugin that provides a way to record and replay end-user sessions from your application.

LaunchDarkly’s observability and session replay plugins use DOM mutation observers to track changes when htmx swaps content in or out of the page. The plugins capture these changes automatically, requiring no additional configuration. To learn more, read How htmx works with observability.

SDK quick links

LaunchDarkly’s SDKs are open source. In addition to this reference guide, we provide source, API reference documentation, and a sample application:

ResourceLocation
SDK API documentationObservability plugin API docs
Session replay API docs
GitHub repository@launchdarkly/observability
Published modulenpm
For use in client applications only

The session replay plugins are available only for the LaunchDarkly client-side JavaScript-based SDKs.

To learn more about LaunchDarkly’s different SDK types, read Choosing an SDK type.

Prerequisites and dependencies

This reference guide assumes that you are familiar with the LaunchDarkly JavaScript SDK and with htmx.

The observability plugin requires JavaScript SDK version 3.7.0 or later.

How htmx works with observability

htmx applications differ from traditional single-page applications (SPAs) in important ways that affect observability:

  • Server-rendered HTML: htmx applications receive HTML fragments from the server rather than JSON data. The server responses determine what content is rendered.
  • Partial DOM updates: htmx swaps portions of the DOM using AJAX requests, rather than performing full page reloads. The SDK and observability plugins load once and persist across all htmx interactions.
  • No client-side routing: htmx uses hx-boost and hx-push-url attributes to update the browser URL, but does not use a client-side router.

The observability plugins work well with htmx because they use DOM mutation observers to track changes. When htmx swaps content in or out of the page, the session replay plugin automatically captures these updates. No additional configuration is needed for basic observability and session replay.

Get started

Follow these steps to get started:

Install the plugins

LaunchDarkly uses plugins to the JavaScript SDK to provide observability. Most customers use both the observability and session replay plugins. However, there is no dependency between them, and you can use only one or the other if you like.

The first step is to make both the SDK and the observability plugins available as dependencies.

If your htmx application uses a bundler such as Vite, esbuild, or webpack, install the packages with a package manager:

$npm install @launchdarkly/js-client-sdk
$npm install @launchdarkly/observability
$npm install @launchdarkly/session-replay

Then, import the plugins into your code:

Import, JS SDK v4.x
1import { initialize } from "@launchdarkly/js-client-sdk";
2import Observability, { LDObserve } from "@launchdarkly/observability";
3import SessionReplay, { LDRecord } from "@launchdarkly/session-replay";

If your htmx application does not use a bundler, you can load the SDK and plugins using <script> tags in your HTML. Add these to your base template’s <head> element so they load once and persist across all htmx navigations.

Do not use script tags from unpkg or jsDelivr in production

Do not use script tags with sources from unpkg or jsDelivr in production environments. These introduce a critical dependency on a third-party service. Use the unpkg and jsDelivr scripts only during development for ease of use when getting started.

In production environments, we strongly recommend that you self-host the JavaScript SDK and observability plugins alongside your other JavaScript resources. To learn more, read Make the SDK available with a script tag in the JavaScript SDK reference guide.

1<head>
2 <script src="path/to/htmx.min.js"></script>
3 <script src="path/to/ldclient.min.js"></script>
4 <script src="path/to/ld-observability.min.js"></script>
5 <script src="path/to/ld-session-replay.min.js"></script>
6 <script>
7 var client = LDClient.initialize('example-client-side-id', {
8 plugins: [
9 new LDObservability(),
10 new LDSessionReplay()
11 ]
12 });
13 </script>
14</head>

Initialize the client

Next, initialize the SDK and the plugins.

To initialize, you need your LaunchDarkly environment’s client-side ID. This authorizes your application to connect to a particular environment within LaunchDarkly. To learn more, read Initialize the client in the JavaScript SDK reference guide.

JavaScript observability SDK credentials

The JavaScript observability SDK requires a client-side ID. Client-side IDs are specific to each project and environment. They are not secret, and you can include them in client-side code. Do not embed a server-side SDK key in a client-side application.

You can find client-side IDs and project keys in Project settings, on the Environments list. To learn more about key types, read Keys.

In an htmx application, you should initialize the SDK once, typically in your base HTML template or application entry point. The SDK persists across htmx navigations because htmx swaps DOM content rather than performing full page reloads.

Initialize SDK client and plugins together

Here’s how to initialize the SDK and plugins:

Initialize, JS SDK v4.x
1const client = initialize('example-client-side-id', {
2 plugins: [ new Observability(), new SessionReplay() ]
3});

Initialize the plugins after the SDK client

You can initialize the observability and session replay plugins manually, after the SDK client is initialized.

This approach supports feature-flagged rollouts or dynamic initialization after end user consent. Both plugins use a manualStart option combined with .start() calls.

First, configure the plugins with manualStart: true:

Manual start configuration, JS SDK v4.x
1const client = initialize('example-client-side-id', {
2 plugins: [
3 new Observability({ manualStart: true }),
4 new SessionReplay({ manualStart: true })
5 ]
6});

Then, start the plugins when appropriate, such as after receiving end user consent or when a feature flag enables observability.

Here’s an example starting the observability plugin:

1// Start observability after user consent or feature flag check
2if (userConsentReceived || featureFlagEnabled) {
3 LDObserve.start();
4}

Here’s an example with the session replay plugin:

1LDRecord.start({
2 silent: false // if true, console.warn messages created in this method are skipped
3});

This approach allows you to:

  • Feature-flag the rollout of observability to a subset of end users
  • Wait for end user consent before starting data collection
  • Dynamically enable observability based on runtime conditions
  • Maintain compliance with privacy regulations

Configure the plugin options

You can configure options for the observability plugins when you initialize the SDK. The plugin constructors take an optional object with the configuration details.

Session replays are obscured by default

By default, session replays use the strict privacy setting, which obscures some data. Use none to turn off session replay obfuscation. This enables more detail on session replays, but may expose more of your customer data than your privacy or data retention policies allow. To learn more, read Session replay config.

Here is an example:

Plugin options, JS SDK v4.x
1const client = initialize('example-client-side-id', {
2 plugins: [
3 new Observability({
4 tracingOrigins: true, // attribute frontend requests to backend domains
5 networkRecording: {
6 enabled: true,
7 recordHeadersAndBody: true
8 }
9 }),
10 new SessionReplay({
11 privacySetting: 'strict',
12 // or 'default' to redact text matching common regex for PII
13 // or 'none' to turn off obfuscation
14 })
15 ]
16});
Network recording captures htmx requests

When networkRecording is enabled, the observability plugin automatically captures all HTTP requests made by htmx, including requests triggered by the hx-get, hx-post, hx-put, hx-patch, and hx-delete attributes. This gives you visibility into the performance and status of every htmx interaction.

For more information on plugin options, read Configuration for client-side observability and Configuration for session replay.

htmx-specific considerations

Session replay and DOM swapping

The session replay plugin uses DOM mutation observers to record changes. htmx updates the page by swapping HTML content in and out of the DOM, which the mutation observers capture automatically. This means session replay works out of the box with htmx, regardless of which swap strategy you use (innerHTML, outerHTML, beforebegin, afterend, and so forth).

If your htmx application uses hx-boost to convert standard links and forms into AJAX requests, the observability plugin handles these navigations correctly. The SDK stays initialized across boosted navigations because htmx swaps the page body content rather than performing a full page reload.

When hx-boost or hx-push-url updates the browser URL, the observability plugin detects this through the browser’s History API and tracks it as a navigation event.

Error tracking

The observability plugin automatically captures JavaScript errors through standard browser error handling. For htmx-specific errors, such as failed AJAX requests, the plugin captures these through network request monitoring when networkRecording is enabled.

If you want to track additional htmx-specific events, you can use the LDObserve API to log custom events:

Using LDObserve to track htmx-specific events
1// Log htmx request errors as custom observability events
2document.addEventListener("htmx:responseError", function(evt) {
3 LDObserve.error("htmx request failed", {}, {
4 url: evt.detail.pathInfo.requestPath,
5 status: evt.detail.xhr.status
6 });
7});

Script persistence across navigations

In htmx applications, scripts loaded in the <head> element persist across hx-boost navigations by default. This means the LaunchDarkly SDK initializes once and continues running throughout the user’s session. You do not need to reinitialize the SDK when htmx swaps page content.

If you use the htmx head-support extension, be aware that it merges <head> elements during boosted navigations. Scripts that are textually identical are preserved and not re-executed. This is the correct behavior for the LaunchDarkly SDK.

Set Content-Security-Policy (CSP)

If your application runs in an environment that enforces content security policies, you must set the Content-Security-Policy (CSP) in your application to tell the browser how your page can interact with third-party scripts.

Here are the policies you need to set to use the observability plugin:

  • connect-src: https://pub.observability.app.launchdarkly.com https://otel.observability.app.launchdarkly.com: This policy allows connecting with LaunchDarkly servers to send recorded observability data.
  • worker-src: data: blob:: This policy allows creating an inline web worker initialized by the npm package for this plugin.

Your CSP definition may look something like this:

Example CSP definition
1<meta
2 http-equiv="Content-Security-Policy"
3 content="connect-src: https://pub.observability.app.launchdarkly.com https://otel.observability.app.launchdarkly.com; worker-src data: blob:;"
4/>

Alternatively, you can set the CSP in the HTML document response header Content-Security-Policy. Check your initial app HTML document load for the header to make sure you are setting it to the desired value.

Explore supported features

The observability plugin supports the following features. After the SDK and plugins are initialized, you can access these from within your application:

Review observability data in LaunchDarkly

After you initialize the SDK and observability plugins, your application automatically starts sending observability data back to LaunchDarkly in the form of custom events. You can review this information in the LaunchDarkly user interface. To learn how, read Observability.

Specifically, the observability data includes events that LaunchDarkly uses to automatically create the following metrics:

  • Average, P95, and P99 Cumulative Layout Shift (CLS) per context (LaunchDarkly)
  • Average, P95, and P99 Document Load Latency per context (LaunchDarkly)
  • Percentage of users with errors (LaunchDarkly)
  • Average, P95, and P99 First Contentful Paint (FCP) per context (LaunchDarkly)
  • Average, P95, and P99 First Input Delay (FID) per context (LaunchDarkly)
  • Average, P95, and P99 Interaction to Next Paint (INP) per context (LaunchDarkly)
  • Average, P95, and P99 Largest Contentful Paint (LCP) (LaunchDarkly)
  • Average, P95, and P99 Time to First Byte (TTFB) per context (LaunchDarkly)

To learn more, read Metrics autogenerated from observability events.