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-jsversion2.106.0or later@opentelemetry/apiavailable 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
As of @supabase/supabase-js version 2.112.0, the OpenTelemetry integration lives in an opt-in subpath that you load once at your application entry point:
1import '@supabase/supabase-js/tracing'The main bundle contains no OpenTelemetry code — this import is what wires it up. The subpath imports @opentelemetry/api directly, so your bundler includes it and module resolution fails loudly if it isn't installed. If tracePropagation is enabled without this import, the SDK logs a one-time warning and sends requests without trace headers.
On versions 2.106.0–2.111.x, the subpath doesn't exist — don't add the import there. Those versions load @opentelemetry/api dynamically and silently no-op when it's missing.
Trace propagation isn't available through the CDN (UMD) build — there's no way to load the tracing runtime there.
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 and takes two steps: load the tracing runtime at your entry point (version 2.112.0 and later), and pass tracePropagation: true when creating the client:
1import '@supabase/supabase-js/tracing'23import { trace } from '@opentelemetry/api'4import { createClient } from '@supabase/supabase-js'56const supabase = createClient(SUPABASE_URL, SUPABASE_KEY, {7 tracePropagation: true,8})910const tracer = trace.getTracer('my-app')1112await tracer.startActiveSpan('fetch-users', async (span) => {13 // Outgoing request carries the active trace context.14 const { data, error } = await supabase.from('users').select('*')15 span.end()16})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:
1import '@supabase/supabase-js/tracing'23const supabase = createClient(SUPABASE_URL, SUPABASE_KEY, {4 tracePropagation: {5 enabled: true,6 // Default: true. When false, headers are attached even if the7 // upstream trace is not sampled — useful when you want every8 // Supabase request tagged with a trace_id for log correlation.9 respectSamplingDecision: false,10 },11})| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable trace propagation. |
respectSamplingDecision | boolean | true | If 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 never throws 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:
- The tracing runtime isn't loaded (version
2.112.0and later).tracePropagationis enabled but your entry point never imports@supabase/supabase-js/tracing. The SDK logs a one-time console warning and sends requests without trace headers — look for that warning in your console. - No active span at request time. The SDK reads the current context. If
supabase.from(...)is called outsidetracer.startActiveSpan(...)(or equivalent), there's nothing to propagate. Wrap the call in a span or use OpenTelemetry's automatic instrumentation. @opentelemetry/apiis not installed in the app making the request. On2.112.0and later the tracing subpath imports it directly, so a missing package surfaces as a module resolution error. On2.106.0–2.111.xit's loaded dynamically and the SDK silently no-ops.- No
TracerProviderregistered.@opentelemetry/apidefaults to a noop provider that produces non-recorded spans. Make sure your app callsprovider.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: falseto 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). - You're using the CDN (UMD) build. Trace propagation isn't available there — the tracing runtime can't be loaded from a script tag.