Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2859,6 +2859,10 @@ export const telemetry: NavMenuConstant = {
name: 'Log drains',
url: '/guides/telemetry/log-drains' as `/${string}`,
},
{
name: 'Tracing with the JS SDK',
url: '/guides/telemetry/client-side-tracing' as `/${string}`,
},
{
name: 'Reports',
url: '/guides/telemetry/reports' as `/${string}`,
Expand Down
90 changes: 90 additions & 0 deletions apps/docs/content/guides/telemetry/client-side-tracing.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
id: 'client-side-tracing'
title: 'Tracing with the JS SDK'
description: 'Propagate W3C trace context from the Supabase JS SDK through Supabase services'
---

The Supabase JS SDK 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 (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](https://opentelemetry.io/docs/languages/js/getting-started/) 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:

```ts
import { trace } from '@opentelemetry/api'
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(SUPABASE_URL, SUPABASE_KEY, {
tracePropagation: true,
})

const tracer = trace.getTracer('my-app')

await tracer.startActiveSpan('fetch-users', async (span) => {
// Outgoing request carries the active trace context.
const { data, error } = await supabase.from('users').select('*')
span.end()
})
```

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:

```ts
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.
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. |

## 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](/guides/telemetry/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`).
Original file line number Diff line number Diff line change
Expand Up @@ -52630,8 +52630,46 @@ exports[`TS type spec parsing > matches snapshot 1`] = `
"type": "union",
"subTypes": [
{
"type": "nameOnly",
"name": "TracePropagationOptions"
"type": "object",
"name": "TracePropagationOptions",
"properties": [
{
"name": "enabled",
"type": {
"type": "intrinsic",
"name": "boolean"
},
"isOptional": true,
"comment": {
"shortText": "Enable trace propagation. Disabled by default.\\n\\nWhen enabled, automatically detects and propagates active trace context\\nfrom the OpenTelemetry API to outgoing Supabase requests. Trace context\\nis only propagated to Supabase domains (\`*.supabase.co\`, \`*.supabase.in\`,\\n\`localhost\`) for security — third-party hosts never receive trace headers.",
"examples": [
{
"id": "example-1",
"name": "Example 1",
"code": "\`\`\`ts\\nconst supabase = createClient(url, key, {\\n tracePropagation: { enabled: true },\\n})\\n\`\`\`"
}
]
}
},
{
"name": "respectSamplingDecision",
"type": {
"type": "intrinsic",
"name": "boolean"
},
"isOptional": true,
"comment": {
"shortText": "Respect upstream sampling decisions.\\n\\nWhen true (the default), trace context is not propagated if the upstream\\ntrace indicates non-sampling (sampled flag = \`0\` in the \`traceparent\`\\nheader). This avoids overhead when traces are being recorded but dropped.\\n\\nSet to \`false\` to always propagate, regardless of the sampling decision\\n— useful when you want every Supabase request tagged with a \`trace_id\`\\nfor log correlation, even if the trace itself will not be exported.",
"examples": [
{
"id": "always-propagate-ignore-sampling",
"name": "Always propagate, ignore sampling",
"code": "\`\`\`ts\\nconst supabase = createClient(url, key, {\\n tracePropagation: { enabled: true, respectSamplingDecision: false },\\n})\\n\`\`\`"
}
]
}
}
]
},
{
"type": "intrinsic",
Expand All @@ -52641,12 +52679,22 @@ exports[`TS type spec parsing > matches snapshot 1`] = `
},
"isOptional": true,
"comment": {
"shortText": "Enable OpenTelemetry / W3C trace context propagation to Supabase services.\\n\\nDisabled by default. Pass \`true\` for the common case (auto-detect an\\nactive OTel context and inject \`traceparent\` / \`tracestate\` / \`baggage\`\\nheaders) or an object for fine-grained control.\\n\\nRequires \`@opentelemetry/api\` to be installed in your application; if\\nnot present, the SDK silently no-ops.",
"shortText": "Enable OpenTelemetry / W3C trace context propagation to Supabase services.\\n\\nDisabled by default. Pass \`true\` for the common case (auto-detect an\\nactive OpenTelemetry context and inject \`traceparent\` / \`tracestate\` /\\n\`baggage\` headers) or an object for fine-grained control.\\n\\nRequires \`@opentelemetry/api\` to be installed in your application; if\\nnot present, the SDK silently no-ops. Trace headers are only attached\\nto requests targeting Supabase domains, so third-party hosts called\\nthrough a custom \`fetch\` are never tagged.\\n\\nThe resulting \`trace_id\` appears in Supabase logs (API Gateway, Edge\\nFunctions), letting you correlate client-side spans with server-side\\nlog entries — including logs forwarded via Log Drains.",
"examples": [
{
"id": "example-1",
"name": "Example 1",
"code": "\`\`\`ts\\n// Shorthand — opt in with defaults.\\ncreateClient(url, key, { tracePropagation: true })\\n\\n// Advanced — always propagate, even for non-sampled traces.\\ncreateClient(url, key, {\\n tracePropagation: { enabled: true, respectSamplingDecision: false },\\n})\\n\`\`\`"
"id": "shorthand-opt-in-with-defaults",
"name": "Shorthand — opt in with defaults",
"code": "\`\`\`ts\\nimport { createClient } from '@supabase/supabase-js'\\n\\nconst supabase = createClient(url, key, { tracePropagation: true })\\n\`\`\`"
},
{
"id": "with-an-active-opentelemetry-span",
"name": "With an active OpenTelemetry span",
"code": "\`\`\`ts\\nimport { createClient } from '@supabase/supabase-js'\\nimport { trace } from '@opentelemetry/api'\\n\\nconst supabase = createClient(url, key, { tracePropagation: true })\\nconst tracer = trace.getTracer('my-app')\\n\\nawait tracer.startActiveSpan('fetch-users', async (span) => {\\n // Request carries the active trace context.\\n const { data, error } = await supabase.from('users').select('*')\\n span.end()\\n})\\n\`\`\`"
},
{
"id": "advanced-always-propagate-even-for-non-sampled-traces",
"name": "Advanced — always propagate, even for non-sampled traces",
"code": "\`\`\`ts\\nconst supabase = createClient(url, key, {\\n tracePropagation: { enabled: true, respectSamplingDecision: false },\\n})\\n\`\`\`"
}
]
}
Expand Down Expand Up @@ -52708,6 +52756,12 @@ exports[`TS type spec parsing > matches snapshot 1`] = `
"id": "with-a-database-query",
"name": "With a database query",
"code": "\`\`\`ts\\nimport { createClient } from '@supabase/supabase-js'\\n\\nconst supabase = createClient('https://xyzcompany.supabase.co', 'your-publishable-key')\\n\\nconst { data } = await supabase.from('profiles').select('*')\\n\`\`\`"
},
{
"id": "with-opentelemetry-tracing",
"name": "With OpenTelemetry tracing",
"code": "\`\`\`ts\\nimport { createClient } from '@supabase/supabase-js'\\nimport { trace } from '@opentelemetry/api'\\n\\nconst supabase = createClient('https://xyzcompany.supabase.co', 'your-publishable-key', {\\n tracePropagation: true,\\n})\\n\\nconst tracer = trace.getTracer('my-app')\\n\\nawait tracer.startActiveSpan('fetch-users', async (span) => {\\n // Outgoing request carries the active trace context.\\n const { data, error } = await supabase.from('users').select('*')\\n span.end()\\n})\\n\`\`\`",
"description": "Opt in to W3C trace context propagation so the \`trace_id\` from your\\nclient-side spans is attached to Supabase requests and appears in API\\nGateway and Edge Function logs. Requires \`@opentelemetry/api\` to be\\ninstalled in your application. See [Tracing with the JS SDK](https://supabase.com/docs/guides/telemetry/client-side-tracing)."
}
]
}
Expand Down
37 changes: 37 additions & 0 deletions apps/docs/features/ui/AgentPluginsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ const PLUGIN_CLIENTS: PluginClient[] = [
repoUrl: 'https://github.com/supabase-community/gemini-extension',
docsUrl: 'https://geminicli.com/docs/extensions/',
},
{
key: 'github-copilot',
label: 'GitHub Copilot',
icon: 'copilot',
hasDistinctDarkIcon: true,
repoUrl: 'https://github.com/supabase-community/supabase-plugin',
docsUrl:
'https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/plugins-finding-installing',
},
]

function PluginInstructions({ client }: { client: PluginClient }) {
Expand Down Expand Up @@ -163,6 +172,34 @@ function PluginInstructions({ client }: { client: PluginClient }) {
)
}

if (client.key === 'github-copilot') {
return (
<div className="space-y-4">
<div className="space-y-2">
<h4 className="text-sm font-medium">From GitHub</h4>
<p className="text-xs text-foreground-lighter">
Install the Supabase plugin directly from the{' '}
<a
href="https://github.com/supabase-community/supabase-plugin"
target="_blank"
rel="noopener noreferrer"
className="text-brand-link hover:underline"
>
GitHub repository
</a>
.
</p>
<CodeBlock
value="copilot plugin install supabase-community/supabase-plugin"
language="bash"
focusable={false}
className="block"
/>
</div>
</div>
)
}

return null
}

Expand Down
2 changes: 2 additions & 0 deletions apps/docs/public/humans.txt
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ Dennis Senn
Dimitrios Liappis
Div Arora
Divit D
Divya Sharma
Douglas Hunley
Dustin Keib
Eduardo Gurgel
Expand Down Expand Up @@ -243,6 +244,7 @@ Sean Oliver
Sean Romberg
Sean Thompson
Sergio Cioban Filho
Shaii O
Shane E
Shreekar Shetty
Sreyas Udayavarman
Expand Down
Loading
Loading