Telemetry

Tracing with the JS SDK


The Supabase JS SDK can attach W3C Trace Context headers (traceparent, tracestate, baggage) to outgoing requests. The resulting trace_id flows through Supabase services and appears in API Gateway and Edge Function logs, so you can correlate client-side spans with the server-side logs they produced — end-to-end, across the network boundary.

Because the headers follow the W3C standard, any compliant tracing SDK (OpenTelemetry, Sentry, Datadog, Honeycomb, etc.) can pick up the trace on the server side, including in self-hosted collectors.

Requirements#

  • @supabase/supabase-js version 2.106.0 or later
  • @opentelemetry/api available at runtime — either installed directly or pulled in as a transitive dependency of your tracing SDK
  • A tracing SDK that registers a W3C-compliant propagator with the OpenTelemetry API

If @opentelemetry/api is not installed, or there is no active trace context at request time, the SDK silently no-ops.

Set up OpenTelemetry first#

The SDK reads from whatever TracerProvider you register globally — it doesn't configure one for you. If you haven't instrumented your app yet, follow the OpenTelemetry JavaScript getting started guide to install an SDK (@opentelemetry/sdk-trace-node for Node, @opentelemetry/sdk-trace-web for browsers) and an exporter for your backend (OTLP, Jaeger, Zipkin, or a vendor-specific one).

The Supabase SDK only takes care of propagating the trace context that's already active when a request is made.

Enable trace propagation#

Trace propagation is opt-in. Pass tracePropagation: true when creating the client:

1
import { trace } from '@opentelemetry/api'
2
import { createClient } from '@supabase/supabase-js'
3
4
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY, {
5
tracePropagation: true,
6
})
7
8
const tracer = trace.getTracer('my-app')
9
10
await tracer.startActiveSpan('fetch-users', async (span) => {
11
// Outgoing request carries the active trace context.
12
const { data, error } = await supabase.from('users').select('*')
13
span.end()
14
})

For security, trace headers are only attached to requests targeting Supabase domains (*.supabase.co, *.supabase.in, and localhost for local development). Third-party hosts called through a custom fetch are never tagged.

Advanced configuration#

Pass an object instead of true for fine-grained control:

1
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY, {
2
tracePropagation: {
3
enabled: true,
4
// Default: true. When false, headers are attached even if the
5
// upstream trace is not sampled — useful when you want every
6
// Supabase request tagged with a trace_id for log correlation.
7
respectSamplingDecision: false,
8
},
9
})
OptionTypeDefaultDescription
enabledbooleanfalseEnable trace propagation.
respectSamplingDecisionbooleantrueIf true, skip propagation when the upstream trace is not sampled.

Correlating with Supabase logs#

Once trace context is flowing through, the trace_id appears in:

  • API Gateway logs — every request to PostgREST, Auth, Storage, and Realtime
  • Edge Function logs — invocations and any structured logs emitted from within the function

If you forward Supabase logs to a third-party backend via Log Drains, you can join Supabase logs to your own client and server traces using the shared trace_id. This is especially useful for self-hosted setups where you already operate your own OpenTelemetry collector — Supabase logs become first-class citizens in your existing tracing UI.

Using a vendor tracing SDK#

Many tracing SDKs are built on top of OpenTelemetry. They work with this guide as long as a W3C-compliant propagator is registered — but propagator behavior varies. Some vendor SDKs inject only their proprietary headers by default and need extra configuration to also emit the standard traceparent header. Check your vendor's OTel integration docs for the exact setup.

Troubleshooting#

The SDK silently no-ops when it can't propagate, which keeps it safe to enable but can mask configuration issues. If trace_id is missing from your Supabase logs, check these in order:

  • No active span at request time. The SDK reads the current context. If supabase.from(...) is called outside tracer.startActiveSpan(...) (or equivalent), there's nothing to propagate. Wrap the call in a span or use OpenTelemetry's automatic instrumentation.
  • @opentelemetry/api is not installed in the app making the request. The SDK imports it dynamically and no-ops if it's missing.
  • No TracerProvider registered. @opentelemetry/api defaults to a noop provider that produces non-recorded spans. Make sure your app calls provider.register() (or your vendor SDK's equivalent) before making requests.
  • The upstream trace is not sampled. By default the SDK respects upstream sampling decisions. Set respectSamplingDecision: false to propagate every request regardless of sampling.
  • You're calling a non-Supabase host through a custom fetch. Trace headers are only attached to Supabase domains (*.supabase.co, *.supabase.in, localhost).