diff --git a/apps/docs/content/guides/functions/cors.mdx b/apps/docs/content/guides/functions/cors.mdx
index 1ca46368fd5..351beb10421 100644
--- a/apps/docs/content/guides/functions/cors.mdx
+++ b/apps/docs/content/guides/functions/cors.mdx
@@ -55,16 +55,34 @@ export default {
}
```
-This approach ensures that when new headers are added to the Supabase SDK, your Edge Functions automatically include them, preventing CORS errors.
+Importing from the SDK keeps your allow-list aligned with the headers the client libraries send: when you upgrade the SDK version in your function and redeploy, newly added headers are picked up with it. As of `@supabase/supabase-js` v2.112.3 the list includes the trace context headers (`traceparent`, `tracestate`, `baggage`) used by [client-side tracing](/docs/guides/monitoring-and-debugging/client-side-tracing) — functions deployed with an older version need a redeploy before browsers can call them with trace propagation enabled.
+
+
+
+The allow-list only states what a browser _may_ send — it doesn't change what the SDK sends. Trace context headers are sent exclusively by clients that explicitly opt in to trace propagation (`tracePropagation: true`, disabled by default). Clients that haven't opted in send exactly the same headers as before.
+
+
+
+The full list, and when each header is sent:
+
+| Header | Sent |
+| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
+| `authorization` | Every request (session token or API key) |
+| `apikey` | Every request |
+| `x-client-info` | Every request (SDK name and version) |
+| `content-type` | Requests with a body |
+| `x-retry-count` | Only on automatic retry attempts (`postgrest-js` retries failed idempotent requests by default) |
+| `traceparent`, `tracestate`, `baggage` | **Only when [trace propagation](/docs/guides/monitoring-and-debugging/client-side-tracing) is explicitly enabled** — never by default |
### For versions before 2.95.0
-If you're using `@supabase/supabase-js` before v2.95.0, you'll need to hardcode the CORS headers. Add a `cors.ts` file within a [`_shared` folder](/docs/guides/functions/development-environment#recommended-project-structure):
+If you're using `@supabase/supabase-js` before v2.95.0, you'll need to hardcode the CORS headers. Add a `cors.ts` file within a [`_shared` folder](/docs/guides/functions/development-environment#recommended-project-structure). The list must cover every header your calling clients send — include the trace context headers if any client enables `tracePropagation`:
```ts _shared/cors.ts
export const corsHeaders = {
'Access-Control-Allow-Origin': '*',
- 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
+ 'Access-Control-Allow-Headers':
+ 'authorization, x-client-info, apikey, content-type, x-retry-count, traceparent, tracestate, baggage',
}
```
diff --git a/apps/docs/content/guides/monitoring-and-debugging/client-side-tracing.mdx b/apps/docs/content/guides/monitoring-and-debugging/client-side-tracing.mdx
index 44b8a378ac4..23ce8095775 100644
--- a/apps/docs/content/guides/monitoring-and-debugging/client-side-tracing.mdx
+++ b/apps/docs/content/guides/monitoring-and-debugging/client-side-tracing.mdx
@@ -6,7 +6,7 @@ description: 'Propagate W3C trace context from the Supabase JS, Swift, and Dart
The Supabase JS, Swift, and Dart SDKs can attach [W3C Trace Context](https://www.w3.org/TR/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 (such as OpenTelemetry, Sentry, Datadog, or Honeycomb) can pick up the trace on the server side, including in self-hosted collectors.
+Because the headers follow the W3C standard, any compliant tracing SDK (such as OpenTelemetry, Sentry, Datadog, or Honeycomb) can pick up the trace on the server side, including in self-hosted collectors. On the client side, some vendor SDKs need a small configuration change before they emit the standard headers — see [Using a vendor tracing SDK](#using-a-vendor-tracing-sdk) in the JavaScript tab.
{
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.
+
+
+Calling Edge Functions from the browser with trace propagation enabled requires the function's CORS allow-list to include the trace headers. In the function, import `corsHeaders` from `npm:@supabase/supabase-js@^2.112.3/cors` or add `traceparent`, `tracestate`, and `baggage` to your own allow-list, then redeploy the function. See [CORS support for Edge Functions](/docs/guides/functions/cors).
+
+
+
## Advanced configuration
Pass an object instead of `true` for fine-grained control:
@@ -81,22 +87,33 @@ import '@supabase/supabase-js/tracing'
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY, {
tracePropagation: {
enabled: true,
- // Default: true. When false, headers are attached even if the
- // upstream trace is not sampled — useful when you want every
- // Supabase request tagged with a trace_id for log correlation.
+ // Default: true. Non-sampled requests carry only `traceparent` (with the
+ // sampled flag preserved, so nothing is recorded downstream) — log
+ // correlation keeps working while `tracestate` and `baggage` are withheld.
+ // Set to false to always send the full trace context regardless of sampling.
respectSamplingDecision: false,
},
})
```
-| Option | Type | Default | Description |
-| ------------------------- | --------- | ------- | ------------------------------------------------------------------- |
-| `enabled` | `boolean` | `false` | Enable trace propagation. |
-| `respectSamplingDecision` | `boolean` | `true` | If `true`, skip propagation when the upstream trace is not sampled. |
+| Option | Type | Default | Description |
+| ------------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `enabled` | `boolean` | `false` | Enable trace propagation. |
+| `respectSamplingDecision` | `boolean` | `true` | When `true`, non-sampled requests send only `traceparent` (sampled flag preserved) and omit `tracestate` and `baggage`; `false` always sends the full trace context. On versions before `2.112.3`, `true` skipped all trace headers for non-sampled requests. |
## 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. 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.
+Many tracing SDKs are built on top of OpenTelemetry, but they differ in whether their propagator emits the standard `traceparent` header by default:
+
+| Vendor setup | Works with `tracePropagation`? | Required configuration |
+| --------------------------------------------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| OpenTelemetry SDK (also Honeycomb, Grafana, New Relic via OTLP) | Yes | None — the W3C propagator is the default |
+| Sentry (Node.js, including Next.js server-side) | Yes, with one flag | Set `propagateTraceparent: true` in `Sentry.init()` — Sentry's propagator omits `traceparent` by default |
+| Sentry (browser) | Via Sentry's own instrumentation | Set `propagateTraceparent: true` and add your project URL (`https://[.supabase.co`) to `tracePropagationTargets` — Sentry's browser SDK only attaches headers cross-origin for listed targets. For Edge Functions, also add `sentry-trace` to the function's CORS allow-list: Sentry always sends its own header, and it isn't part of `corsHeaders` |
+| Datadog `dd-trace` (Node.js) | Out of the box | None — `dd-trace` injects W3C headers at the HTTP layer itself, even without `tracePropagation` |
+| Datadog Browser RUM | Yes, with configuration | Add your project URL to `allowedTracingUrls` with the `tracecontext` propagator type |
+
+If a propagator is active but doesn't emit `traceparent`, the SDK logs a one-time console warning naming the headers the propagator wrote (version `2.112.3` and later).
## Troubleshooting
@@ -106,7 +123,8 @@ The SDK never throws when it can't propagate, which keeps it safe to enable but
- **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. On `2.112.0` and later the tracing subpath imports it directly, so a missing package surfaces as a module resolution error. On `2.106.0`–`2.111.x` it's loaded dynamically and the SDK silently no-ops.
- **No `TracerProvider` registered.** `@opentelemetry/api` defaults to a noop provider that produces non-recorded spans. Ensure 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.
+- **Your tracing SDK's propagator doesn't emit W3C `traceparent`.** Sentry's propagator, for example, only emits it when `propagateTraceparent: true` is set. From version `2.112.3` the SDK logs a one-time warning naming the headers the propagator wrote — see [Using a vendor tracing SDK](#using-a-vendor-tracing-sdk).
+- **The upstream trace is not sampled** (versions before `2.112.3`). Older versions skip all trace headers when the upstream trace is not sampled. From `2.112.3`, non-sampled requests still carry `traceparent`, so log correlation keeps working by default. Set `respectSamplingDecision: false` to always send the full trace context.
- **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.
diff --git a/apps/www/supabase/functions/_shared/cors.ts b/apps/www/supabase/functions/_shared/cors.ts
index 2ac4d89b14a..6fa1092e9de 100644
--- a/apps/www/supabase/functions/_shared/cors.ts
+++ b/apps/www/supabase/functions/_shared/cors.ts
@@ -1,4 +1,5 @@
export const corsHeaders = {
'Access-Control-Allow-Origin': '*',
- 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
+ 'Access-Control-Allow-Headers':
+ 'authorization, x-client-info, apikey, content-type, x-retry-count, traceparent, tracestate, baggage',
}
diff --git a/examples/edge-functions/supabase/functions/_shared/cors.ts b/examples/edge-functions/supabase/functions/_shared/cors.ts
index a418716bcf4..645cce8fa2a 100644
--- a/examples/edge-functions/supabase/functions/_shared/cors.ts
+++ b/examples/edge-functions/supabase/functions/_shared/cors.ts
@@ -8,5 +8,6 @@
export const corsHeaders = {
'Access-Control-Allow-Origin': '*',
- 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
+ 'Access-Control-Allow-Headers':
+ 'authorization, x-client-info, apikey, content-type, x-retry-count, traceparent, tracestate, baggage',
}
]