David Luna

OpenTelemetry browser instrumentation using EDOT Browser & Kibana

A step-by-step guide on OpenTelemetry browser instrumentation. Learn how to add EDOT Browser to a web app, export browser telemetry via OTLP, and verify traces, spans, and service maps in Kibana.

Instrumenting the browser is key to understanding how users truly experience your web application. It lets you track frontend activity, such as page loads and interactions, giving visibility into performance and issues your users face in real time. Browser instrumentation links what happens on the client to backend services, allowing you to see the full picture and troubleshoot end-to-end. Getting started with OpenTelemetry in the browser has traditionally meant wrestling with multiple dependencies and a complex setup. We built EDOT Browser to make this simpler, so you can instrument web apps quickly with a streamlined experience. While EDOT Browser is in tech preview today, once generally available, it will be Elastic's supported, production-ready distribution of the OpenTelemetry Browser SDK. With EDOT Browser, you'll have an easier onboarding path today and official support through Elastic once the project reaches general availability.

This guide walks through a practical way to add EDOT Browser to a web application, send browser telemetry to Elastic, and verify the results in Kibana.

What you'll build

In this guide, you'll add EDOT Browser to your web app and make sure it is initialized early in the application's lifecycle. You'll configure an export path for OTLP data and set up a reverse proxy to handle authentication and CORS.

As you generate browser activity, the resulting telemetry will be viewable in Kibana. You should see external.http spans for browser fetch and XHR requests, user interaction spans such as click and submit, and full traces that connect browser activity to backend services. All of this data will be accessible through Kibana's Discover and Service Maps.

Prerequisites

Before you start, make sure you have:

  • a web application you can modify
  • access to Elastic Observability
  • an OTLP destination:
    • Elastic Managed OTLP, or
    • an EDOT / OpenTelemetry Collector
  • a reverse proxy or web server layer you can configure
  • an API key or other auth mechanism for the OTLP destination

Important:

  • Do not embed API keys directly in browser code.
  • Do not run EDOT Browser alongside another browser APM or RUM agent. Running multiple browser agents can cause duplicate telemetry, conflicting instrumentation, or unexpected behavior.

Architecture overview

A recommended production-friendly flow looks like this:

Using a reverse proxy offers several advantages in this architecture. It injects the Authorization header on the server side, keeping credentials out of frontend code. A reverse proxy also simplifies Cross-Origin Resource Sharing (CORS) management and handles preflight OPTIONS requests automatically. Additionally, it provides a natural place to implement rate limiting or apply other traffic controls.

Step-by-step guide

Step 1: Add EDOT Browser to your web app

Install the EDOT Browser package in your frontend project. EDOT Browser is a single package that bundles multiple OpenTelemetry dependencies and provides opinionated defaults for common browser telemetry use cases.

Add the bundle to your app the same way you add other assets in your frontend project. You can download it from the GitHub releases page. The files you need are:

  • elastic-otel-browser.min.js: this is the bundle file.
  • elastic-otel-browser.min.js.map: this is the map file in case you want to debug.

Note: EDOT Browser is also distributed as an NPM package, and the integration with your web app depends on the framework you use. From more details, please refer to the EDOT Browser reference documentation.

Step 2: Initialize EDOT Browser early

Initialize EDOT Browser as early as possible in your application startup so it can capture the following telemetry:

  • document and page load activity
  • user interactions
  • browser network requests
  • Core Web Vitals
  • browser-side runtime and performance metrics

Assuming you placed the bundle in /assets path of your web app. A simplified example looks like this:

<script src="/assets/elastic-otel-browser.min.js"></script>
<script>
  startBrowserSdk({
    serviceName: 'frontend-web-app',
    otlpEndpoint: '/otlp',
  });
</script>
</html>

The exact initialization API and option names should follow the EDOT Browser setup/configuration docs for your version, but conceptually you need:

  • a service name
  • an OTLP HTTP endpoint
  • any optional configuration your team wants to enable

Initializing EDOT Browser as early as possible in your application's startup sequence is crucial. Doing so ensures that you capture important telemetry, such as the initial page load, early user interactions, and any network activity that occurs right after the application loads. Delayed initialization can result in missing these valuable signals.

Step 3: Point the browser to a proxy endpoint

Instead of sending telemetry directly to Elastic from the browser, configure the browser SDK to send OTLP traffic to a path on your own app origin, for example /otlp. This gives you a same-origin endpoint from the browser's perspective and simplifies CORS handling.

startBrowserSdk({
  serviceName: 'frontend-web-app',
  otlpEndpoint: '/otlp',
});

Your reverse proxy will then forward /otlp to Elastic Managed OTLP or your EDOT / OpenTelemetry Collector.

Step 4: Configure the reverse proxy

Your reverse proxy should do three things:

  1. forward OTLP requests to the real backend endpoint (e.g., Elastic Managed OTLP endpoint)
  2. inject the Authorization header
  3. handle CORS and preflight requests if needed

Here is an example NGINX-style configuration sketch:

location /otlp/ {
    add_header Access-Control-Allow-Origin https://your-app.example.com;
    add_header Access-Control-Allow-Headers Content-Type,Authorization;
    add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
    add_header Access-Control-Allow-Credentials true;
    if ($request_method = OPTIONS) {
        return 204;
    }
    proxy_pass https://your-otlp-endpoint/;
    proxy_set_header Authorization "ApiKey YOUR_SERVER_SIDE_KEY";
    proxy_set_header Host your-otlp-endpoint;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

Adjust this to match your environment.

Important security note: Keep the API key on the server side only. Do not expose it in JavaScript bundles, HTML source, browser dev tools, or public config files.

Step 5: Make sure CORS is handled correctly

If your browser app and OTLP destination are on different origins, the browser may block export requests unless CORS is configured correctly.

To ensure proper communication between your application and the OTLP endpoint, your proxy or backend should be configured to accept requests from your app’s origin. It should also permit the necessary headers and HTTP methods. Additionally, make sure it can correctly handle preflight OPTIONS requests required by CORS. A same-origin proxy is often the easiest approach.

Step 6: Add a small test page

To verify that instrumentation is working, open or create a page that loads in the browser, allows you to click a button, and triggers a fetch request to a backend endpoint. Here is a minimal example:

<!DOCTYPE html>
<html>
  <head>
    <title>EDOT Browser Demo</title>
    <script src="/assets/elastic-otel-browser.min.js"></script>
    <script>
      startBrowserSdk({
        serviceName: 'frontend-web-app',
        otlpEndpoint: '/otlp',
      });
    </script>
  </head>
  <body>
    <h1>EDOT Browser Demo</h1>
    <button id="loadData">Load data</button>
    <script type="module">
      document.getElementById('loadData').addEventListener('click', async () => {
        const response = await fetch('/api/demo');
        const data = await response.json();
        console.log(data);
      });
    </script>
  </body>
</html>

Step 7: Start the app and generate traffic

To begin generating telemetry, start your application and open it in a browser. Once the application is running, load the page, click the button several times, and, if your app includes multiple pages, navigate between them. Also, perform any actions that trigger XHR or fetch requests to ensure the instrumentation captures typical user interactions.

At this point, EDOT Browser should be collecting and exporting telemetry.

Step 8: Verify the browser is sending telemetry

Before checking Kibana, verify the browser is actually making OTLP requests. Open browser developer tools and inspect the Network tab.

Look for requests to your configured endpoint, such as:

/otlp/v1/traces
/otlp/v1/metrics
/otlp/v1/logs

When verifying that the browser is sending telemetry, make sure that requests are actually being made to the expected endpoint. Check that responses are successful and confirm there are no CORS issues. Look out for any 401 or 403 authentication errors, and ensure that your proxy is correctly forwarding OTLP traffic. If you encounter failing requests, it is often due to an incorrect OTLP endpoint, missing authentication headers on the proxy, or a misconfigured CORS policy. Other common causes include mismatched proxy paths or initializing the SDK too late or incorrectly within your application.

Browser telemetry in Kibana

EDOT Browser captures browser-side telemetry including document load events, user interactions, network requests, and Core Web Vitals. In Kibana, this data typically appears as browser-originated spans, grouped interaction spans, correlated frontend and backend traces, and browser performance-related metrics. These insights are especially helpful for understanding slow frontend interactions, identifying slow backend calls triggered by the UI, spotting user-visible performance issues, and analyzing end-to-end request behavior.

Once the browser telemetry is flowing, you can use the curated UIs and dashboards in Kibana or use ES|QL, Dashboarding, Alerting and other Elastic Observability features to analyze the data.

In this example we used a very simple web application (play-app) with a Node.js backend (play-backend).

The service map shows the dependency between the browser app and backend service.

The service overview page provides a high-level view of the service, including typical RED metrics (i.e. rate, errors, duration) as well as an overview on the app's events / transactions (such as document load and click events).

In the trace waterfall view, you can see the full trace of the browser request and the backend response.

In addition to the APM views, users can install the RUM OpenTelemetry content pack that comes with a ready-to-use dashboard for browser telemetry and web vitals.

Common troubleshooting tips

If you do not see data in Kibana, check the following:

  1. Is the SDK initialized? Make sure EDOT Browser is actually loaded and initialized in the page.
  2. Is it initialized early enough? Late initialization can miss important startup telemetry.
  3. Is the OTLP endpoint correct? Verify the browser is sending to the expected path.
  4. Is the proxy forwarding correctly? Confirm the proxy target URL is correct.
  5. Is authentication being injected? Managed OTLP or the Collector may require an Authorization header.
  6. Is CORS blocking requests? Look for browser console or network errors related to cross-origin requests.
  7. Is another browser agent also installed? Do not run EDOT Browser alongside another APM or RUM browser agent.

Limitations to keep in mind

The following are not currently available in EDOT Browser:

  • full feature parity with classic Elastic APM browser agents
  • migration tooling from classic agents

If you are moving from a classic Elastic RUM agent, plan for testing and manual validation.

Summary

This article covered the basics of instrumenting a web application with EDOT Browser and viewing the data in Kibana. For a more detailed guide, please refer to the EDOT Browser reference documentation.

Share this article