From c2848114f81efec34e2d4fdea75e245556e24881 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Tue, 27 Oct 2026 12:55:50 -0400 Subject: [PATCH 01/17] docs(js): Document the managed tunnel and serveTunnelIntegration Adds the serveTunnelIntegration page, the browser tunnel: true option, a managed tunnel section in troubleshooting, framework getting-started snippets, and updates TanStack Start tunnelRoute docs for 11.10.0. --- .../integrations/servetunnel.mdx | 106 ++++++++++++++++++ .../common/configuration/options.mdx | 23 ++++ .../common/troubleshooting/index.mdx | 36 +++++- .../features/createSentryTunnelRoute.mdx | 4 +- .../features/sentryTanstackStart.mdx | 30 ++--- .../manual-setup/index.mdx | 2 +- .../javascript.nuxt.mdx | 5 +- .../javascript.astro.mdx | 19 ++++ .../javascript.nuxt.mdx | 19 ++++ .../javascript.sveltekit.mdx | 17 ++- 10 files changed, 235 insertions(+), 26 deletions(-) create mode 100644 docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx create mode 100644 platform-includes/getting-started-tunneling/javascript.astro.mdx create mode 100644 platform-includes/getting-started-tunneling/javascript.nuxt.mdx diff --git a/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx b/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx new file mode 100644 index 00000000000000..0d5d3487cb0b53 --- /dev/null +++ b/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx @@ -0,0 +1,106 @@ +--- +title: ServeTunnel +description: "Serves a tunnel on your server so browser events reach Sentry through your own origin, past ad blockers." +supported: + - javascript.sveltekit + - javascript.nuxt + - javascript.nitro + - javascript.astro + - javascript.react-router + - javascript.tanstackstart-react + - javascript.solidstart + - javascript.remix + - javascript.cloudflare +--- + + + +_Import name: `Sentry.serveTunnelIntegration`_ + +Ad blockers often drop requests to `*.ingest.sentry.io`. This integration serves a tunnel endpoint from your app's server, so the browser SDK can send its events to your own origin instead. The server forwards them to Sentry. + +Set it up on both sides. Add the integration to your server `Sentry.init`: + +```javascript {filename:Server} +Sentry.init({ + dsn: "___PUBLIC_DSN___", + integrations: [Sentry.serveTunnelIntegration()], +}); +``` + +And set `tunnel: true` in your browser `Sentry.init`: + +```javascript {filename:Browser} +Sentry.init({ + dsn: "___PUBLIC_DSN___", + tunnel: true, +}); +``` + +The browser and the server both derive the tunnel path from the DSN, for example `/qpo967`. You don't need a route file, a build step, or an environment variable. If your app is served under a base path, the SDK adds it on both sides. + + + +The tunnel is only served by the SDKs that answer requests from a framework hook: SvelteKit, Nuxt, Nitro, Astro, React Router (framework mode), TanStack Start, SolidStart, and Remix 3. On other servers, see [Using the `tunnel` Option](/platforms/javascript/troubleshooting/#using-the-tunnel-option). + + + + + +On Cloudflare Workers, import the integration from `@sentry/cloudflare` and add it to the options you pass to `withSentry`, `defineCloudflareOptions`, or `initCloudflareSentryHandle`: + +```javascript +import { serveTunnelIntegration } from "@sentry/cloudflare"; + +export default { + dsn: "___PUBLIC_DSN___", + integrations: [serveTunnelIntegration()], +}; +``` + + + +## How It Works + +- The tunnel answers before your app's own middleware, so auth or CSRF middleware doesn't block it. +- It only forwards envelopes whose DSN is allowed. By default, that's the server's own DSN. Other envelopes get a `403`, so the tunnel can't be used as an open proxy. +- It forwards only the user agent, client hint, `Origin`, and `Referer` headers. Cookies and auth headers never reach Sentry. The visitor's IP address is forwarded from the `cf-connecting-ip`, `x-vercel-forwarded-for`, or `x-forwarded-for` header. +- Tunnel requests don't create server spans. +- The tunnel is only served while the server SDK is enabled. Without a DSN, or with `enabled: false`, requests to the tunnel path get your app's 404 page, and the browser's events are lost. + +## Options + +### `allowedDsns` + +_Type: `string[]`_ + +DSNs to forward besides the server's own. Use this when the browser uses a different DSN than the server. The server's own DSN is always allowed. + +```javascript +Sentry.serveTunnelIntegration({ + allowedDsns: ["___PUBLIC_DSN___"], +}); +``` + +### `path` + +_Type: `string`_ + +A fixed tunnel path instead of the one derived from the DSN. The path is served as written, without the base path. Set the same path in the browser with the object form. A string `tunnel` is sent as `text/plain`, which the CSRF protection in SvelteKit and Astro rejects. + +```javascript {filename:Server} +Sentry.serveTunnelIntegration({ path: "/monitoring" }); +``` + +```javascript {filename:Browser} +Sentry.init({ + dsn: "___PUBLIC_DSN___", + tunnel: { path: "/monitoring" }, +}); +``` + +### `basePath` + +_Type: `string`_ + +The path prefix your app is served under, for example `/docs`. The SDK reads it from your framework config, so you only need this option when it can't. Set the same value in the browser `tunnel` option. diff --git a/docs/platforms/javascript/common/configuration/options.mdx b/docs/platforms/javascript/common/configuration/options.mdx index a5ae09f8897e8d..052f952fba7b9f 100644 --- a/docs/platforms/javascript/common/configuration/options.mdx +++ b/docs/platforms/javascript/common/configuration/options.mdx @@ -83,6 +83,8 @@ Environments are case-sensitive. The environment name can't contain newlines, sp + + Sets the URL that will be used to transport captured events. This can be used to work around ad-blockers or to have more granular control over events sent to Sentry. Adding your DSN is still required when using this option so necessary attributes can be set on the generated Sentry data. This option **requires the implementation** of a custom server endpoint. Learn more and find examples in [Dealing with Ad-Blockers](/platforms/javascript/troubleshooting/#dealing-with-ad-blockers). @@ -91,6 +93,27 @@ The endpoint must forward only the request body to Sentry and accept only your o + + + + + + +Where the browser SDK sends captured events instead of the Sentry ingest URL. Use it to work around ad blockers or to control which events reach Sentry. The DSN is still required, so the SDK can set the attributes it needs on your data. + +- A string (URL or relative path) sends events to it as written. This **requires the implementation** of a custom server endpoint. The endpoint must forward only the request body to Sentry and accept only your own DSNs. Learn more in [Using the `tunnel` Option](/platforms/javascript/troubleshooting/#using-the-tunnel-option). +- `true` sends events to the managed tunnel, a path on your own origin derived from the DSN. Your server serves it with `serveTunnelIntegration`. Available since version `11.10.0`. +- An object works like `true`, with these options: + - `path`: a fixed tunnel path, used as written without the base path. + - `basePath`: the path prefix your app is served under, for example `/docs`. Most framework SDKs set it for you. + - `trailingSlash`: appends a trailing slash to the tunnel path, for servers that redirect paths without one. + +On the server, `tunnel` only takes a URL. It's where the server SDK sends its own events, and it's independent from `serveTunnelIntegration`. + + + + + diff --git a/docs/platforms/javascript/common/troubleshooting/index.mdx b/docs/platforms/javascript/common/troubleshooting/index.mdx index e97f66b03a2b28..cc6f97a41d0d98 100644 --- a/docs/platforms/javascript/common/troubleshooting/index.mdx +++ b/docs/platforms/javascript/common/troubleshooting/index.mdx @@ -223,6 +223,40 @@ You can work around our CDN bundles being blocked by [using our NPM packages](#u + + + + + +The SDK can serve the tunnel for you. Set `tunnel: true` in your browser `Sentry.init`, and add `serveTunnelIntegration` to your server `Sentry.init`: + +```javascript {tabTitle:Browser} +Sentry.init({ + dsn: "___PUBLIC_DSN___", + tunnel: true, +}); +``` + +```javascript {tabTitle:Server} +Sentry.init({ + dsn: "___PUBLIC_DSN___", + integrations: [Sentry.serveTunnelIntegration()], +}); +``` + +The browser then sends its events to a path on your own origin derived from the DSN, and your server forwards them to Sentry. The SDK reads your app's base path from your framework config. + + + +The managed tunnel is only available for Remix 3. + + + +See `serveTunnelIntegration` for a fixed path, more allowed DSNs, and how the tunnel handles requests. If you can't use the managed tunnel, set up your own tunnel as explained below. + + + + @@ -258,7 +292,7 @@ Your tunnel endpoint must: - Accept only envelopes whose DSN is on a list of allowed DSNs. Otherwise, anyone can use your endpoint to send requests to other servers. - Send the envelope only to the host of that DSN. -On a JavaScript server with Fetch API `Request` and `Response` objects, `handleTunnelRequest` from `@sentry/core` (SDK version `10.40.0` and later) does this for you. Starting with versions `10.77.1` (v10) and `11.9.0` (v11), it also rejects requests larger than 20 MiB. +On a JavaScript server with Fetch API `Request` and `Response` objects, `handleTunnelRequest` from `@sentry/core` (SDK version `10.40.0` and later) does this for you. Starting with version `11.10.0`, the DSN of the active server SDK is always allowed, so `allowedDsns` is optional. It forwards the user agent, client hint, `Origin`, and `Referer` headers, and the visitor's IP address, but never cookies or auth headers. ```javascript import { handleTunnelRequest } from "@sentry/core"; diff --git a/docs/platforms/javascript/guides/tanstackstart-react/features/createSentryTunnelRoute.mdx b/docs/platforms/javascript/guides/tanstackstart-react/features/createSentryTunnelRoute.mdx index d3244d9717c84b..e27f458929227f 100644 --- a/docs/platforms/javascript/guides/tanstackstart-react/features/createSentryTunnelRoute.mdx +++ b/docs/platforms/javascript/guides/tanstackstart-react/features/createSentryTunnelRoute.mdx @@ -13,7 +13,7 @@ description: "Learn how to manually define a Sentry tunnel route in your TanStac the route file, the DSN allowlist, or can't use the Vite plugin. -The `createSentryTunnelRoute` helper returns a TanStack Start server-route configuration with a `POST` handler that forwards Sentry envelopes to Sentry's ingest servers. Use it inside a file route to expose a same-origin tunnel endpoint. It forwards only the request body to Sentry. +The `createSentryTunnelRoute` helper returns a TanStack Start server-route configuration with a `POST` handler that forwards Sentry envelopes to Sentry's ingest servers. Use it inside a file route to expose a same-origin tunnel endpoint. For a tunnel that needs no route file, use the [`tunnelRoute`](/platforms/javascript/guides/tanstackstart-react/features/sentryTanstackStart/#tunnel-route) option instead. ## Usage @@ -46,7 +46,7 @@ Sentry.init({ A list of DSN strings that the route is allowed to forward to. The route validates each incoming envelope against this list and rejects any DSN that isn't allowed. -If `allowedDsns` is omitted or empty, the route falls back to the DSN of the active server-side Sentry SDK at runtime. If neither is available, requests to the route return a 500 response. +Since version `11.10.0`, the DSN of the active server-side Sentry SDK is always allowed too. If `allowedDsns` is omitted or empty, the route uses only that DSN. If neither is available, requests to the route return a 500 response. ```typescript {filename:src/routes/monitor.ts} Sentry.createSentryTunnelRoute({ diff --git a/docs/platforms/javascript/guides/tanstackstart-react/features/sentryTanstackStart.mdx b/docs/platforms/javascript/guides/tanstackstart-react/features/sentryTanstackStart.mdx index 5663884faf889f..60c363debc735f 100644 --- a/docs/platforms/javascript/guides/tanstackstart-react/features/sentryTanstackStart.mdx +++ b/docs/platforms/javascript/guides/tanstackstart-react/features/sentryTanstackStart.mdx @@ -97,13 +97,15 @@ sentryTanstackStart({ -The `tunnelRoute` option registers a same-origin TanStack Start server route that forwards Sentry envelopes to Sentry's ingest servers. It also automatically sets the client `tunnel` option in `Sentry.init()` so browser events go through that route. This helps avoid ad blockers and corporate firewalls that block requests to `*.sentry.io`. It forwards only the request body to Sentry. +The `tunnelRoute` option serves a same-origin tunnel that forwards Sentry envelopes to Sentry's ingest servers. It also automatically sets the client `tunnel` option in `Sentry.init()` so browser events go through it. This helps avoid ad blockers and corporate firewalls that block requests to `*.sentry.io`. + +Since version `11.10.0`, the tunnel answers in the server entry, before TanStack Start's request middleware and routing. It works the same way as `serveTunnelIntegration`, which you can use instead of this option. `tunnelRoute` accepts three shapes: #### `tunnelRoute: true` (recommended) -Generates an opaque, unguessable route path for each dev session and production build. Because the path changes between builds, ad blockers can't reliably target it. +Serves a path derived from the DSN, under your router's base path. It's the same path that `Sentry.init({ tunnel: true })` uses. ```typescript {filename:vite.config.ts} sentryTanstackStart({ @@ -111,9 +113,14 @@ sentryTanstackStart({ }), ``` + + Before version `11.10.0`, `tunnelRoute: true` generated a new random path for + each dev session and production build. + + #### Static path -Pass a string to use a fixed route path. This is easier to reason about, but a known path is easier for ad blockers to add to a list. +Pass a string to use a fixed route path. The path is used as written, without the base path. ```typescript {filename:vite.config.ts} sentryTanstackStart({ @@ -123,7 +130,7 @@ sentryTanstackStart({ #### Object form -Use the object form to control the DSN allowlist or set a static path explicitly: +Use the object form to allow more DSNs or set a static path explicitly: ```typescript {filename:vite.config.ts} sentryTanstackStart({ @@ -134,19 +141,14 @@ sentryTanstackStart({ }), ``` -- **`allowedDsns`** — only envelopes targeting one of these DSNs are forwarded. If omitted or empty, the route falls back to the DSN of the active server-side Sentry SDK at runtime. -- **`path`** — the public route path. If omitted, an opaque path is generated (same behavior as `tunnelRoute: true`). +- **`allowedDsns`**: envelopes for these DSNs are forwarded, in addition to the DSN of the active server-side Sentry SDK. +- **`path`**: the public route path. If omitted, the path is derived from the DSN (same behavior as `tunnelRoute: true`). - If you also pass `tunnel` to `Sentry.init()`, the runtime option wins and the - managed tunnel route is bypassed. The SDK logs a warning when this happens. - - - - When using `tunnelRoute: true` (or the object form without `path`), the - generated path changes for each dev session and each production build. Don't - hard-code it elsewhere in your app or infrastructure. + If you also pass a different `tunnel` to `Sentry.init()`, the runtime option + wins and the managed tunnel route is bypassed. The SDK logs a warning when + this happens. diff --git a/docs/platforms/javascript/guides/tanstackstart-react/manual-setup/index.mdx b/docs/platforms/javascript/guides/tanstackstart-react/manual-setup/index.mdx index a9c893db8c2492..b8e980f5bb13b9 100644 --- a/docs/platforms/javascript/guides/tanstackstart-react/manual-setup/index.mdx +++ b/docs/platforms/javascript/guides/tanstackstart-react/manual-setup/index.mdx @@ -370,7 +370,7 @@ Ad blockers and corporate firewalls often block requests to `*.sentry.io`, which The `sentryTanstackStart` Vite plugin can register a tunnel route for you and automatically wire up the client `tunnel` option. -Setting `tunnelRoute: true` generates an opaque route for each dev session and production build, making it harder for ad blockers to target. See the [`sentryTanstackStart`](/platforms/javascript/guides/tanstackstart-react/features/sentryTanstackStart/#tunnel-route) feature page for static paths and the full set of options. +Setting `tunnelRoute: true` serves the tunnel at a path derived from your DSN. See the [`sentryTanstackStart`](/platforms/javascript/guides/tanstackstart-react/features/sentryTanstackStart/#tunnel-route) feature page for static paths and the full set of options. If you can't use the Vite plugin, or need full control over the route file and DSN allowlist, use [`createSentryTunnelRoute`](/platforms/javascript/guides/tanstackstart-react/features/createSentryTunnelRoute/) to define the route yourself. diff --git a/platform-includes/getting-started-complete/javascript.nuxt.mdx b/platform-includes/getting-started-complete/javascript.nuxt.mdx index ca94d38d99c5cd..d026ff24a141b0 100644 --- a/platform-includes/getting-started-complete/javascript.nuxt.mdx +++ b/platform-includes/getting-started-complete/javascript.nuxt.mdx @@ -206,10 +206,7 @@ export default defineNuxtConfig({ ### Avoid Ad Blockers With Tunneling (Optional) - + ### Control the Data You Send to Sentry (Optional) diff --git a/platform-includes/getting-started-tunneling/javascript.astro.mdx b/platform-includes/getting-started-tunneling/javascript.astro.mdx new file mode 100644 index 00000000000000..b99782cc63ad64 --- /dev/null +++ b/platform-includes/getting-started-tunneling/javascript.astro.mdx @@ -0,0 +1,19 @@ +To keep ad blockers from dropping Sentry events, send them through a tunnel on your own origin. The SDK serves the tunnel and forwards the events to Sentry. + +Set `tunnel: true` in your client `Sentry.init`, and add `serveTunnelIntegration` to your server `Sentry.init`: + +```javascript {filename:sentry.client.config.(js|ts)} {3} +Sentry.init({ + dsn: "___PUBLIC_DSN___", + tunnel: true, +}); +``` + +```javascript {filename:sentry.server.config.(js|ts)} {3} +Sentry.init({ + dsn: "___PUBLIC_DSN___", + integrations: [Sentry.serveTunnelIntegration()], +}); +``` + +Learn more about the options in `serveTunnelIntegration`. To set up your own tunnel endpoint instead, see our Troubleshooting page. diff --git a/platform-includes/getting-started-tunneling/javascript.nuxt.mdx b/platform-includes/getting-started-tunneling/javascript.nuxt.mdx new file mode 100644 index 00000000000000..ba7feb068a7f04 --- /dev/null +++ b/platform-includes/getting-started-tunneling/javascript.nuxt.mdx @@ -0,0 +1,19 @@ +To keep ad blockers from dropping Sentry events, send them through a tunnel on your own origin. The SDK serves the tunnel and forwards the events to Sentry. + +Set `tunnel: true` in your client `Sentry.init`, and add `serveTunnelIntegration` to your server `Sentry.init`: + +```javascript {filename:sentry.client.config.ts} {3} +Sentry.init({ + dsn: "___PUBLIC_DSN___", + tunnel: true, +}); +``` + +```javascript {filename:sentry.server.config.ts} {3} +Sentry.init({ + dsn: "___PUBLIC_DSN___", + integrations: [Sentry.serveTunnelIntegration()], +}); +``` + +Learn more about the options in `serveTunnelIntegration`. To set up your own tunnel endpoint instead, see our Troubleshooting page. diff --git a/platform-includes/getting-started-tunneling/javascript.sveltekit.mdx b/platform-includes/getting-started-tunneling/javascript.sveltekit.mdx index 3b1ef2770c630f..db41cd6335b34c 100644 --- a/platform-includes/getting-started-tunneling/javascript.sveltekit.mdx +++ b/platform-includes/getting-started-tunneling/javascript.sveltekit.mdx @@ -1,10 +1,19 @@ -You can prevent ad blockers from blocking Sentry events using tunneling. Use the `tunnel` option inside your `hooks.client.(js|ts)` file's `Sentry.init` to add an API endpoint in your application that forwards Sentry events to Sentry servers. +To keep ad blockers from dropping Sentry events, send them through a tunnel on your own origin. The SDK serves the tunnel and forwards the events to Sentry. -This will send all events to the `tunnel` endpoint. However, the events need to be parsed and redirected to Sentry, so you'll need to do additional configuration on the server. The endpoint must forward only the request body to Sentry and accept only your own DSN. You can find a detailed explanation on how to do this on our [Troubleshooting page](/platforms/javascript/guides/sveltekit/troubleshooting/#using-the-tunnel-option). +Set `tunnel: true` in your client `Sentry.init`, and add `serveTunnelIntegration` to your server `Sentry.init`: -```javascript {filename:hooks.client.(js|ts)} {3} +```javascript {filename:src/hooks.client.(js|ts)} {3} Sentry.init({ dsn: "___PUBLIC_DSN___", - tunnel: "/tunnel", + tunnel: true, }); ``` + +```javascript {filename:src/instrumentation.server.(js|ts)} {3} +Sentry.init({ + dsn: "___PUBLIC_DSN___", + integrations: [Sentry.serveTunnelIntegration()], +}); +``` + +Learn more about the options in `serveTunnelIntegration`. To set up your own tunnel endpoint instead, see our Troubleshooting page. From 56f876d0ed337ba4cf68fca640239ae2a994ad78 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Tue, 27 Oct 2026 16:55:30 -0400 Subject: [PATCH 02/17] docs(js): Document serving the managed tunnel on any server Covers the tunnel helpers every server SDK exports and the new handleTunnelRequest signature from getsentry/sentry-javascript#25783. --- .../integrations/servetunnel.mdx | 51 +++++++++++++------ .../common/troubleshooting/index.mdx | 23 +++++---- 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx b/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx index 0d5d3487cb0b53..06610725fcee9d 100644 --- a/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx +++ b/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx @@ -1,16 +1,8 @@ --- title: ServeTunnel description: "Serves a tunnel on your server so browser events reach Sentry through your own origin, past ad blockers." -supported: - - javascript.sveltekit - - javascript.nuxt - - javascript.nitro - - javascript.astro - - javascript.react-router - - javascript.tanstackstart-react - - javascript.solidstart - - javascript.remix - - javascript.cloudflare +supportedCategories: + - server --- @@ -39,11 +31,7 @@ Sentry.init({ The browser and the server both derive the tunnel path from the DSN, for example `/qpo967`. You don't need a route file, a build step, or an environment variable. If your app is served under a base path, the SDK adds it on both sides. - - -The tunnel is only served by the SDKs that answer requests from a framework hook: SvelteKit, Nuxt, Nitro, Astro, React Router (framework mode), TanStack Start, SolidStart, and Remix 3. On other servers, see [Using the `tunnel` Option](/platforms/javascript/troubleshooting/#using-the-tunnel-option). - - +The SvelteKit, Nuxt, Nitro, Astro, React Router (framework mode), TanStack Start, SolidStart, and Remix 3 SDKs serve the tunnel from their request hooks. On other servers, [serve it yourself](#serving-the-tunnel-yourself). @@ -104,3 +92,36 @@ Sentry.init({ _Type: `string`_ The path prefix your app is served under, for example `/docs`. The SDK reads it from your framework config, so you only need this option when it can't. Set the same value in the browser `tunnel` option. + +## Serving the Tunnel Yourself + +On servers without a framework SDK hook, like Express, Fastify, `node:http`, Bun, or Deno, mount `handleTunnelRequestIfMatched` before your routes and body parsers. It forwards tunnel requests and returns `undefined` for everything else: + +```javascript {tabTitle:Express} +const express = require("express"); +const Sentry = require("@sentry/node"); + +const app = express(); + +app.use( + (req, res, next) => Sentry.handleTunnelRequestIfMatched(req, res) ?? next() +); +``` + +```javascript {tabTitle:Fetch API} +export default { + fetch(request) { + return Sentry.handleTunnelRequestIfMatched(request) ?? handleApp(request); + }, +}; +``` + +To register the tunnel as a route instead, use `getTunnelPath()`, which returns the path the browser derives from the server's DSN, and `handleTunnelRequest`: + +```javascript +app.post(Sentry.getTunnelPath(), (req, res) => + Sentry.handleTunnelRequest(req, res) +); +``` + +Both helpers take a fetch `Request`, or Node's `req` and `res`. Without options, they use the options of `serveTunnelIntegration`, or the server's own DSN if the integration isn't set up. `handleTunnelRequestIfMatched` also takes the same options as the integration, and `handleTunnelRequest` takes `allowedDsns`. To check a request without reading its body, use `isTunnelRequest(request)`. diff --git a/docs/platforms/javascript/common/troubleshooting/index.mdx b/docs/platforms/javascript/common/troubleshooting/index.mdx index cc6f97a41d0d98..810e1eb93ddd5a 100644 --- a/docs/platforms/javascript/common/troubleshooting/index.mdx +++ b/docs/platforms/javascript/common/troubleshooting/index.mdx @@ -223,7 +223,7 @@ You can work around our CDN bundles being blocked by [using our NPM packages](#u - + @@ -244,13 +244,15 @@ Sentry.init({ }); ``` -The browser then sends its events to a path on your own origin derived from the DSN, and your server forwards them to Sentry. The SDK reads your app's base path from your framework config. +The browser then sends its events to a path on your own origin derived from the DSN, and your server forwards them to Sentry. - +The SvelteKit, Nuxt, Astro, React Router (framework mode), TanStack Start, SolidStart, and Remix 3 SDKs serve the tunnel for you and read your app's base path from your framework config. On other servers, like Express, mount `handleTunnelRequestIfMatched` before your routes and body parsers: -The managed tunnel is only available for Remix 3. - - +```javascript {filename:Server} +app.use( + (req, res, next) => Sentry.handleTunnelRequestIfMatched(req, res) ?? next() +); +``` See `serveTunnelIntegration` for a fixed path, more allowed DSNs, and how the tunnel handles requests. If you can't use the managed tunnel, set up your own tunnel as explained below. @@ -292,19 +294,20 @@ Your tunnel endpoint must: - Accept only envelopes whose DSN is on a list of allowed DSNs. Otherwise, anyone can use your endpoint to send requests to other servers. - Send the envelope only to the host of that DSN. -On a JavaScript server with Fetch API `Request` and `Response` objects, `handleTunnelRequest` from `@sentry/core` (SDK version `10.40.0` and later) does this for you. Starting with version `11.10.0`, the DSN of the active server SDK is always allowed, so `allowedDsns` is optional. It forwards the user agent, client hint, `Origin`, and `Referer` headers, and the visitor's IP address, but never cookies or auth headers. +On a JavaScript server with Fetch API `Request` and `Response` objects, `handleTunnelRequest` from `@sentry/core` (SDK version `10.40.0` and later) does this for you. Starting with version `11.10.0`, every server SDK exports it, and the DSN of the active server SDK is always allowed, so `allowedDsns` is optional. It forwards the user agent, client hint, `Origin`, and `Referer` headers, and the visitor's IP address, but never cookies or auth headers. ```javascript -import { handleTunnelRequest } from "@sentry/core"; +import * as Sentry from "@sentry/node"; export async function POST(request) { - return handleTunnelRequest({ - request, + return Sentry.handleTunnelRequest(request, { allowedDsns: ["___PUBLIC_DSN___"], }); } ``` +It also takes Node's `req` and `res`, for example in Express: `app.post("/tunnel", (req, res) => Sentry.handleTunnelRequest(req, res))`. Versions before `11.10.0` take the request as an option instead, `handleTunnelRequest({ request, allowedDsns })`. That form still works, but is deprecated. + If you implement the endpoint yourself, here's an example for your server component: ```csharp From 552e8be1cad7a9acb6995a50f7142ccf59d4877d Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Wed, 28 Oct 2026 12:34:36 -0400 Subject: [PATCH 03/17] docs(js): Align the managed tunnel docs with the 11.10.0 release Hides SolidStart until it serves the managed tunnel, fixes the Cloudflare example, and notes that the manual helpers need serveTunnelIntegration to drop tunnel spans. --- .../configuration/integrations/servetunnel.mdx | 17 ++++++++++++----- .../javascript/common/configuration/options.mdx | 5 +++++ .../javascript/common/troubleshooting/index.mdx | 4 ++-- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx b/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx index 06610725fcee9d..676130496bb7cd 100644 --- a/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx +++ b/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx @@ -3,6 +3,8 @@ title: ServeTunnel description: "Serves a tunnel on your server so browser events reach Sentry through your own origin, past ad blockers." supportedCategories: - server +notSupported: + - javascript.solidstart --- @@ -31,19 +33,22 @@ Sentry.init({ The browser and the server both derive the tunnel path from the DSN, for example `/qpo967`. You don't need a route file, a build step, or an environment variable. If your app is served under a base path, the SDK adds it on both sides. -The SvelteKit, Nuxt, Nitro, Astro, React Router (framework mode), TanStack Start, SolidStart, and Remix 3 SDKs serve the tunnel from their request hooks. On other servers, [serve it yourself](#serving-the-tunnel-yourself). +The SvelteKit, Nuxt, Nitro, Astro, React Router (framework mode), TanStack Start, and Remix 3 SDKs serve the tunnel from their request hooks. On other servers, [serve it yourself](#serving-the-tunnel-yourself). On Cloudflare Workers, import the integration from `@sentry/cloudflare` and add it to the options you pass to `withSentry`, `defineCloudflareOptions`, or `initCloudflareSentryHandle`: ```javascript -import { serveTunnelIntegration } from "@sentry/cloudflare"; +import { + defineCloudflareOptions, + serveTunnelIntegration, +} from "@sentry/cloudflare"; -export default { +export default defineCloudflareOptions((env) => ({ dsn: "___PUBLIC_DSN___", integrations: [serveTunnelIntegration()], -}; +})); ``` @@ -124,4 +129,6 @@ app.post(Sentry.getTunnelPath(), (req, res) => ); ``` -Both helpers take a fetch `Request`, or Node's `req` and `res`. Without options, they use the options of `serveTunnelIntegration`, or the server's own DSN if the integration isn't set up. `handleTunnelRequestIfMatched` also takes the same options as the integration, and `handleTunnelRequest` takes `allowedDsns`. To check a request without reading its body, use `isTunnelRequest(request)`. +Both helpers take a fetch `Request`, or Node's `req` and `res` (in Fastify, `request.raw` and `reply.raw`). Without options, they use the options of `serveTunnelIntegration`, or the server's own DSN if the integration isn't set up. `handleTunnelRequestIfMatched` also takes the same options as the integration, and `handleTunnelRequest` takes `allowedDsns`. To check a request without reading its body, use `isTunnelRequest(request)`. + +The helpers work without `serveTunnelIntegration`. Add it anyway, so tunnel requests don't create server spans. diff --git a/docs/platforms/javascript/common/configuration/options.mdx b/docs/platforms/javascript/common/configuration/options.mdx index 052f952fba7b9f..8ff9ca12105826 100644 --- a/docs/platforms/javascript/common/configuration/options.mdx +++ b/docs/platforms/javascript/common/configuration/options.mdx @@ -102,6 +102,9 @@ The endpoint must forward only the request body to Sentry and accept only your o Where the browser SDK sends captured events instead of the Sentry ingest URL. Use it to work around ad blockers or to control which events reach Sentry. The DSN is still required, so the SDK can set the attributes it needs on your data. - A string (URL or relative path) sends events to it as written. This **requires the implementation** of a custom server endpoint. The endpoint must forward only the request body to Sentry and accept only your own DSNs. Learn more in [Using the `tunnel` Option](/platforms/javascript/troubleshooting/#using-the-tunnel-option). + + + - `true` sends events to the managed tunnel, a path on your own origin derived from the DSN. Your server serves it with `serveTunnelIntegration`. Available since version `11.10.0`. - An object works like `true`, with these options: - `path`: a fixed tunnel path, used as written without the base path. @@ -110,6 +113,8 @@ Where the browser SDK sends captured events instead of the Sentry ingest URL. Us On the server, `tunnel` only takes a URL. It's where the server SDK sends its own events, and it's independent from `serveTunnelIntegration`. + + diff --git a/docs/platforms/javascript/common/troubleshooting/index.mdx b/docs/platforms/javascript/common/troubleshooting/index.mdx index 810e1eb93ddd5a..fb957e480e9e84 100644 --- a/docs/platforms/javascript/common/troubleshooting/index.mdx +++ b/docs/platforms/javascript/common/troubleshooting/index.mdx @@ -223,7 +223,7 @@ You can work around our CDN bundles being blocked by [using our NPM packages](#u - + @@ -246,7 +246,7 @@ Sentry.init({ The browser then sends its events to a path on your own origin derived from the DSN, and your server forwards them to Sentry. -The SvelteKit, Nuxt, Astro, React Router (framework mode), TanStack Start, SolidStart, and Remix 3 SDKs serve the tunnel for you and read your app's base path from your framework config. On other servers, like Express, mount `handleTunnelRequestIfMatched` before your routes and body parsers: +The SvelteKit, Nuxt, Astro, React Router (framework mode), TanStack Start, and Remix 3 SDKs serve the tunnel for you and read your app's base path from your framework config. On other servers, like Express, mount `handleTunnelRequestIfMatched` before your routes and body parsers: ```javascript {filename:Server} app.use( From 2807cbe7504927c94cc61c352b901f8d0753b8b2 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Wed, 28 Oct 2026 12:36:20 -0400 Subject: [PATCH 04/17] docs(js): Hide the managed tunnel docs from Next.js for now --- .../common/configuration/integrations/servetunnel.mdx | 1 + docs/platforms/javascript/common/configuration/options.mdx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx b/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx index 676130496bb7cd..4ecdb32c7aa674 100644 --- a/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx +++ b/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx @@ -4,6 +4,7 @@ description: "Serves a tunnel on your server so browser events reach Sentry thro supportedCategories: - server notSupported: + - javascript.nextjs - javascript.solidstart --- diff --git a/docs/platforms/javascript/common/configuration/options.mdx b/docs/platforms/javascript/common/configuration/options.mdx index 8ff9ca12105826..158e929510cf82 100644 --- a/docs/platforms/javascript/common/configuration/options.mdx +++ b/docs/platforms/javascript/common/configuration/options.mdx @@ -103,7 +103,7 @@ Where the browser SDK sends captured events instead of the Sentry ingest URL. Us - A string (URL or relative path) sends events to it as written. This **requires the implementation** of a custom server endpoint. The endpoint must forward only the request body to Sentry and accept only your own DSNs. Learn more in [Using the `tunnel` Option](/platforms/javascript/troubleshooting/#using-the-tunnel-option). - + - `true` sends events to the managed tunnel, a path on your own origin derived from the DSN. Your server serves it with `serveTunnelIntegration`. Available since version `11.10.0`. - An object works like `true`, with these options: From f5d4bf81eb58d35740769f0bf0acc596d9023727 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Wed, 28 Oct 2026 12:38:17 -0400 Subject: [PATCH 05/17] docs(js): Align the tunnel endpoint rules with handleTunnelRequest --- docs/platforms/javascript/common/troubleshooting/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/platforms/javascript/common/troubleshooting/index.mdx b/docs/platforms/javascript/common/troubleshooting/index.mdx index fb957e480e9e84..67f60a082889d5 100644 --- a/docs/platforms/javascript/common/troubleshooting/index.mdx +++ b/docs/platforms/javascript/common/troubleshooting/index.mdx @@ -290,7 +290,7 @@ Once configured, all events will be sent to the `/tunnel` endpoint. This solutio Your tunnel endpoint must: -- Forward only the request body to Sentry, without the request headers. +- Forward the request body to Sentry, without cookies or auth headers. - Accept only envelopes whose DSN is on a list of allowed DSNs. Otherwise, anyone can use your endpoint to send requests to other servers. - Send the envelope only to the host of that DSN. From cd71dcf428893cf327b9ef5746883dc38fd8399f Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Wed, 28 Oct 2026 12:40:23 -0400 Subject: [PATCH 06/17] docs(js): Keep the handleTunnelRequest paragraph, with the non-deprecated signature --- .../platforms/javascript/common/troubleshooting/index.mdx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/platforms/javascript/common/troubleshooting/index.mdx b/docs/platforms/javascript/common/troubleshooting/index.mdx index 67f60a082889d5..83854eeebd47b3 100644 --- a/docs/platforms/javascript/common/troubleshooting/index.mdx +++ b/docs/platforms/javascript/common/troubleshooting/index.mdx @@ -294,20 +294,18 @@ Your tunnel endpoint must: - Accept only envelopes whose DSN is on a list of allowed DSNs. Otherwise, anyone can use your endpoint to send requests to other servers. - Send the envelope only to the host of that DSN. -On a JavaScript server with Fetch API `Request` and `Response` objects, `handleTunnelRequest` from `@sentry/core` (SDK version `10.40.0` and later) does this for you. Starting with version `11.10.0`, every server SDK exports it, and the DSN of the active server SDK is always allowed, so `allowedDsns` is optional. It forwards the user agent, client hint, `Origin`, and `Referer` headers, and the visitor's IP address, but never cookies or auth headers. +On a JavaScript server with Fetch API `Request` and `Response` objects, `handleTunnelRequest` from `@sentry/core` (SDK version `10.40.0` and later) does this for you. Starting with versions `10.77.1` (v10) and `11.9.0` (v11), it also rejects requests larger than 20 MiB. ```javascript -import * as Sentry from "@sentry/node"; +import { handleTunnelRequest } from "@sentry/core"; export async function POST(request) { - return Sentry.handleTunnelRequest(request, { + return handleTunnelRequest(request, { allowedDsns: ["___PUBLIC_DSN___"], }); } ``` -It also takes Node's `req` and `res`, for example in Express: `app.post("/tunnel", (req, res) => Sentry.handleTunnelRequest(req, res))`. Versions before `11.10.0` take the request as an option instead, `handleTunnelRequest({ request, allowedDsns })`. That form still works, but is deprecated. - If you implement the endpoint yourself, here's an example for your server component: ```csharp From 26dd85408c2d7e5e2dd070d276403d7a33106755 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Wed, 28 Oct 2026 14:10:21 -0400 Subject: [PATCH 07/17] docs(js): Link browser-only platforms to the Node.js serveTunnel page The serveTunnel page only exists on server platforms, so the links 404'd on browser-only ones. --- .../javascript/common/configuration/options.mdx | 11 +++++++++++ .../javascript/common/troubleshooting/index.mdx | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/docs/platforms/javascript/common/configuration/options.mdx b/docs/platforms/javascript/common/configuration/options.mdx index 158e929510cf82..7a136ba7e5ca3c 100644 --- a/docs/platforms/javascript/common/configuration/options.mdx +++ b/docs/platforms/javascript/common/configuration/options.mdx @@ -105,7 +105,18 @@ Where the browser SDK sends captured events instead of the Sentry ingest URL. Us + + - `true` sends events to the managed tunnel, a path on your own origin derived from the DSN. Your server serves it with `serveTunnelIntegration`. Available since version `11.10.0`. + + + + + +- `true` sends events to the managed tunnel, a path on your own origin derived from the DSN. Your server serves it with [`serveTunnelIntegration`](/platforms/javascript/guides/node/configuration/integrations/servetunnel/). Available since version `11.10.0`. + + + - An object works like `true`, with these options: - `path`: a fixed tunnel path, used as written without the base path. - `basePath`: the path prefix your app is served under, for example `/docs`. Most framework SDKs set it for you. diff --git a/docs/platforms/javascript/common/troubleshooting/index.mdx b/docs/platforms/javascript/common/troubleshooting/index.mdx index 83854eeebd47b3..1fd8bf634193a4 100644 --- a/docs/platforms/javascript/common/troubleshooting/index.mdx +++ b/docs/platforms/javascript/common/troubleshooting/index.mdx @@ -254,8 +254,18 @@ app.use( ); ``` + + See `serveTunnelIntegration` for a fixed path, more allowed DSNs, and how the tunnel handles requests. If you can't use the managed tunnel, set up your own tunnel as explained below. + + + + +See [`serveTunnelIntegration`](/platforms/javascript/guides/node/configuration/integrations/servetunnel/) for a fixed path, more allowed DSNs, and how the tunnel handles requests. If you can't use the managed tunnel, set up your own tunnel as explained below. + + + From 809d61fc9b94e91f4b2771b540665e8f47d57117 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Wed, 28 Oct 2026 14:23:26 -0400 Subject: [PATCH 08/17] docs(js): Document only the middleware for serving the tunnel yourself --- .../configuration/integrations/servetunnel.mdx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx b/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx index 4ecdb32c7aa674..dd7e3823b62b6a 100644 --- a/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx +++ b/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx @@ -122,14 +122,6 @@ export default { }; ``` -To register the tunnel as a route instead, use `getTunnelPath()`, which returns the path the browser derives from the server's DSN, and `handleTunnelRequest`: +`handleTunnelRequestIfMatched` takes a fetch `Request`, or Node's `req` and `res` (in Fastify, `request.raw` and `reply.raw`). Without options, it uses the options of `serveTunnelIntegration`, or the server's own DSN if the integration isn't set up. It also takes the same options as the integration. To check a request without reading its body, use `isTunnelRequest(request)`. -```javascript -app.post(Sentry.getTunnelPath(), (req, res) => - Sentry.handleTunnelRequest(req, res) -); -``` - -Both helpers take a fetch `Request`, or Node's `req` and `res` (in Fastify, `request.raw` and `reply.raw`). Without options, they use the options of `serveTunnelIntegration`, or the server's own DSN if the integration isn't set up. `handleTunnelRequestIfMatched` also takes the same options as the integration, and `handleTunnelRequest` takes `allowedDsns`. To check a request without reading its body, use `isTunnelRequest(request)`. - -The helpers work without `serveTunnelIntegration`. Add it anyway, so tunnel requests don't create server spans. +The middleware works without `serveTunnelIntegration`. Add it anyway, so tunnel requests don't create server spans. From ebe5801ac7f1d944a950cce868609c4d8f08541d Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Wed, 28 Oct 2026 14:24:50 -0400 Subject: [PATCH 09/17] docs(js): Document handleTunnelRequest for a fixed tunnel path --- .../configuration/integrations/servetunnel.mdx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx b/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx index dd7e3823b62b6a..9f40eac21fafa6 100644 --- a/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx +++ b/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx @@ -124,4 +124,19 @@ export default { `handleTunnelRequestIfMatched` takes a fetch `Request`, or Node's `req` and `res` (in Fastify, `request.raw` and `reply.raw`). Without options, it uses the options of `serveTunnelIntegration`, or the server's own DSN if the integration isn't set up. It also takes the same options as the integration. To check a request without reading its body, use `isTunnelRequest(request)`. -The middleware works without `serveTunnelIntegration`. Add it anyway, so tunnel requests don't create server spans. +If you'd rather use a fixed path, register it as a route with `handleTunnelRequest`, and set the same path in the browser: + +```javascript {filename:Server} +app.post("/monitoring", (req, res) => Sentry.handleTunnelRequest(req, res)); +``` + +```javascript {filename:Browser} +Sentry.init({ + dsn: "___PUBLIC_DSN___", + tunnel: { path: "/monitoring" }, +}); +``` + +`handleTunnelRequest` also takes a fetch `Request`, and an `allowedDsns` option for DSNs besides the server's own. + +Both work without `serveTunnelIntegration`. Add it anyway, so tunnel requests don't create server spans. With a fixed path, pass it as the integration's `path` too: `serveTunnelIntegration({ path: "/monitoring" })`. From c246c5ec354809f8796f78c334033405094663b0 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Wed, 28 Oct 2026 14:59:19 -0400 Subject: [PATCH 10/17] docs(js): Apply the docs style guide to the managed tunnel docs --- .../integrations/servetunnel.mdx | 40 ++++++++++--------- .../common/troubleshooting/index.mdx | 2 +- .../features/sentryTanstackStart.mdx | 2 +- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx b/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx index 9f40eac21fafa6..21f69186a0c279 100644 --- a/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx +++ b/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx @@ -14,18 +14,16 @@ _Import name: `Sentry.serveTunnelIntegration`_ Ad blockers often drop requests to `*.ingest.sentry.io`. This integration serves a tunnel endpoint from your app's server, so the browser SDK can send its events to your own origin instead. The server forwards them to Sentry. -Set it up on both sides. Add the integration to your server `Sentry.init`: +Set it up on both sides. Add the integration to your server `Sentry.init`, and set `tunnel: true` in your browser `Sentry.init`: -```javascript {filename:Server} +```javascript {tabTitle:Server} Sentry.init({ dsn: "___PUBLIC_DSN___", integrations: [Sentry.serveTunnelIntegration()], }); ``` -And set `tunnel: true` in your browser `Sentry.init`: - -```javascript {filename:Browser} +```javascript {tabTitle:Browser} Sentry.init({ dsn: "___PUBLIC_DSN___", tunnel: true, @@ -64,6 +62,8 @@ export default defineCloudflareOptions((env) => ({ ## Options +Pass these options to `serveTunnelIntegration`. Most apps don't need any of them. + ### `allowedDsns` _Type: `string[]`_ @@ -80,13 +80,15 @@ Sentry.serveTunnelIntegration({ _Type: `string`_ -A fixed tunnel path instead of the one derived from the DSN. The path is served as written, without the base path. Set the same path in the browser with the object form. A string `tunnel` is sent as `text/plain`, which the CSRF protection in SvelteKit and Astro rejects. +A fixed tunnel path instead of the one derived from the DSN. The SDK serves it as written, without the base path. + +Set the same path in the browser with the object form. Don't pass it as a string: the browser SDK then sends `text/plain`, which the CSRF protection in SvelteKit and Astro rejects. -```javascript {filename:Server} +```javascript {tabTitle:Server} Sentry.serveTunnelIntegration({ path: "/monitoring" }); ``` -```javascript {filename:Browser} +```javascript {tabTitle:Browser} Sentry.init({ dsn: "___PUBLIC_DSN___", tunnel: { path: "/monitoring" }, @@ -117,26 +119,28 @@ app.use( ```javascript {tabTitle:Fetch API} export default { fetch(request) { - return Sentry.handleTunnelRequestIfMatched(request) ?? handleApp(request); + return ( + Sentry.handleTunnelRequestIfMatched(request) ?? new Response("Hello") + ); }, }; ``` -`handleTunnelRequestIfMatched` takes a fetch `Request`, or Node's `req` and `res` (in Fastify, `request.raw` and `reply.raw`). Without options, it uses the options of `serveTunnelIntegration`, or the server's own DSN if the integration isn't set up. It also takes the same options as the integration. To check a request without reading its body, use `isTunnelRequest(request)`. +`handleTunnelRequestIfMatched` takes a fetch `Request`, or Node's `req` and `res` (in Fastify, `request.raw` and `reply.raw`). It takes the same options as the integration. -If you'd rather use a fixed path, register it as a route with `handleTunnelRequest`, and set the same path in the browser: +Without options, it uses the options of `serveTunnelIntegration`, or the server's own DSN if the integration isn't set up. To check a request without reading its body, use `isTunnelRequest(request)`. -```javascript {filename:Server} -app.post("/monitoring", (req, res) => Sentry.handleTunnelRequest(req, res)); -``` +If you'd rather use a fixed path, register it as a route with `handleTunnelRequest`. Pass the same path to the integration, so tunnel requests don't create server spans: -```javascript {filename:Browser} +```javascript Sentry.init({ dsn: "___PUBLIC_DSN___", - tunnel: { path: "/monitoring" }, + integrations: [Sentry.serveTunnelIntegration({ path: "/monitoring" })], }); + +app.post("/monitoring", (req, res) => Sentry.handleTunnelRequest(req, res)); ``` -`handleTunnelRequest` also takes a fetch `Request`, and an `allowedDsns` option for DSNs besides the server's own. +Set the same path in the browser, as shown in [`path`](#path). `handleTunnelRequest` also takes a fetch `Request`, and an `allowedDsns` option for DSNs besides the server's own. -Both work without `serveTunnelIntegration`. Add it anyway, so tunnel requests don't create server spans. With a fixed path, pass it as the integration's `path` too: `serveTunnelIntegration({ path: "/monitoring" })`. +Both helpers work without `serveTunnelIntegration`. Add it anyway, so tunnel requests don't create server spans. diff --git a/docs/platforms/javascript/common/troubleshooting/index.mdx b/docs/platforms/javascript/common/troubleshooting/index.mdx index 1fd8bf634193a4..7bfaf5ae303d6e 100644 --- a/docs/platforms/javascript/common/troubleshooting/index.mdx +++ b/docs/platforms/javascript/common/troubleshooting/index.mdx @@ -246,7 +246,7 @@ Sentry.init({ The browser then sends its events to a path on your own origin derived from the DSN, and your server forwards them to Sentry. -The SvelteKit, Nuxt, Astro, React Router (framework mode), TanStack Start, and Remix 3 SDKs serve the tunnel for you and read your app's base path from your framework config. On other servers, like Express, mount `handleTunnelRequestIfMatched` before your routes and body parsers: +The SvelteKit, Nuxt, Nitro, Astro, React Router (framework mode), TanStack Start, and Remix 3 SDKs serve the tunnel for you and read your app's base path from your framework config. On other servers, like Express, mount `handleTunnelRequestIfMatched` before your routes and body parsers: ```javascript {filename:Server} app.use( diff --git a/docs/platforms/javascript/guides/tanstackstart-react/features/sentryTanstackStart.mdx b/docs/platforms/javascript/guides/tanstackstart-react/features/sentryTanstackStart.mdx index 60c363debc735f..717b5fbf6d0406 100644 --- a/docs/platforms/javascript/guides/tanstackstart-react/features/sentryTanstackStart.mdx +++ b/docs/platforms/javascript/guides/tanstackstart-react/features/sentryTanstackStart.mdx @@ -120,7 +120,7 @@ sentryTanstackStart({ #### Static path -Pass a string to use a fixed route path. The path is used as written, without the base path. +Pass a string to use a fixed route path. The SDK serves it as written, without the base path. ```typescript {filename:vite.config.ts} sentryTanstackStart({ From 9efd075f5a180f3a891069775c11b0bb6e8ef839 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Thu, 29 Oct 2026 09:59:30 -0400 Subject: [PATCH 11/17] docs(js): Clarify that serveTunnelIntegration needs a request hook to serve the tunnel On servers without a framework SDK hook, including Cloudflare Workers, the integration only configures the tunnel. --- .../integrations/servetunnel.mdx | 33 +++++++++++-------- .../common/configuration/options.mdx | 4 +-- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx b/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx index 21f69186a0c279..02e875d4ecbcf2 100644 --- a/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx +++ b/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx @@ -12,7 +12,7 @@ notSupported: _Import name: `Sentry.serveTunnelIntegration`_ -Ad blockers often drop requests to `*.ingest.sentry.io`. This integration serves a tunnel endpoint from your app's server, so the browser SDK can send its events to your own origin instead. The server forwards them to Sentry. +Ad blockers often drop requests to `*.ingest.sentry.io`. With a tunnel on your own origin, the browser SDK sends its events to your server instead, and your server forwards them to Sentry. This integration configures that tunnel on the server. Set it up on both sides. Add the integration to your server `Sentry.init`, and set `tunnel: true` in your browser `Sentry.init`: @@ -32,29 +32,36 @@ Sentry.init({ The browser and the server both derive the tunnel path from the DSN, for example `/qpo967`. You don't need a route file, a build step, or an environment variable. If your app is served under a base path, the SDK adds it on both sides. -The SvelteKit, Nuxt, Nitro, Astro, React Router (framework mode), TanStack Start, and Remix 3 SDKs serve the tunnel from their request hooks. On other servers, [serve it yourself](#serving-the-tunnel-yourself). +The integration doesn't handle requests by itself. The SvelteKit, Nuxt, Nitro, Astro, React Router (framework mode), TanStack Start, and Remix 3 SDKs serve the tunnel from their request hooks. On other servers, like Express, Fastify, Bun, Deno, or Cloudflare Workers, [serve it yourself](#serving-the-tunnel-yourself). -On Cloudflare Workers, import the integration from `@sentry/cloudflare` and add it to the options you pass to `withSentry`, `defineCloudflareOptions`, or `initCloudflareSentryHandle`: +On Cloudflare Workers, add the integration to your Sentry options, and call `handleTunnelRequestIfMatched` at the start of your `fetch` handler: ```javascript -import { - defineCloudflareOptions, - serveTunnelIntegration, -} from "@sentry/cloudflare"; - -export default defineCloudflareOptions((env) => ({ - dsn: "___PUBLIC_DSN___", - integrations: [serveTunnelIntegration()], -})); +import * as Sentry from "@sentry/cloudflare"; + +export default Sentry.withSentry( + (env) => ({ + dsn: "___PUBLIC_DSN___", + integrations: [Sentry.serveTunnelIntegration()], + }), + { + async fetch(request, env, ctx) { + return ( + (await Sentry.handleTunnelRequestIfMatched(request)) ?? + new Response("Hello") + ); + }, + } +); ``` ## How It Works -- The tunnel answers before your app's own middleware, so auth or CSRF middleware doesn't block it. +- Framework SDKs answer tunnel requests before your app's own middleware, so auth or CSRF middleware doesn't block them. When you serve the tunnel yourself, mount it before that middleware too. - It only forwards envelopes whose DSN is allowed. By default, that's the server's own DSN. Other envelopes get a `403`, so the tunnel can't be used as an open proxy. - It forwards only the user agent, client hint, `Origin`, and `Referer` headers. Cookies and auth headers never reach Sentry. The visitor's IP address is forwarded from the `cf-connecting-ip`, `x-vercel-forwarded-for`, or `x-forwarded-for` header. - Tunnel requests don't create server spans. diff --git a/docs/platforms/javascript/common/configuration/options.mdx b/docs/platforms/javascript/common/configuration/options.mdx index 7a136ba7e5ca3c..bc17cb84e9b312 100644 --- a/docs/platforms/javascript/common/configuration/options.mdx +++ b/docs/platforms/javascript/common/configuration/options.mdx @@ -107,13 +107,13 @@ Where the browser SDK sends captured events instead of the Sentry ingest URL. Us -- `true` sends events to the managed tunnel, a path on your own origin derived from the DSN. Your server serves it with `serveTunnelIntegration`. Available since version `11.10.0`. +- `true` sends events to the managed tunnel, a path on your own origin derived from the DSN. Your server needs to serve it, as described in `serveTunnelIntegration`. Available since version `11.10.0`. -- `true` sends events to the managed tunnel, a path on your own origin derived from the DSN. Your server serves it with [`serveTunnelIntegration`](/platforms/javascript/guides/node/configuration/integrations/servetunnel/). Available since version `11.10.0`. +- `true` sends events to the managed tunnel, a path on your own origin derived from the DSN. Your server needs to serve it, as described in [`serveTunnelIntegration`](/platforms/javascript/guides/node/configuration/integrations/servetunnel/). Available since version `11.10.0`. From 17286bd50dad92e61b5cba119d74faad0f8c6f95 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Thu, 29 Oct 2026 10:03:45 -0400 Subject: [PATCH 12/17] docs(js): Show per-platform tunnel serving notes instead of listing every framework --- .../integrations/servetunnel.mdx | 26 +++++++++++++++++-- .../common/troubleshooting/index.mdx | 26 +++++++++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx b/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx index 02e875d4ecbcf2..fcb2b2f80e44a6 100644 --- a/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx +++ b/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx @@ -32,7 +32,29 @@ Sentry.init({ The browser and the server both derive the tunnel path from the DSN, for example `/qpo967`. You don't need a route file, a build step, or an environment variable. If your app is served under a base path, the SDK adds it on both sides. -The integration doesn't handle requests by itself. The SvelteKit, Nuxt, Nitro, Astro, React Router (framework mode), TanStack Start, and Remix 3 SDKs serve the tunnel from their request hooks. On other servers, like Express, Fastify, Bun, Deno, or Cloudflare Workers, [serve it yourself](#serving-the-tunnel-yourself). + + +The SDK serves the tunnel from its request hooks, so this is all the setup you need. + + + + + +In framework mode, the SDK serves the tunnel from its request hooks, so this is all the setup you need. + + + + + +On Remix 3, the SDK serves the tunnel from its request hooks, so this is all the setup you need. On earlier versions, [serve it yourself](#serving-the-tunnel-yourself). + + + + + +The integration doesn't handle requests by itself, so you need to [serve the tunnel yourself](#serving-the-tunnel-yourself). + + @@ -110,7 +132,7 @@ The path prefix your app is served under, for example `/docs`. The SDK reads it ## Serving the Tunnel Yourself -On servers without a framework SDK hook, like Express, Fastify, `node:http`, Bun, or Deno, mount `handleTunnelRequestIfMatched` before your routes and body parsers. It forwards tunnel requests and returns `undefined` for everything else: +On servers without a framework SDK hook, mount `handleTunnelRequestIfMatched` before your routes and body parsers. It forwards tunnel requests and returns `undefined` for everything else: ```javascript {tabTitle:Express} const express = require("express"); diff --git a/docs/platforms/javascript/common/troubleshooting/index.mdx b/docs/platforms/javascript/common/troubleshooting/index.mdx index 7bfaf5ae303d6e..90a6568346df4a 100644 --- a/docs/platforms/javascript/common/troubleshooting/index.mdx +++ b/docs/platforms/javascript/common/troubleshooting/index.mdx @@ -228,7 +228,7 @@ You can work around our CDN bundles being blocked by [using our NPM packages](#u -The SDK can serve the tunnel for you. Set `tunnel: true` in your browser `Sentry.init`, and add `serveTunnelIntegration` to your server `Sentry.init`: +Set `tunnel: true` in your browser `Sentry.init`, and add `serveTunnelIntegration` to your server `Sentry.init`: ```javascript {tabTitle:Browser} Sentry.init({ @@ -246,7 +246,27 @@ Sentry.init({ The browser then sends its events to a path on your own origin derived from the DSN, and your server forwards them to Sentry. -The SvelteKit, Nuxt, Nitro, Astro, React Router (framework mode), TanStack Start, and Remix 3 SDKs serve the tunnel for you and read your app's base path from your framework config. On other servers, like Express, mount `handleTunnelRequestIfMatched` before your routes and body parsers: + + +The SDK serves the tunnel for you and reads your app's base path from your framework config. + + + + + +In framework mode, the SDK serves the tunnel for you and reads your app's base path from your framework config. In other modes, see Serving the Tunnel Yourself. + + + + + +On Remix 3, the SDK serves the tunnel for you and reads your app's base path from your framework config. On earlier versions, see Serving the Tunnel Yourself. + + + + + +The integration doesn't handle requests by itself. Mount `handleTunnelRequestIfMatched` on your server, before your routes and body parsers: ```javascript {filename:Server} app.use( @@ -254,6 +274,8 @@ app.use( ); ``` + + See `serveTunnelIntegration` for a fixed path, more allowed DSNs, and how the tunnel handles requests. If you can't use the managed tunnel, set up your own tunnel as explained below. From e33f929ee51140e7bc57eebfd865322c837ad60a Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Fri, 30 Oct 2026 10:54:54 -0400 Subject: [PATCH 13/17] docs(js): Move tunneling docs to their own configuration page Add a Tunneling page under Extended Configuration and move the tunnel content there from Troubleshooting, which now only links to it. Point existing tunneling links at the new page. Also address review feedback on the managed tunnel docs: list serveTunnelIntegration in the server integration tables, add a Fastify example, and tighten the tunnel option wording. --- .../android/troubleshooting/index.mdx | 2 +- .../dotnet/guides/aspnetcore/index.mdx | 2 +- .../img/tunnel.png | Bin .../integrations/servetunnel.mdx | 34 +- .../common/configuration/options.mdx | 52 +++- .../common/configuration/tunneling.mdx | 293 ++++++++++++++++++ .../common/troubleshooting/index.mdx | 267 +--------------- .../javascript/guides/astro/index.mdx | 2 +- .../guides/cordova/configuration/options.mdx | 4 +- .../javascript/guides/ember/index.mdx | 2 +- .../javascript/guides/gatsby/index.mdx | 2 +- .../nextjs/configuration/build/index.mdx | 2 +- .../javascript/guides/react-router/index.mdx | 2 +- .../guides/react-router/manual-setup.mdx | 2 +- .../javascript/guides/react/index.mdx | 2 +- .../javascript/guides/solid/index.mdx | 2 +- .../javascript/guides/solidstart/index.mdx | 2 +- .../javascript/guides/svelte/index.mdx | 2 +- .../platforms/javascript/guides/vue/index.mdx | 2 +- docs/product/explore/dropped-data.mdx | 2 +- .../integrations/javascript.astro.mdx | 61 ++-- .../integrations/javascript.aws-lambda.mdx | 5 +- .../integrations/javascript.bun.mdx | 13 +- .../integrations/javascript.cloudflare.mdx | 5 +- .../integrations/javascript.deno.mdx | 45 +-- .../integrations/javascript.fastify.mdx | 5 +- .../integrations/javascript.gcp-functions.mdx | 5 +- .../integrations/javascript.hapi.mdx | 5 +- .../integrations/javascript.nestjs.mdx | 5 +- .../integrations/javascript.node.mdx | 5 +- .../integrations/javascript.nuxt.mdx | 61 ++-- .../integrations/javascript.remix.mdx | 61 ++-- .../integrations/javascript.sveltekit.mdx | 59 ++-- .../getting-started-complete/javascript.mdx | 2 +- .../javascript.nextjs.mdx | 2 +- .../javascript.astro.mdx | 2 +- .../getting-started-tunneling/javascript.mdx | 2 +- .../javascript.nuxt.mdx | 2 +- .../javascript.sveltekit.mdx | 2 +- 39 files changed, 555 insertions(+), 470 deletions(-) rename docs/platforms/javascript/common/{troubleshooting => configuration}/img/tunnel.png (100%) create mode 100644 docs/platforms/javascript/common/configuration/tunneling.mdx diff --git a/docs/platforms/android/troubleshooting/index.mdx b/docs/platforms/android/troubleshooting/index.mdx index f3a6786fae9809..b9fa8464e5d991 100644 --- a/docs/platforms/android/troubleshooting/index.mdx +++ b/docs/platforms/android/troubleshooting/index.mdx @@ -199,7 +199,7 @@ If you set up the Sentry SDK and it's not sending any data to Sentry: - Check that you have configured a DSN and that you are passing it to the `dsn` option in `SentryAndroid.init()` or `AndroidManifest`. - When a crash happens or the app closes, the system allows only a short time to handle it before terminating the process. The SDK might not have enough time to send the event, spans, profile, or replay data. In these cases, simply relaunching the app should allow the SDK to send the remaining data. -- An ad-blocker (e.g. [DuckDuckGo's App Tracking Protection](https://duckduckgo.com/duckduckgo-help-pages/p-app-tracking-protection/what-is-app-tracking-protection)) or network restrictions might be preventing the SDK from reaching Sentry's servers. As a workaround, you can use [tunneling](/platforms/javascript/troubleshooting/#using-the-tunnel-option) to proxy Sentry events through your own server. Then you can add the [proxy option](/platforms/android/configuration/options/#proxy) to your SDK configuration. +- An ad-blocker (e.g. [DuckDuckGo's App Tracking Protection](https://duckduckgo.com/duckduckgo-help-pages/p-app-tracking-protection/what-is-app-tracking-protection)) or network restrictions might be preventing the SDK from reaching Sentry's servers. As a workaround, you can use [tunneling](/platforms/javascript/configuration/tunneling/) to proxy Sentry events through your own server. Then you can add the [proxy option](/platforms/android/configuration/options/#proxy) to your SDK configuration. - Set `debug: true` in the `SentryAndroid.init()` or the manifest options and observe your Logcat output when you start your application. The SDK may tell you why it is not sending any data. - Check the [Stats](https://sentry.io/orgredirect/organizations/:orgslug/stats/) and [Subscription](https://sentry.io/orgredirect/organizations/:orgslug/settings/billing/overview/) pages in Sentry. You may be out of quota. diff --git a/docs/platforms/dotnet/guides/aspnetcore/index.mdx b/docs/platforms/dotnet/guides/aspnetcore/index.mdx index d8301dda91873d..37cc563421f902 100644 --- a/docs/platforms/dotnet/guides/aspnetcore/index.mdx +++ b/docs/platforms/dotnet/guides/aspnetcore/index.mdx @@ -261,7 +261,7 @@ Opt-in to capture values from [System.Diagnostic.Activity](https://github.com/do ### Tunnel -If events are being logged to Sentry from the client-side, then you should use a [tunnel](/platforms/javascript/troubleshooting/#using-the-tunnel-option). +If events are being logged to Sentry from the client-side, then you should use a [tunnel](/platforms/javascript/configuration/tunneling/). A tunnel is an HTTP endpoint that acts as a proxy between Sentry and your application. Because you control this server, there is no risk of any requests sent to it being blocked. diff --git a/docs/platforms/javascript/common/troubleshooting/img/tunnel.png b/docs/platforms/javascript/common/configuration/img/tunnel.png similarity index 100% rename from docs/platforms/javascript/common/troubleshooting/img/tunnel.png rename to docs/platforms/javascript/common/configuration/img/tunnel.png diff --git a/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx b/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx index fcb2b2f80e44a6..789d9c3ff1a147 100644 --- a/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx +++ b/docs/platforms/javascript/common/configuration/integrations/servetunnel.mdx @@ -1,6 +1,6 @@ --- title: ServeTunnel -description: "Serves a tunnel on your server so browser events reach Sentry through your own origin, past ad blockers." +description: "Serves a tunnel on your server so Sentry browser SDK events reach Sentry through your own origin, past ad blockers." supportedCategories: - server notSupported: @@ -14,6 +14,8 @@ _Import name: `Sentry.serveTunnelIntegration`_ Ad blockers often drop requests to `*.ingest.sentry.io`. With a tunnel on your own origin, the browser SDK sends its events to your server instead, and your server forwards them to Sentry. This integration configures that tunnel on the server. +For an overview of tunneling, including how to set up your own tunnel endpoint, see Tunneling. + Set it up on both sides. Add the integration to your server `Sentry.init`, and set `tunnel: true` in your browser `Sentry.init`: ```javascript {tabTitle:Server} @@ -46,13 +48,13 @@ In framework mode, the SDK serves the tunnel from its request hooks, so this is -On Remix 3, the SDK serves the tunnel from its request hooks, so this is all the setup you need. On earlier versions, [serve it yourself](#serving-the-tunnel-yourself). +On Remix 3, the SDK serves the tunnel from its request hooks, so this is all the setup you need. On earlier versions, you also need to [mount the tunnel handler](#mounting-the-tunnel-handler). -The integration doesn't handle requests by itself, so you need to [serve the tunnel yourself](#serving-the-tunnel-yourself). +The integration doesn't handle requests by itself, so you also need to [mount the tunnel handler](#mounting-the-tunnel-handler). @@ -83,7 +85,7 @@ export default Sentry.withSentry( ## How It Works -- Framework SDKs answer tunnel requests before your app's own middleware, so auth or CSRF middleware doesn't block them. When you serve the tunnel yourself, mount it before that middleware too. +- Framework SDKs answer tunnel requests before your app's own middleware, so auth or CSRF middleware doesn't block them. When you mount the tunnel handler yourself, put it before that middleware too. - It only forwards envelopes whose DSN is allowed. By default, that's the server's own DSN. Other envelopes get a `403`, so the tunnel can't be used as an open proxy. - It forwards only the user agent, client hint, `Origin`, and `Referer` headers. Cookies and auth headers never reach Sentry. The visitor's IP address is forwarded from the `cf-connecting-ip`, `x-vercel-forwarded-for`, or `x-forwarded-for` header. - Tunnel requests don't create server spans. @@ -130,7 +132,7 @@ _Type: `string`_ The path prefix your app is served under, for example `/docs`. The SDK reads it from your framework config, so you only need this option when it can't. Set the same value in the browser `tunnel` option. -## Serving the Tunnel Yourself +## Mounting the Tunnel Handler On servers without a framework SDK hook, mount `handleTunnelRequestIfMatched` before your routes and body parsers. It forwards tunnel requests and returns `undefined` for everything else: @@ -145,6 +147,24 @@ app.use( ); ``` +```javascript {tabTitle:Fastify} +const Fastify = require("fastify"); +const Sentry = require("@sentry/node"); + +const app = Fastify(); + +app.addHook("onRequest", async (request, reply) => { + const tunnelResponse = Sentry.handleTunnelRequestIfMatched( + request.raw, + reply.raw + ); + if (tunnelResponse) { + await tunnelResponse; + return reply; + } +}); +``` + ```javascript {tabTitle:Fetch API} export default { fetch(request) { @@ -155,9 +175,7 @@ export default { }; ``` -`handleTunnelRequestIfMatched` takes a fetch `Request`, or Node's `req` and `res` (in Fastify, `request.raw` and `reply.raw`). It takes the same options as the integration. - -Without options, it uses the options of `serveTunnelIntegration`, or the server's own DSN if the integration isn't set up. To check a request without reading its body, use `isTunnelRequest(request)`. +`handleTunnelRequestIfMatched` takes a fetch `Request`, or Node's `req` and `res`, and the same options as the integration. Without options, `handleTunnelRequestIfMatched` uses the options of `serveTunnelIntegration`, or the server's own DSN if the integration isn't set up. To check a request without reading its body, use `isTunnelRequest(request)`. If you'd rather use a fixed path, register it as a route with `handleTunnelRequest`. Pass the same path to the integration, so tunnel requests don't create server spans: diff --git a/docs/platforms/javascript/common/configuration/options.mdx b/docs/platforms/javascript/common/configuration/options.mdx index bc17cb84e9b312..d97c1fec56e69e 100644 --- a/docs/platforms/javascript/common/configuration/options.mdx +++ b/docs/platforms/javascript/common/configuration/options.mdx @@ -87,9 +87,9 @@ Environments are case-sensitive. The environment name can't contain newlines, sp -Sets the URL that will be used to transport captured events. This can be used to work around ad-blockers or to have more granular control over events sent to Sentry. Adding your DSN is still required when using this option so necessary attributes can be set on the generated Sentry data. This option **requires the implementation** of a custom server endpoint. Learn more and find examples in [Dealing with Ad-Blockers](/platforms/javascript/troubleshooting/#dealing-with-ad-blockers). +Sets the URL that will be used to transport captured events. This can be used to work around ad-blockers or to have more granular control over events sent to Sentry. Adding your DSN is still required when using this option so necessary attributes can be set on the generated Sentry data. This option **requires the implementation** of a custom server endpoint. Learn more and find examples in Tunneling. -The endpoint must forward only the request body to Sentry and accept only your own DSNs. Learn more in [Using the `tunnel` Option](/platforms/javascript/troubleshooting/#using-the-tunnel-option). +The endpoint must forward only the request body to Sentry and accept only your own DSNs. Learn more in Setting Up Your Own Tunnel. @@ -99,9 +99,11 @@ The endpoint must forward only the request body to Sentry and accept only your o -Where the browser SDK sends captured events instead of the Sentry ingest URL. Use it to work around ad blockers or to control which events reach Sentry. The DSN is still required, so the SDK can set the attributes it needs on your data. +Where the browser SDK sends captured events instead of the Sentry ingest URL. Use it to work around ad blockers or to control which events reach Sentry. The DSN is still required, so the SDK can initialize correctly and forward the Sentry ingest URL to the tunnel. -- A string (URL or relative path) sends events to it as written. This **requires the implementation** of a custom server endpoint. The endpoint must forward only the request body to Sentry and accept only your own DSNs. Learn more in [Using the `tunnel` Option](/platforms/javascript/troubleshooting/#using-the-tunnel-option). +The `tunnel` option can be set to the following values: + +- A string (URL or relative path) sends events to it as written. This **requires the implementation** of a custom server endpoint. The endpoint must forward only the request body to Sentry and accept only your own DSNs. Learn more in Setting Up Your Own Tunnel. @@ -147,7 +149,7 @@ Set to `false` to opt out, for example, when using a custom `tunnel` or when run Controls which categories of data the SDK collects automatically. All fields are optional. - By default the SDK collects rich debugging context (including user identity, request/response bodies, and generative AI content) and scrubs values whose keys match the built-in sensitive denylist (`auth`, `token`, `password`, and similar). +By default the SDK collects rich debugging context (including user identity, request/response bodies, and generative AI content) and scrubs values whose keys match the built-in sensitive denylist (`auth`, `token`, `password`, and similar). @@ -338,21 +340,35 @@ The callback gets a second argument (called a "hint") which contains the origina - The JavaScript SDK uses a transport to send events to Sentry. - Transports will drop an event if it fails to send due to a lack of connection. + The JavaScript SDK uses a transport to send events to Sentry. Transports will + drop an event if it fails to send due to a lack of connection. In the browser, a `fetch`-based transport is used by default. - - - On the server, a `https`-based transport is used by default. - + + + On the server, a `https`-based transport is used by default. + - + A `fetch`-based transport is used by default. @@ -590,7 +606,8 @@ The `samplingContext` object passed to the function has the following properties - `name`: The name of the span as it was started. - `attributes`: The initial attributes of the span. - - `location`: The current page or worker URL, if available. Provided by the BrowserTracing integration. + - `location`: The current page or worker URL, if available. Provided by the + BrowserTracing integration. - `normalizedRequest`: Request information of the currently active HTTP @@ -644,7 +661,7 @@ The SDK skips storage operation spans for Durable Object keys whose names start Controls which bindings on `env` receive tracing data over [RPC calls](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc/) to Durable Objects and other Workers. RPC has no headers, so the SDK appends the trace context as a trailing argument that an instrumented receiver strips again before your method runs. -String entries must match a binding name exactly, so `"DB"` matches `"DB"` but not `"MY_DB"`. Use a regular expression for pattern matching. +String entries must match a binding name exactly, so `"DB"` matches `"DB"` but not `"MY_DB"`. Use a regular expression for pattern matching. This option controls outgoing RPC trace propagation, which is disabled by default. Add bindings to enable it for those receivers. Sentry-instrumented receivers automatically continue incoming traces without configuring this option. @@ -921,7 +938,8 @@ Controls whether the SDK installs runtime hooks that instrument supported depend - On SDK versions below `10.71.0`, logs are opt-in. Set `enableLogs: true` in your `Sentry.init` to send them. + On SDK versions below `10.71.0`, logs are opt-in. Set `enableLogs: true` in + your `Sentry.init` to send them. @@ -933,8 +951,8 @@ This function is called with a log object, and can return a modified log object, ## Metrics Options - **Note:** For Electron, metric options apply to the process where these options - are set. + **Note:** For Electron, metric options apply to the process where these + options are set. diff --git a/docs/platforms/javascript/common/configuration/tunneling.mdx b/docs/platforms/javascript/common/configuration/tunneling.mdx new file mode 100644 index 00000000000000..4843d66e41f4ed --- /dev/null +++ b/docs/platforms/javascript/common/configuration/tunneling.mdx @@ -0,0 +1,293 @@ +--- +title: Tunneling +sidebar_order: 35 +description: "Send browser SDK events to Sentry through your own server, so ad blockers don't drop them." +supportedCategories: + - browser + - server +--- + +Ad blockers often block requests to Sentry's ingest endpoints, so errors, sessions, and performance data never reach Sentry. A tunnel is an endpoint on your own origin that forwards these events to Sentry. Ad blockers don't treat requests to your own origin as third-party requests, so they let them through. + +![Tunnel Flow](./img/tunnel.png) + +## Choosing an Approach + + + +- **`tunnelRoute` (recommended):** The Next.js SDK creates the tunnel route for you. See `tunnelRoute`. +- **Your own tunnel:** Point the `tunnel` option at an endpoint you write. See [Setting Up Your Own Tunnel](#setting-up-your-own-tunnel). + + + + + +Point the `tunnel` option at an endpoint you write. See [Setting Up Your Own Tunnel](#setting-up-your-own-tunnel). + + + + + +- **Managed tunnel (recommended):** The SDK picks the tunnel path and your server SDK forwards the events. See [Using the Managed Tunnel](#using-the-managed-tunnel). +- **Your own tunnel:** Point the `tunnel` option at an endpoint you write. Use this on SDK versions before `11.10.0`, or when your server doesn't run a Sentry JavaScript SDK. See [Setting Up Your Own Tunnel](#setting-up-your-own-tunnel). + +## Using the Managed Tunnel + + + +Set `tunnel: true` in your browser `Sentry.init`, and add `serveTunnelIntegration` to your server `Sentry.init`: + +```javascript {tabTitle:Browser} +Sentry.init({ + dsn: "___PUBLIC_DSN___", + tunnel: true, +}); +``` + +```javascript {tabTitle:Server} +Sentry.init({ + dsn: "___PUBLIC_DSN___", + integrations: [Sentry.serveTunnelIntegration()], +}); +``` + +The browser then sends its events to a path on your own origin derived from the DSN, and your server forwards them to Sentry. + + + +The SDK serves the tunnel for you, so this is all the setup you need. + + + + + +In framework mode, the SDK serves the tunnel for you. In other modes, you also need to mount the tunnel handler. + + + + + +On Remix 3, the SDK serves the tunnel for you. On earlier versions, you also need to mount the tunnel handler. + + + + + + + + + +The integration doesn't handle requests by itself. Call `handleTunnelRequestIfMatched` at the start of your `fetch` handler: + +```javascript +import * as Sentry from "@sentry/cloudflare"; + +export default Sentry.withSentry( + (env) => ({ + dsn: "___PUBLIC_DSN___", + integrations: [Sentry.serveTunnelIntegration()], + }), + { + async fetch(request, env, ctx) { + return ( + (await Sentry.handleTunnelRequestIfMatched(request)) ?? + new Response("Hello") + ); + }, + } +); +``` + + + + + +The integration doesn't handle requests by itself. Mount the tunnel handler, `handleTunnelRequestIfMatched`, before your routes and body parsers. It forwards tunnel requests and returns `undefined` for everything else: + +```javascript {tabTitle:Express} +const express = require("express"); +const Sentry = require("@sentry/node"); + +const app = express(); + +app.use( + (req, res, next) => Sentry.handleTunnelRequestIfMatched(req, res) ?? next() +); +``` + +```javascript {tabTitle:Fastify} +const Fastify = require("fastify"); +const Sentry = require("@sentry/node"); + +const app = Fastify(); + +app.addHook("onRequest", async (request, reply) => { + const tunnelResponse = Sentry.handleTunnelRequestIfMatched( + request.raw, + reply.raw + ); + if (tunnelResponse) { + await tunnelResponse; + return reply; + } +}); +``` + +```javascript {tabTitle:Fetch API} +export default { + fetch(request) { + return ( + Sentry.handleTunnelRequestIfMatched(request) ?? new Response("Hello") + ); + }, +}; +``` + + + + + +See `serveTunnelIntegration` for all options, like a fixed path or more allowed DSNs, and for registering the tunnel as a route instead. + + + + + +Your server also needs to serve the tunnel. See [`serveTunnelIntegration`](/platforms/javascript/guides/node/configuration/integrations/servetunnel/) for how to set it up, and for all options, like a fixed path or more allowed DSNs. + + + + + +## Setting Up Your Own Tunnel + +Set the `tunnel` option to the URL of your endpoint. We recommend a path on your own origin, like `/tunnel`, so the browser doesn't send a CORS preflight request: + +```javascript +Sentry.init({ + dsn: "___PUBLIC_DSN___", + tunnel: "/tunnel", +}); +``` + +The SDK now sends all events to `/tunnel`. Your endpoint must: + +- Forward the request body to Sentry, without cookies or auth headers. +- Accept only envelopes whose DSN is on a list of allowed DSNs. Otherwise, anyone can use your endpoint to send requests to other servers. +- Send the envelope only to the host of that DSN. + +If your server runs a Sentry JavaScript SDK, `handleTunnelRequest` does all of this for you: + +```javascript +// Import from your server SDK. On version 10, import from @sentry/core instead. +import { handleTunnelRequest } from "@sentry/node"; + +export async function POST(request) { + return handleTunnelRequest(request, { + allowedDsns: ["___PUBLIC_DSN___"], + }); +} +``` + + + +```csharp {tabTitle:ASP.NET Core} +// This functionality is now built-in to the Sentry.AspNetCore package. +// See https://docs.sentry.io/platforms/dotnet/guides/aspnetcore/#tunnel +// docs for more information. + +// This example shows how you could implement it yourself: + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Text.Json; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; + +// Change host appropriately if you run your own Sentry instance. +const string host = "sentry.io"; +// Set knownProjectIds to a list with your Sentry project IDs which you +// want to accept through this proxy. +var knownProjectIds = new HashSet() { }; + +var client = new HttpClient(); +WebHost.CreateDefaultBuilder(args).Configure(a => + a.Run(async context => + { + context.Request.EnableBuffering(); + using var reader = new StreamReader(context.Request.Body); + var header = await reader.ReadLineAsync(); + var headerJson = JsonSerializer.Deserialize>(header); + if (headerJson.TryGetValue("dsn", out var dsnString) + && Uri.TryCreate(dsnString.ToString(), UriKind.Absolute, out var dsn)) + { + var projectId = dsn.AbsolutePath.Trim('/'); + if (knownProjectIds.Contains(projectId) && string.Equals(dsn.Host, host, StringComparison.OrdinalIgnoreCase)) { + context.Request.Body.Position = 0; + await client.PostAsync($"https://{dsn.Host}/api/{projectId}/envelope/", + new StreamContent(context.Request.Body)); + } + } + })).Build().Run(); +``` + +```ruby {tabTitle:Rails} +require 'net/http' + +class HomeController < ApplicationController + + SENTRY_HOST = "oXXXXXX.ingest.sentry.io" + SENTRY_PROJECT_IDS = ["123456"] + + def tunnel + envelope = request.body.read + piece = envelope.split("\n").first + header = JSON.parse(piece) + dsn = URI.parse(header['dsn']) + project_id = dsn.path.tr('/', '') + + raise "Invalid sentry hostname: #{dsn.hostname}" if dsn.hostname != SENTRY_HOST + raise "Invalid sentry project id: #{project_id}" unless SENTRY_PROJECT_IDS.include?(project_id) + + upstream_sentry_url = "https://#{SENTRY_HOST}/api/#{project_id}/envelope/" + Net::HTTP.post(URI(upstream_sentry_url), envelope) + + head(:ok) + rescue => e + # handle exception in your preferred style, + # e.g. by logging or forwarding to server Sentry project + Rails.logger.error('error tunneling to sentry') + end + +end +``` + + + +### Credentials on Tunnel Requests + +The browser SDK doesn't set the [`credentials`](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials) option of `fetch`. With the default value `same-origin`, the browser sends credentials (cookies and HTTP authentication) to a tunnel on the same origin as your app. A tunnel that forwards request headers sends these credentials to Sentry. + + + +The Next.js `tunnelRoute` option handles this for you. + + + +Forward only the request body, or tell the browser to send no credentials: + +```javascript +Sentry.init({ + dsn: "___PUBLIC_DSN___", + tunnel: "/tunnel", + transportOptions: { + fetchOptions: { credentials: "omit" }, + }, +}); +``` + +If a layer in front of your tunnel requires credentials (for example, Vercel Deployment Protection, Cloudflare Access, or HTTP Basic authentication), it rejects requests without credentials. Keep the default in this case, and make sure that your tunnel doesn't forward the request headers. diff --git a/docs/platforms/javascript/common/troubleshooting/index.mdx b/docs/platforms/javascript/common/troubleshooting/index.mdx index 90a6568346df4a..069439f628f5a9 100644 --- a/docs/platforms/javascript/common/troubleshooting/index.mdx +++ b/docs/platforms/javascript/common/troubleshooting/index.mdx @@ -41,6 +41,7 @@ If you set up the Sentry SDK and it's not sending any data to Sentry: If you update your Sentry SDK to a new major version, you might encounter breaking changes that need some adaption on your end. + Check out our migration guide to learn everything you need @@ -126,7 +127,7 @@ Since Chrome 142, a page needs the user's permission before it can send requests | public | private network or localhost | Permission prompt | | private network | localhost | Permission prompt | -This affects a publicly hosted app reporting to a self-hosted Sentry that is only reachable on an internal address, and Spotlight enabled on a non-localhost page. To avoid the prompt, expose Sentry on a public hostname, or use the [`tunnel` option](#using-the-tunnel-option) to send events through your app's own origin. +This affects a publicly hosted app reporting to a self-hosted Sentry that is only reachable on an internal address, and Spotlight enabled on a non-localhost page. To avoid the prompt, expose Sentry on a public hostname, or use a tunnel to send events through your app's own origin. @@ -213,242 +214,10 @@ Chrome then ignores the SDK stack frames when debugging. -When you are using our CDN, ad-blocking or script-blocking extensions may prevent our SDK from being fetched and initialized properly. Because of this, any call to the SDKs API will fail and may cause your application to behave unexpectedly. - -Additionally, even when the SDK is downloaded and initialized correctly, Sentry endpoints that need to receive captured data may be blocked as well. This prevents any error reports, sessions health, or performance data from being delivered, making it effectively unavailable in [sentry.io](https://sentry.io). - -Furthermore, some browsers, like [Brave](https://brave.com/), have built-in ad-blockers that may block requests sent to our endpoint. Even if users deactivate your domain from blocking, Brave might continue to block [requests made from service workers](https://github.com/getsentry/sentry/issues/47912#issuecomment-1573714875). - -You can work around our CDN bundles being blocked by [using our NPM packages](#using-the-npm-package) and bundling our SDK with your app. However, the endpoint blockage can only be resolved by using a tunnel as explained below. - - - - - - - - -Set `tunnel: true` in your browser `Sentry.init`, and add `serveTunnelIntegration` to your server `Sentry.init`: - -```javascript {tabTitle:Browser} -Sentry.init({ - dsn: "___PUBLIC_DSN___", - tunnel: true, -}); -``` - -```javascript {tabTitle:Server} -Sentry.init({ - dsn: "___PUBLIC_DSN___", - integrations: [Sentry.serveTunnelIntegration()], -}); -``` - -The browser then sends its events to a path on your own origin derived from the DSN, and your server forwards them to Sentry. - - - -The SDK serves the tunnel for you and reads your app's base path from your framework config. - - - - - -In framework mode, the SDK serves the tunnel for you and reads your app's base path from your framework config. In other modes, see Serving the Tunnel Yourself. - - - - - -On Remix 3, the SDK serves the tunnel for you and reads your app's base path from your framework config. On earlier versions, see Serving the Tunnel Yourself. - - - - - -The integration doesn't handle requests by itself. Mount `handleTunnelRequestIfMatched` on your server, before your routes and body parsers: - -```javascript {filename:Server} -app.use( - (req, res, next) => Sentry.handleTunnelRequestIfMatched(req, res) ?? next() -); -``` - - - - - -See `serveTunnelIntegration` for a fixed path, more allowed DSNs, and how the tunnel handles requests. If you can't use the managed tunnel, set up your own tunnel as explained below. - - - - - -See [`serveTunnelIntegration`](/platforms/javascript/guides/node/configuration/integrations/servetunnel/) for a fixed path, more allowed DSNs, and how the tunnel handles requests. If you can't use the managed tunnel, set up your own tunnel as explained below. - - - - - - - - - - -The Sentry Next.js SDK has a separate option to make setting up tunnels very -straight-forward, allowing you to skip the setup below. See -`tunnelRoute` to learn how to set up tunneling on Next.js. - -If you do not want to configure `tunnelRoute`, you can follow the guide below. - - - -A tunnel is an HTTP endpoint that acts as a proxy between Sentry and your application. Because you control this server, there is no risk of any requests sent to it being blocked. When the endpoint lives under the same origin (although it does not have to in order for the tunnel to work), the browser will not treat any requests to the endpoint as a third-party request. As a result, these requests will have different security measures applied which, by default, don't trigger ad-blockers. A quick summary of the flow can be found below. - -![Tunnel Flow](./img/tunnel.png) - -Starting with version `6.7.0` of the JavaScript SDK, you can use the `tunnel` option to tell the SDK to deliver events to the configured URL, instead of using the DSN. This allows the SDK to remove `sentry_key` from the query parameters, which is one of the main reasons ad-blockers prevent sending events in the first place. This option also stops the SDK from sending preflight requests, which was one of the requirements that necessitated sending the `sentry_key` in the query parameters. - -To enable the `tunnel` option, provide either a relative or an absolute URL in your `Sentry.init` call. When you use a relative URL, it's relative to the current origin, and this is the form that we recommend. Using a relative URL will not trigger a preflight CORS request, so no events will be blocked, because the ad-blocker will not treat these events as third-party requests. When using tunneling with server-side SDKs like `@sentry/node` or `@sentry/bun` you must provide an absolute URL. +Ad-blocking or script-blocking extensions may block Sentry in two ways: -```javascript -Sentry.init({ - dsn: "___PUBLIC_DSN___", - tunnel: "/tunnel", -}); -``` - -Once configured, all events will be sent to the `/tunnel` endpoint. This solution, however, requires an additional configuration on the server, as the events now need to be parsed and redirected to Sentry. - -Your tunnel endpoint must: - -- Forward the request body to Sentry, without cookies or auth headers. -- Accept only envelopes whose DSN is on a list of allowed DSNs. Otherwise, anyone can use your endpoint to send requests to other servers. -- Send the envelope only to the host of that DSN. - -On a JavaScript server with Fetch API `Request` and `Response` objects, `handleTunnelRequest` from `@sentry/core` (SDK version `10.40.0` and later) does this for you. Starting with versions `10.77.1` (v10) and `11.9.0` (v11), it also rejects requests larger than 20 MiB. - -```javascript -import { handleTunnelRequest } from "@sentry/core"; - -export async function POST(request) { - return handleTunnelRequest(request, { - allowedDsns: ["___PUBLIC_DSN___"], - }); -} -``` - -If you implement the endpoint yourself, here's an example for your server component: - -```csharp - -// This functionality is now built-in to the Sentry.AspNetCore package. -// See https://docs.sentry.io/platforms/dotnet/guides/aspnetcore/#tunnel -// docs for more information. - -// This example shows how you could implement it yourself: - -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using System.Text.Json; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; - -// Change host appropriately if you run your own Sentry instance. -const string host = "sentry.io"; -// Set knownProjectIds to a list with your Sentry project IDs which you -// want to accept through this proxy. -var knownProjectIds = new HashSet() { }; - -var client = new HttpClient(); -WebHost.CreateDefaultBuilder(args).Configure(a => - a.Run(async context => - { - context.Request.EnableBuffering(); - using var reader = new StreamReader(context.Request.Body); - var header = await reader.ReadLineAsync(); - var headerJson = JsonSerializer.Deserialize>(header); - if (headerJson.TryGetValue("dsn", out var dsnString) - && Uri.TryCreate(dsnString.ToString(), UriKind.Absolute, out var dsn)) - { - var projectId = dsn.AbsolutePath.Trim('/'); - if (knownProjectIds.Contains(projectId) && string.Equals(dsn.Host, host, StringComparison.OrdinalIgnoreCase)) { - context.Request.Body.Position = 0; - await client.PostAsync($"https://{dsn.Host}/api/{projectId}/envelope/", - new StreamContent(context.Request.Body)); - } - } - })).Build().Run(); -``` - -```ruby -require 'net/http' - -class HomeController < ApplicationController - - SENTRY_HOST = "oXXXXXX.ingest.sentry.io" - SENTRY_PROJECT_IDS = ["123456"] - - def tunnel - envelope = request.body.read - piece = envelope.split("\n").first - header = JSON.parse(piece) - dsn = URI.parse(header['dsn']) - project_id = dsn.path.tr('/', '') - - raise "Invalid sentry hostname: #{dsn.hostname}" if dsn.hostname != SENTRY_HOST - raise "Invalid sentry project id: #{project_id}" unless SENTRY_PROJECT_IDS.include?(project_id) - - upstream_sentry_url = "https://#{SENTRY_HOST}/api/#{project_id}/envelope/" - Net::HTTP.post(URI(upstream_sentry_url), envelope) - - head(:ok) - rescue => e - # handle exception in your preferred style, - # e.g. by logging or forwarding to server Sentry project - Rails.logger.error('error tunneling to sentry') - end - -end -``` - -```javascript -const SENTRY_HOST = "oXXXXXX.ingest.sentry.io"; -const SENTRY_PROJECT_IDS = ["123456"]; - -export const postAction = async ({ request }) => { - try { - const envelopeBytes = await request.arrayBuffer(); - const envelope = new TextDecoder().decode(envelopeBytes); - const piece = envelope.split("\n")[0]; - const header = JSON.parse(piece); - const dsn = new URL(header["dsn"]); - const project_id = dsn.pathname?.replace("/", ""); - - if (dsn.hostname !== SENTRY_HOST) { - throw new Error(`Invalid sentry hostname: ${dsn.hostname}`); - } - - if (!project_id || !SENTRY_PROJECT_IDS.includes(project_id)) { - throw new Error(`Invalid sentry project id: ${project_id}`); - } - - const upstream_sentry_url = `https://${SENTRY_HOST}/api/${project_id}/envelope/`; - await fetch(upstream_sentry_url, { method: "POST", body: envelopeBytes }); - - return json({}, { status: 200 }); - } catch (e) { - console.error("error tunneling to sentry", e); - return json({ error: "error tunneling to sentry" }, { status: 500 }); - } -}; -``` - -If your use-case is related to the SDK package itself being blocked, any of the following solutions will help you resolve this issue. +- They block requests to Sentry's ingest endpoints, so captured data never reaches [sentry.io](https://sentry.io). Send events through your own server to avoid this, as described in Tunneling. +- When you load the SDK from our CDN, they may prevent it from being fetched and initialized. Any call to the SDK's API then fails and may cause your application to behave unexpectedly. Any of the following solutions resolves this. ### Using the NPM Package @@ -508,32 +277,6 @@ If you would like to copy and paste the snippet directly, here it is minified: - - -The browser SDK doesn't set the [`credentials`](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials) option of `fetch`. With the default value `same-origin`, the browser sends credentials (cookies and HTTP authentication) to a tunnel on the same origin as your app. A tunnel that forwards request headers sends these credentials to Sentry. - - - -The Next.js `tunnelRoute` option handles this for you. See `tunnelRoute`. - - - -Forward only the request body, or tell the browser to send no credentials: - -```javascript -Sentry.init({ - dsn: "___PUBLIC_DSN___", - tunnel: "/tunnel", - transportOptions: { - fetchOptions: { credentials: "omit" }, - }, -}); -``` - -If a layer in front of your tunnel requires credentials (for example, Vercel Deployment Protection, Cloudflare Access, or HTTP Basic authentication), it rejects requests without credentials. Keep the default in this case, and make sure that your tunnel doesn't forward the request headers. - - - diff --git a/docs/platforms/javascript/guides/astro/index.mdx b/docs/platforms/javascript/guides/astro/index.mdx index 8e03a67597c9bb..377dd37dd3990b 100644 --- a/docs/platforms/javascript/guides/astro/index.mdx +++ b/docs/platforms/javascript/guides/astro/index.mdx @@ -205,7 +205,7 @@ At this point, you should have integrated Sentry into your Astro application. He - Explore [practical guides](/get-started/guides/) on what to monitor, log, track, and investigate after setup - Set up Agent Tracing to monitor your AI systems -- Set up tunneling to avoid ad blockers blocking Sentry events +- Set up tunneling to avoid ad blockers blocking Sentry events diff --git a/docs/platforms/javascript/guides/cordova/configuration/options.mdx b/docs/platforms/javascript/guides/cordova/configuration/options.mdx index 6906a9235b5026..94c8535356f424 100644 --- a/docs/platforms/javascript/guides/cordova/configuration/options.mdx +++ b/docs/platforms/javascript/guides/cordova/configuration/options.mdx @@ -49,9 +49,9 @@ Environments are case-sensitive. The environment name can't contain newlines, sp -Sets the URL that will be used to transport captured events. This can be used to work around ad-blockers or to have more granular control over events sent to Sentry. Adding your DSN is still required when using this option so necessary attributes can be set on the generated Sentry data. This option **requires the implementation** of a custom server endpoint. Learn more and find examples in [Dealing with Ad-Blockers](/platforms/javascript/troubleshooting/#dealing-with-ad-blockers). +Sets the URL that will be used to transport captured events. This can be used to work around ad-blockers or to have more granular control over events sent to Sentry. Adding your DSN is still required when using this option so necessary attributes can be set on the generated Sentry data. This option **requires the implementation** of a custom server endpoint. Learn more and find examples in Tunneling. -The endpoint must forward only the request body to Sentry and accept only your own DSNs. Learn more in [Using the `tunnel` Option](/platforms/javascript/troubleshooting/#using-the-tunnel-option). +The endpoint must forward only the request body to Sentry and accept only your own DSNs. Learn more in Setting Up Your Own Tunnel. diff --git a/docs/platforms/javascript/guides/ember/index.mdx b/docs/platforms/javascript/guides/ember/index.mdx index 4a12916733b9e7..8b944aebf8d2fb 100644 --- a/docs/platforms/javascript/guides/ember/index.mdx +++ b/docs/platforms/javascript/guides/ember/index.mdx @@ -154,7 +154,7 @@ Now, head over to your project on [Sentry.io](https://sentry.io) to view the col At this point, you should have integrated Sentry into your Ember application. Here's what to explore next: - Explore [practical guides](/get-started/guides/) on what to monitor, log, track, and investigate after setup -- Set up tunneling to avoid ad blockers from blocking Sentry events +- Set up tunneling to avoid ad blockers from blocking Sentry events diff --git a/docs/platforms/javascript/guides/gatsby/index.mdx b/docs/platforms/javascript/guides/gatsby/index.mdx index ec585fdbbe0d23..c6d51f50c2fa3b 100644 --- a/docs/platforms/javascript/guides/gatsby/index.mdx +++ b/docs/platforms/javascript/guides/gatsby/index.mdx @@ -165,7 +165,7 @@ At this point, you should have integrated Sentry into your Gatsby application. H - Explore [practical guides](/get-started/guides/) on what to monitor, log, track, and investigate after setup - Set up Agent Tracing to monitor your AI systems -- Set up tunneling to avoid ad blockers from blocking Sentry events +- Set up tunneling to avoid ad blockers from blocking Sentry events diff --git a/docs/platforms/javascript/guides/nextjs/configuration/build/index.mdx b/docs/platforms/javascript/guides/nextjs/configuration/build/index.mdx index 65cc4f66e263a2..7bf7f3ce738122 100644 --- a/docs/platforms/javascript/guides/nextjs/configuration/build/index.mdx +++ b/docs/platforms/javascript/guides/nextjs/configuration/build/index.mdx @@ -227,7 +227,7 @@ export const config = { -Learn more about tunneling in the troubleshooting section. +Learn more about tunneling in Tunneling. The tunnel route works in one of two modes: diff --git a/docs/platforms/javascript/guides/react-router/index.mdx b/docs/platforms/javascript/guides/react-router/index.mdx index 27fcb45648a807..14c926394cb087 100644 --- a/docs/platforms/javascript/guides/react-router/index.mdx +++ b/docs/platforms/javascript/guides/react-router/index.mdx @@ -82,7 +82,7 @@ Now, head over to your project on [Sentry.io](https://sentry.io) to view the col At this point, you should have integrated Sentry into your React Router Framework application. Here's what to explore next: - Explore [practical guides](/get-started/guides/) on what to monitor, log, track, and investigate after setup -- Set up tunneling to avoid ad blockers blocking Sentry events +- Set up tunneling to avoid ad blockers blocking Sentry events - Read the manual setup guide for advanced configuration diff --git a/docs/platforms/javascript/guides/react-router/manual-setup.mdx b/docs/platforms/javascript/guides/react-router/manual-setup.mdx index aec0eda1e80641..9dc43365e7f9dd 100644 --- a/docs/platforms/javascript/guides/react-router/manual-setup.mdx +++ b/docs/platforms/javascript/guides/react-router/manual-setup.mdx @@ -458,7 +458,7 @@ Now, head over to your project on [Sentry.io](https://sentry.io) to view the col At this point, you should have integrated Sentry into your React Router Framework application. Here's what to explore next: - Explore [practical guides](/get-started/guides/) on what to monitor, log, track, and investigate after setup -- Set up tunneling to avoid ad blockers blocking Sentry events +- Set up tunneling to avoid ad blockers blocking Sentry events - Set up Agent Tracing to monitor your AI systems diff --git a/docs/platforms/javascript/guides/react/index.mdx b/docs/platforms/javascript/guides/react/index.mdx index f44073a9ba55a5..4b2bc7300c0a92 100644 --- a/docs/platforms/javascript/guides/react/index.mdx +++ b/docs/platforms/javascript/guides/react/index.mdx @@ -276,7 +276,7 @@ Now, head over to your project on [Sentry.io](https://sentry.io) to view the col At this point, you should have integrated Sentry into your React application. Here's what to explore next: - Explore [practical guides](/get-started/guides/) on what to monitor, log, track, and investigate after setup -- Set up tunneling to avoid ad blockers from blocking Sentry events +- Set up tunneling to avoid ad blockers from blocking Sentry events - Explore React-specific features diff --git a/docs/platforms/javascript/guides/solid/index.mdx b/docs/platforms/javascript/guides/solid/index.mdx index 4cfd9c1810a377..3f68538c33adc1 100644 --- a/docs/platforms/javascript/guides/solid/index.mdx +++ b/docs/platforms/javascript/guides/solid/index.mdx @@ -152,7 +152,7 @@ Now, head over to your project on [Sentry.io](https://sentry.io/) to view the co At this point, you should have integrated Sentry into your Solid application. Here's what to explore next: - Explore [practical guides](/get-started/guides/) on what to monitor, log, track, and investigate after setup -- Set up tunneling to avoid ad blockers from blocking Sentry events +- Set up tunneling to avoid ad blockers from blocking Sentry events - Explore Solid-specific features diff --git a/docs/platforms/javascript/guides/solidstart/index.mdx b/docs/platforms/javascript/guides/solidstart/index.mdx index f7c7a9f1baade9..bb69e18cc532df 100644 --- a/docs/platforms/javascript/guides/solidstart/index.mdx +++ b/docs/platforms/javascript/guides/solidstart/index.mdx @@ -369,7 +369,7 @@ At this point, you should have integrated Sentry into your SolidStart applicatio - Explore [practical guides](/get-started/guides/) on what to monitor, log, track, and investigate after setup - Set up Agent Tracing to monitor your AI systems -- Set up tunneling to avoid ad blockers blocking Sentry events +- Set up tunneling to avoid ad blockers blocking Sentry events diff --git a/docs/platforms/javascript/guides/svelte/index.mdx b/docs/platforms/javascript/guides/svelte/index.mdx index 1931db34362199..3a50e8b42a42e0 100644 --- a/docs/platforms/javascript/guides/svelte/index.mdx +++ b/docs/platforms/javascript/guides/svelte/index.mdx @@ -192,7 +192,7 @@ Now, head over to your project on [Sentry.io](https://sentry.io/) to view the co At this point, you should have integrated Sentry into your Svelte application. Here's what to explore next: - Explore [practical guides](/get-started/guides/) on what to monitor, log, track, and investigate after setup -- Set up tunneling to avoid ad blockers from blocking Sentry events +- Set up tunneling to avoid ad blockers from blocking Sentry events - Explore Svelte-specific features diff --git a/docs/platforms/javascript/guides/vue/index.mdx b/docs/platforms/javascript/guides/vue/index.mdx index fe85f766b38162..7d1102360a9e6f 100644 --- a/docs/platforms/javascript/guides/vue/index.mdx +++ b/docs/platforms/javascript/guides/vue/index.mdx @@ -232,7 +232,7 @@ Now, head over to your project on [Sentry.io](https://sentry.io/) to view the co At this point, you should have integrated Sentry into your Vue application. Here's what to explore next: - Explore [practical guides](/get-started/guides/) on what to monitor, log, track, and investigate after setup -- Set up tunneling to avoid ad blockers from blocking Sentry events +- Set up tunneling to avoid ad blockers from blocking Sentry events - Explore Vue-specific features diff --git a/docs/product/explore/dropped-data.mdx b/docs/product/explore/dropped-data.mdx index 93623f10b9f5e6..88a2a5cc36adc0 100644 --- a/docs/product/explore/dropped-data.mdx +++ b/docs/product/explore/dropped-data.mdx @@ -158,7 +158,7 @@ The request failed without a response. Ad blockers and browser extensions are a - **Browser:** A CORS misconfiguration, the user being offline, or the page unloading before the request completes. - **Server:** No outbound network access from the host or container, an egress firewall, a DNS failure, TLS errors from an intercepting proxy, or proxy settings the SDK is not using. - **Actions:** - - For ad blockers, route events through your own domain with the [`tunnel` option](/platforms/javascript/troubleshooting/#using-the-tunnel-option). + - For ad blockers, route events through your own domain with the [`tunnel` option](/platforms/javascript/configuration/tunneling/). - For browsers, confirm in devtools that requests to your ingest domain are not blocked, and check your CORS configuration. - For servers, verify outbound connectivity to your ingest domain from the host or container. diff --git a/platform-includes/configuration/integrations/javascript.astro.mdx b/platform-includes/configuration/integrations/javascript.astro.mdx index 66fba29ecf1f82..7466a54771c5d7 100644 --- a/platform-includes/configuration/integrations/javascript.astro.mdx +++ b/platform-includes/configuration/integrations/javascript.astro.mdx @@ -8,8 +8,8 @@ Depending on whether an integration enhances the functionality of a particular r ### Common Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | -| --------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | +| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | +| ---------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | | [`dedupeIntegration`](./dedupe/) | ✓ | ✓ | | | | [`eventFiltersIntegration`](./eventfilters/) | ✓ | ✓ | | | | [`functionToStringIntegration`](./functiontostring/) | ✓ | | | | @@ -20,37 +20,37 @@ Depending on whether an integration enhances the functionality of a particular r ### Browser Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Replay** | **Additional Context** | -| ----------------------------------------------------- | :--------------: | :--------: | :---------: | :--------: | :--------------------: | -| [`breadcrumbsIntegration`](./breadcrumbs/) | ✓ | | | | ✓ | -| [`browserApiErrorsIntegration`](./browserapierrors/) | ✓ | ✓ | | | | -| [`browserSessionIntegration`](./browsersession/) | ✓ | | | | ✓ | -| [`browserTracingIntegration`](./browsertracing/) | ✓ | | ✓ | | ✓ | -| [`fetchStreamPerformanceIntegration`](./fetchstreamperformance/) | | | ✓ | | | -| [`interactionsIntegration`](./interactions/) | | | ✓ | | | -| [`userTimingIntegration`](./usertiming/) | | | ✓ | | | -| [`webVitalsIntegration`](./webvitals/) | With BrowserTracing | | ✓ | | | -| [`globalHandlersIntegration`](./globalhandlers/) | ✓ | ✓ | | | | -| [`httpContextIntegration`](./httpcontext/) | ✓ | | | | ✓ | -| [`browserProfilingIntegration`](./browserprofiling/) | | | ✓ | | | -| [`contextLinesIntegration`](./contextlines/) | | ✓ | | | | -| [`featureFlagsIntegration`](./featureflags/) | | | | | ✓ | -| [`graphqlClientIntegration`](./graphqlclient/) | | | | | ✓ | -| [`httpClientIntegration`](./httpclient/) | | ✓ | | | | -| [`launchDarklyIntegration`](./launchdarkly/) | | | | | ✓ | -| [`moduleMetadataIntegration`](./modulemetadata/) | | | | | ✓ | -| [`openFeatureIntegration`](./openfeature/) | | | | | ✓ | -| [`replayCanvasIntegration`](./replaycanvas/) | | | | ✓ | | -| [`replayIntegration`](./replay/) | | | | ✓ | ✓ | -| [`reportingObserverIntegration`](./reportingobserver/) | | ✓ | | | | -| [`statsigIntegration`](./statsig/) | | | | | ✓ | -| [`supabaseIntegration`](./supabase/) | | ✓ | ✓ | | | -| [`unleashIntegration`](./unleash/) | | | | | ✓ | +| | **Auto Enabled** | **Errors** | **Tracing** | **Replay** | **Additional Context** | +| ---------------------------------------------------------------- | :-----------------: | :--------: | :---------: | :--------: | :--------------------: | +| [`breadcrumbsIntegration`](./breadcrumbs/) | ✓ | | | | ✓ | +| [`browserApiErrorsIntegration`](./browserapierrors/) | ✓ | ✓ | | | | +| [`browserSessionIntegration`](./browsersession/) | ✓ | | | | ✓ | +| [`browserTracingIntegration`](./browsertracing/) | ✓ | | ✓ | | ✓ | +| [`fetchStreamPerformanceIntegration`](./fetchstreamperformance/) | | | ✓ | | | +| [`interactionsIntegration`](./interactions/) | | | ✓ | | | +| [`userTimingIntegration`](./usertiming/) | | | ✓ | | | +| [`webVitalsIntegration`](./webvitals/) | With BrowserTracing | | ✓ | | | +| [`globalHandlersIntegration`](./globalhandlers/) | ✓ | ✓ | | | | +| [`httpContextIntegration`](./httpcontext/) | ✓ | | | | ✓ | +| [`browserProfilingIntegration`](./browserprofiling/) | | | ✓ | | | +| [`contextLinesIntegration`](./contextlines/) | | ✓ | | | | +| [`featureFlagsIntegration`](./featureflags/) | | | | | ✓ | +| [`graphqlClientIntegration`](./graphqlclient/) | | | | | ✓ | +| [`httpClientIntegration`](./httpclient/) | | ✓ | | | | +| [`launchDarklyIntegration`](./launchdarkly/) | | | | | ✓ | +| [`moduleMetadataIntegration`](./modulemetadata/) | | | | | ✓ | +| [`openFeatureIntegration`](./openfeature/) | | | | | ✓ | +| [`replayCanvasIntegration`](./replaycanvas/) | | | | ✓ | | +| [`replayIntegration`](./replay/) | | | | ✓ | ✓ | +| [`reportingObserverIntegration`](./reportingobserver/) | | ✓ | | | | +| [`statsigIntegration`](./statsig/) | | | | | ✓ | +| [`supabaseIntegration`](./supabase/) | | ✓ | ✓ | | | +| [`unleashIntegration`](./unleash/) | | | | | ✓ | ### Node.js Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | -| --------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | +| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | +| ---------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | | [`amqplibIntegration`](./amqplib/) | ✓ | | ✓ | | | [`awsLambdaWebIntegration`](./awslambdaweb/) | ✓ | | | ✓ | | [`consoleIntegration`](./console/) | ✓ | | | ✓ | @@ -98,3 +98,4 @@ Depending on whether an integration enhances the functionality of a particular r | [`firebaseIntegration`](./firebase/) | ✓ | | ✓ | | | [`zodErrorsIntegration`](./zodErrors/) | | | | ✓ | | [`pinoIntegration`](./pino/) | | ✓ | | | +| [`serveTunnelIntegration`](./servetunnel/) | | | | | diff --git a/platform-includes/configuration/integrations/javascript.aws-lambda.mdx b/platform-includes/configuration/integrations/javascript.aws-lambda.mdx index ac3543690d0d99..016c450488ece7 100644 --- a/platform-includes/configuration/integrations/javascript.aws-lambda.mdx +++ b/platform-includes/configuration/integrations/javascript.aws-lambda.mdx @@ -1,7 +1,7 @@ ### Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | -| --------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | +| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | +| ---------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | | [`awsIntegration`](./aws/) | ✓ | | ✓ | | | [`awsLambdaIntegration`](./awslambda/) | ✓ | | ✓ | | | [`consoleIntegration`](./console/) | ✓ | | | ✓ | @@ -55,3 +55,4 @@ | [`trpcMiddleware`](./trpc/) | | ✓ | ✓ | ✓ | | [`zodErrorsIntegration`](./zodErrors/) | | | | ✓ | | [`pinoIntegration`](./pino/) | | ✓ | | | +| [`serveTunnelIntegration`](./servetunnel/) | | | | | diff --git a/platform-includes/configuration/integrations/javascript.bun.mdx b/platform-includes/configuration/integrations/javascript.bun.mdx index 8dc423f3b61c6c..f0fc5778ce959b 100644 --- a/platform-includes/configuration/integrations/javascript.bun.mdx +++ b/platform-includes/configuration/integrations/javascript.bun.mdx @@ -1,7 +1,7 @@ ### Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | -| --------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | +| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | +| ---------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | | [`amqplibIntegration`](./amqplib/) | ✓ | | ✓ | | | [`consoleIntegration`](./console/) | ✓ | | | ✓ | | [`contextLinesIntegration`](./contextlines/) | ✓ | ✓ | | | @@ -47,14 +47,15 @@ | [`supabaseIntegration`](./supabase/) | | ✓ | ✓ | | | [`trpcMiddleware`](./trpc/) | | ✓ | ✓ | ✓ | | [`zodErrorsIntegration`](./zodErrors/) | | | | ✓ | +| [`serveTunnelIntegration`](./servetunnel/) | | | | | ### Bun Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | -| --------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | -| [`bunServerIntegration`](./bunserver/) | ✓ | ✓ | ✓ | | -| [`bunRuntimeMetricsIntegration`](./bunruntimemetrics/) | | | | ✓ | +| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | +| ------------------------------------------------------ | :--------------: | :--------: | :---------: | :--------------------: | +| [`bunServerIntegration`](./bunserver/) | ✓ | ✓ | ✓ | | +| [`bunRuntimeMetricsIntegration`](./bunruntimemetrics/) | | | | ✓ | diff --git a/platform-includes/configuration/integrations/javascript.cloudflare.mdx b/platform-includes/configuration/integrations/javascript.cloudflare.mdx index 99a2952b3905bc..03a90fa3226b7d 100644 --- a/platform-includes/configuration/integrations/javascript.cloudflare.mdx +++ b/platform-includes/configuration/integrations/javascript.cloudflare.mdx @@ -1,7 +1,7 @@ ### Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Cron** | **Additional Context** | -| --------------------------------------------------- | :--------------: | :--------: | :---------: | :------: | :--------------------: | +| | **Auto Enabled** | **Errors** | **Tracing** | **Cron** | **Additional Context** | +| ---------------------------------------------------- | :--------------: | :--------: | :---------: | :------: | :--------------------: | | [`dedupeIntegration`](./dedupe/) | ✓ | ✓ | | | | | [`eventFiltersIntegration`](./eventfilters/) | ✓ | ✓ | | | | | [`fetchIntegration`](./fetchIntegration/) | ✓ | ✓ | ✓ | | | @@ -14,6 +14,7 @@ | [`supabaseIntegration`](./supabase/) | | ✓ | ✓ | | | | [`instrumentPostgresJsSql`](./postgresjs/) | | | ✓ | | | | [`prismaIntegration`](./prisma/) | | | ✓ | | | +| [`serveTunnelIntegration`](./servetunnel/) | | | | | | [Mistral AI](./mistral/) is automatically instrumented when built with the Sentry Cloudflare Vite plugin. Without the plugin, use `instrumentMistralAiClient`. diff --git a/platform-includes/configuration/integrations/javascript.deno.mdx b/platform-includes/configuration/integrations/javascript.deno.mdx index 187f4353e9937e..e7b056b608096e 100644 --- a/platform-includes/configuration/integrations/javascript.deno.mdx +++ b/platform-includes/configuration/integrations/javascript.deno.mdx @@ -1,24 +1,25 @@ ### Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Cron** | **Additional Context** | -| --------------------------------------------------- | :--------------: | :--------: | :---------: | :------: | :--------------------: | -| [`breadcrumbsIntegration`](./breadcrumbs/) | ✓ | | | | ✓ | -| [`consoleIntegration`](./console/) | ✓ | | | | ✓ | -| [`contextLinesIntegration`](./contextlines/) | ✓ | ✓ | | | | -| [`dedupeIntegration`](./dedupe/) | ✓ | ✓ | | | | -| [`denoContextIntegration`](./denocontext/) | ✓ | | | | ✓ | -| [`eventFiltersIntegration`](./eventfilters/) | ✓ | ✓ | | | | -| [`functionToStringIntegration`](./functiontostring/) | ✓ | | | | | -| [`globalHandlersIntegration`](./globalhandlers/) | ✓ | ✓ | | | | -| [`linkedErrorsIntegration`](./linkederrors/) | ✓ | ✓ | | | | -| [`captureConsoleIntegration`](./captureconsole/) | | | | | ✓ | -| [`denoCronIntegration`](./denocron/) | | | | ✓ | | -| [`denoRuntimeMetricsIntegration`](./denoruntimemetrics/) | | | | | ✓ | -| [`extraErrorDataIntegration`](./extraerrordata/) | | | | | ✓ | -| [`rewriteFramesIntegration`](./rewriteframes/) | | ✓ | | | | -| [`supabaseIntegration`](./supabase/) | | ✓ | ✓ | | | -| [`instrumentPostgresJsSql`](./postgresjs/) | | | ✓ | | | -| [`trpcMiddleware`](./trpc/) | | ✓ | ✓ | | ✓ | -| [`zodErrorsIntegration`](./zodErrors/) | | | | ✓ | -| [`mistralAIIntegration`](./mistral/) | ✓ | | ✓ | | | -| [`typesafeIntegration`](./typesafe/) | ✓ | | ✓ | | | +| | **Auto Enabled** | **Errors** | **Tracing** | **Cron** | **Additional Context** | +| -------------------------------------------------------- | :--------------: | :--------: | :---------: | :------: | :--------------------: | +| [`breadcrumbsIntegration`](./breadcrumbs/) | ✓ | | | | ✓ | +| [`consoleIntegration`](./console/) | ✓ | | | | ✓ | +| [`contextLinesIntegration`](./contextlines/) | ✓ | ✓ | | | | +| [`dedupeIntegration`](./dedupe/) | ✓ | ✓ | | | | +| [`denoContextIntegration`](./denocontext/) | ✓ | | | | ✓ | +| [`eventFiltersIntegration`](./eventfilters/) | ✓ | ✓ | | | | +| [`functionToStringIntegration`](./functiontostring/) | ✓ | | | | | +| [`globalHandlersIntegration`](./globalhandlers/) | ✓ | ✓ | | | | +| [`linkedErrorsIntegration`](./linkederrors/) | ✓ | ✓ | | | | +| [`captureConsoleIntegration`](./captureconsole/) | | | | | ✓ | +| [`denoCronIntegration`](./denocron/) | | | | ✓ | | +| [`denoRuntimeMetricsIntegration`](./denoruntimemetrics/) | | | | | ✓ | +| [`extraErrorDataIntegration`](./extraerrordata/) | | | | | ✓ | +| [`rewriteFramesIntegration`](./rewriteframes/) | | ✓ | | | | +| [`supabaseIntegration`](./supabase/) | | ✓ | ✓ | | | +| [`instrumentPostgresJsSql`](./postgresjs/) | | | ✓ | | | +| [`trpcMiddleware`](./trpc/) | | ✓ | ✓ | | ✓ | +| [`zodErrorsIntegration`](./zodErrors/) | | | | ✓ | +| [`mistralAIIntegration`](./mistral/) | ✓ | | ✓ | | | +| [`typesafeIntegration`](./typesafe/) | ✓ | | ✓ | | | +| [`serveTunnelIntegration`](./servetunnel/) | | | | | | diff --git a/platform-includes/configuration/integrations/javascript.fastify.mdx b/platform-includes/configuration/integrations/javascript.fastify.mdx index 8392ee505ad662..954b7d37a6a4eb 100644 --- a/platform-includes/configuration/integrations/javascript.fastify.mdx +++ b/platform-includes/configuration/integrations/javascript.fastify.mdx @@ -1,7 +1,7 @@ ### Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | -| --------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | +| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | +| ---------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | | [`amqplibIntegration`](./amqplib/) | ✓ | | ✓ | | | [`consoleIntegration`](./console/) | ✓ | | | ✓ | | [`dedupeIntegration`](./dedupe/) | ✓ | ✓ | | | @@ -55,3 +55,4 @@ | [`trpcMiddleware`](./trpc/) | | ✓ | ✓ | ✓ | | [`zodErrorsIntegration`](./zodErrors/) | | | | ✓ | | [`pinoIntegration`](./pino/) | | ✓ | | | +| [`serveTunnelIntegration`](./servetunnel/) | | | | | diff --git a/platform-includes/configuration/integrations/javascript.gcp-functions.mdx b/platform-includes/configuration/integrations/javascript.gcp-functions.mdx index 461d95cdd4ff56..f58776e8a9ba92 100644 --- a/platform-includes/configuration/integrations/javascript.gcp-functions.mdx +++ b/platform-includes/configuration/integrations/javascript.gcp-functions.mdx @@ -1,7 +1,7 @@ ### Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | -| --------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | +| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | +| ---------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | | [`consoleIntegration`](./console/) | ✓ | | | ✓ | | [`contextLinesIntegration`](./contextlines/) | ✓ | ✓ | | | | [`dedupeIntegration`](./dedupe/) | ✓ | ✓ | | | @@ -56,3 +56,4 @@ | [`trpcMiddleware`](./trpc/) | | ✓ | ✓ | ✓ | | [`zodErrorsIntegration`](./zodErrors/) | | | | ✓ | | [`pinoIntegration`](./pino/) | | ✓ | | | +| [`serveTunnelIntegration`](./servetunnel/) | | | | | diff --git a/platform-includes/configuration/integrations/javascript.hapi.mdx b/platform-includes/configuration/integrations/javascript.hapi.mdx index c8712e2712a528..7390c815a8120b 100644 --- a/platform-includes/configuration/integrations/javascript.hapi.mdx +++ b/platform-includes/configuration/integrations/javascript.hapi.mdx @@ -1,7 +1,7 @@ ### Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | -| --------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | +| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | +| ---------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | | [`amqplibIntegration`](./amqplib/) | ✓ | | ✓ | | | [`consoleIntegration`](./console/) | ✓ | | | ✓ | | [`contextLinesIntegration`](./contextlines/) | ✓ | ✓ | | | @@ -56,3 +56,4 @@ | [`trpcMiddleware`](./trpc/) | | ✓ | ✓ | ✓ | | [`zodErrorsIntegration`](./zodErrors/) | | | | ✓ | | [`pinoIntegration`](./pino/) | | ✓ | | | +| [`serveTunnelIntegration`](./servetunnel/) | | | | | diff --git a/platform-includes/configuration/integrations/javascript.nestjs.mdx b/platform-includes/configuration/integrations/javascript.nestjs.mdx index 28e3d6cf083dd8..97fa6f1a46988d 100644 --- a/platform-includes/configuration/integrations/javascript.nestjs.mdx +++ b/platform-includes/configuration/integrations/javascript.nestjs.mdx @@ -1,7 +1,7 @@ ### Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | -| --------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | +| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | +| ---------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | | [`amqplibIntegration`](./amqplib/) | ✓ | | ✓ | | | [`consoleIntegration`](./console/) | ✓ | | | ✓ | | [`contextLinesIntegration`](./contextlines/) | ✓ | ✓ | | | @@ -56,3 +56,4 @@ | [`typesafeIntegration`](./typesafe/) | ✓ | | ✓ | | | [`zodErrorsIntegration`](./zodErrors/) | | | | ✓ | | [`pinoIntegration`](./pino/) | | ✓ | | | +| [`serveTunnelIntegration`](./servetunnel/) | | | | | diff --git a/platform-includes/configuration/integrations/javascript.node.mdx b/platform-includes/configuration/integrations/javascript.node.mdx index ac9a469467c0d1..3362ecd736fd49 100644 --- a/platform-includes/configuration/integrations/javascript.node.mdx +++ b/platform-includes/configuration/integrations/javascript.node.mdx @@ -1,7 +1,7 @@ ### Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | -| --------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | +| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | +| ---------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | | [`amqplibIntegration`](./amqplib/) | ✓ | | ✓ | | | [`anrIntegration`](./anr/) | | ✓ | | | | [`awsLambdaWebIntegration`](./awslambdaweb/) | ✓ | | | ✓ | @@ -56,3 +56,4 @@ | [`firebaseIntegration`](./firebase/) | ✓ | | ✓ | | | [`zodErrorsIntegration`](./zodErrors/) | | | | ✓ | | [`pinoIntegration`](./pino/) | | ✓ | | | +| [`serveTunnelIntegration`](./servetunnel/) | | | | | diff --git a/platform-includes/configuration/integrations/javascript.nuxt.mdx b/platform-includes/configuration/integrations/javascript.nuxt.mdx index 047fbcf8775fda..db4ec7cad18f79 100644 --- a/platform-includes/configuration/integrations/javascript.nuxt.mdx +++ b/platform-includes/configuration/integrations/javascript.nuxt.mdx @@ -8,8 +8,8 @@ Depending on whether an integration enhances the functionality of a particular r ### Common Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | -| --------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | +| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | +| ---------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | | [`dedupeIntegration`](./dedupe/) | ✓ | ✓ | | | | [`eventFiltersIntegration`](./eventfilters/) | ✓ | ✓ | | | | [`functionToStringIntegration`](./functiontostring/) | ✓ | | | | @@ -20,37 +20,37 @@ Depending on whether an integration enhances the functionality of a particular r ### Browser Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Replay** | **Additional Context** | -| ----------------------------------------------------- | :--------------: | :--------: | :---------: | :--------: | :--------------------: | -| [`breadcrumbsIntegration`](./breadcrumbs/) | ✓ | | | | ✓ | -| [`browserApiErrorsIntegration`](./browserapierrors/) | ✓ | ✓ | | | | -| [`browserSessionIntegration`](./browsersession/) | ✓ | | | | ✓ | -| [`browserTracingIntegration`](./browsertracing/) | ✓ | | ✓ | | ✓ | -| [`fetchStreamPerformanceIntegration`](./fetchstreamperformance/) | | | ✓ | | | -| [`interactionsIntegration`](./interactions/) | | | ✓ | | | -| [`userTimingIntegration`](./usertiming/) | | | ✓ | | | -| [`webVitalsIntegration`](./webvitals/) | With BrowserTracing | | ✓ | | | -| [`globalHandlersIntegration`](./globalhandlers/) | ✓ | ✓ | | | | -| [`httpContextIntegration`](./httpcontext/) | ✓ | | | | ✓ | -| [`browserProfilingIntegration`](./browserprofiling/) | | | ✓ | | | -| [`contextLinesIntegration`](./contextlines/) | | ✓ | | | | -| [`featureFlagsIntegration`](./featureflags/) | | | | | ✓ | -| [`graphqlClientIntegration`](./graphqlclient/) | | | | | ✓ | -| [`httpClientIntegration`](./httpclient/) | | ✓ | | | | -| [`launchDarklyIntegration`](./launchdarkly/) | | | | | ✓ | -| [`moduleMetadataIntegration`](./modulemetadata/) | | | | | ✓ | -| [`openFeatureIntegration`](./openfeature/) | | | | | ✓ | -| [`replayCanvasIntegration`](./replaycanvas/) | | | | ✓ | | -| [`replayIntegration`](./replay/) | | | | ✓ | ✓ | -| [`reportingObserverIntegration`](./reportingobserver/) | | ✓ | | | | -| [`statsigIntegration`](./statsig/) | | | | | ✓ | -| [`supabaseIntegration`](./supabase/) | | ✓ | ✓ | | | -| [`unleashIntegration`](./unleash/) | | | | | ✓ | +| | **Auto Enabled** | **Errors** | **Tracing** | **Replay** | **Additional Context** | +| ---------------------------------------------------------------- | :-----------------: | :--------: | :---------: | :--------: | :--------------------: | +| [`breadcrumbsIntegration`](./breadcrumbs/) | ✓ | | | | ✓ | +| [`browserApiErrorsIntegration`](./browserapierrors/) | ✓ | ✓ | | | | +| [`browserSessionIntegration`](./browsersession/) | ✓ | | | | ✓ | +| [`browserTracingIntegration`](./browsertracing/) | ✓ | | ✓ | | ✓ | +| [`fetchStreamPerformanceIntegration`](./fetchstreamperformance/) | | | ✓ | | | +| [`interactionsIntegration`](./interactions/) | | | ✓ | | | +| [`userTimingIntegration`](./usertiming/) | | | ✓ | | | +| [`webVitalsIntegration`](./webvitals/) | With BrowserTracing | | ✓ | | | +| [`globalHandlersIntegration`](./globalhandlers/) | ✓ | ✓ | | | | +| [`httpContextIntegration`](./httpcontext/) | ✓ | | | | ✓ | +| [`browserProfilingIntegration`](./browserprofiling/) | | | ✓ | | | +| [`contextLinesIntegration`](./contextlines/) | | ✓ | | | | +| [`featureFlagsIntegration`](./featureflags/) | | | | | ✓ | +| [`graphqlClientIntegration`](./graphqlclient/) | | | | | ✓ | +| [`httpClientIntegration`](./httpclient/) | | ✓ | | | | +| [`launchDarklyIntegration`](./launchdarkly/) | | | | | ✓ | +| [`moduleMetadataIntegration`](./modulemetadata/) | | | | | ✓ | +| [`openFeatureIntegration`](./openfeature/) | | | | | ✓ | +| [`replayCanvasIntegration`](./replaycanvas/) | | | | ✓ | | +| [`replayIntegration`](./replay/) | | | | ✓ | ✓ | +| [`reportingObserverIntegration`](./reportingobserver/) | | ✓ | | | | +| [`statsigIntegration`](./statsig/) | | | | | ✓ | +| [`supabaseIntegration`](./supabase/) | | ✓ | ✓ | | | +| [`unleashIntegration`](./unleash/) | | | | | ✓ | ### Node.js Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | -| --------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | +| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | +| ---------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | | [`amqplibIntegration`](./amqplib/) | ✓ | | ✓ | | | [`awsLambdaWebIntegration`](./awslambdaweb/) | ✓ | | | ✓ | | [`consoleIntegration`](./console/) | ✓ | | | ✓ | @@ -97,3 +97,4 @@ Depending on whether an integration enhances the functionality of a particular r | [`typesafeIntegration`](./typesafe/) | ✓ | | ✓ | | | [`zodErrorsIntegration`](./zodErrors/) | | | | ✓ | | [`pinoIntegration`](./pino/) | | ✓ | | | +| [`serveTunnelIntegration`](./servetunnel/) | | | | | diff --git a/platform-includes/configuration/integrations/javascript.remix.mdx b/platform-includes/configuration/integrations/javascript.remix.mdx index 651fe9ea347ffa..b90d1bd450b58d 100644 --- a/platform-includes/configuration/integrations/javascript.remix.mdx +++ b/platform-includes/configuration/integrations/javascript.remix.mdx @@ -8,8 +8,8 @@ Depending on whether an integration enhances the functionality of a particular r ### Common Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | -| --------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | +| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | +| ---------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | | [`dedupeIntegration`](./dedupe/) | ✓ | ✓ | | | | [`eventFiltersIntegration`](./eventfilters/) | ✓ | ✓ | | | | [`functionToStringIntegration`](./functiontostring/) | ✓ | | | | @@ -20,37 +20,37 @@ Depending on whether an integration enhances the functionality of a particular r ### Browser Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Replay** | **Additional Context** | -| ----------------------------------------------------- | :--------------: | :--------: | :---------: | :--------: | :--------------------: | -| [`breadcrumbsIntegration`](./breadcrumbs/) | ✓ | | | | ✓ | -| [`browserApiErrorsIntegration`](./browserapierrors/) | ✓ | ✓ | | | | -| [`browserSessionIntegration`](./browsersession/) | ✓ | | | | ✓ | -| [`browserTracingIntegration`](./browsertracing/) | ✓ | | ✓ | | ✓ | -| [`fetchStreamPerformanceIntegration`](./fetchstreamperformance/) | | | ✓ | | | -| [`interactionsIntegration`](./interactions/) | | | ✓ | | | -| [`userTimingIntegration`](./usertiming/) | | | ✓ | | | -| [`webVitalsIntegration`](./webvitals/) | With BrowserTracing | | ✓ | | | -| [`globalHandlersIntegration`](./globalhandlers/) | ✓ | ✓ | | | | -| [`httpContextIntegration`](./httpcontext/) | ✓ | | | | ✓ | -| [`browserProfilingIntegration`](./browserprofiling/) | | | ✓ | | | -| [`contextLinesIntegration`](./contextlines/) | | ✓ | | | | -| [`featureFlagsIntegration`](./featureflags/) | | | | | ✓ | -| [`graphqlClientIntegration`](./graphqlclient/) | | | | | ✓ | -| [`httpClientIntegration`](./httpclient/) | | ✓ | | | | -| [`launchDarklyIntegration`](./launchdarkly/) | | | | | ✓ | -| [`moduleMetadataIntegration`](./modulemetadata/) | | | | | ✓ | -| [`openFeatureIntegration`](./openfeature/) | | | | | ✓ | -| [`replayCanvasIntegration`](./replaycanvas/) | | | | ✓ | | -| [`replayIntegration`](./replay/) | | | | ✓ | ✓ | -| [`reportingObserverIntegration`](./reportingobserver/) | | ✓ | | | | -| [`statsigIntegration`](./statsig/) | | | | | ✓ | -| [`supabaseIntegration`](./supabase/) | | ✓ | ✓ | | | -| [`unleashIntegration`](./unleash/) | | | | | ✓ | +| | **Auto Enabled** | **Errors** | **Tracing** | **Replay** | **Additional Context** | +| ---------------------------------------------------------------- | :-----------------: | :--------: | :---------: | :--------: | :--------------------: | +| [`breadcrumbsIntegration`](./breadcrumbs/) | ✓ | | | | ✓ | +| [`browserApiErrorsIntegration`](./browserapierrors/) | ✓ | ✓ | | | | +| [`browserSessionIntegration`](./browsersession/) | ✓ | | | | ✓ | +| [`browserTracingIntegration`](./browsertracing/) | ✓ | | ✓ | | ✓ | +| [`fetchStreamPerformanceIntegration`](./fetchstreamperformance/) | | | ✓ | | | +| [`interactionsIntegration`](./interactions/) | | | ✓ | | | +| [`userTimingIntegration`](./usertiming/) | | | ✓ | | | +| [`webVitalsIntegration`](./webvitals/) | With BrowserTracing | | ✓ | | | +| [`globalHandlersIntegration`](./globalhandlers/) | ✓ | ✓ | | | | +| [`httpContextIntegration`](./httpcontext/) | ✓ | | | | ✓ | +| [`browserProfilingIntegration`](./browserprofiling/) | | | ✓ | | | +| [`contextLinesIntegration`](./contextlines/) | | ✓ | | | | +| [`featureFlagsIntegration`](./featureflags/) | | | | | ✓ | +| [`graphqlClientIntegration`](./graphqlclient/) | | | | | ✓ | +| [`httpClientIntegration`](./httpclient/) | | ✓ | | | | +| [`launchDarklyIntegration`](./launchdarkly/) | | | | | ✓ | +| [`moduleMetadataIntegration`](./modulemetadata/) | | | | | ✓ | +| [`openFeatureIntegration`](./openfeature/) | | | | | ✓ | +| [`replayCanvasIntegration`](./replaycanvas/) | | | | ✓ | | +| [`replayIntegration`](./replay/) | | | | ✓ | ✓ | +| [`reportingObserverIntegration`](./reportingobserver/) | | ✓ | | | | +| [`statsigIntegration`](./statsig/) | | | | | ✓ | +| [`supabaseIntegration`](./supabase/) | | ✓ | ✓ | | | +| [`unleashIntegration`](./unleash/) | | | | | ✓ | ### Node.js Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | -| --------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | +| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | +| ---------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | | [`amqplibIntegration`](./amqplib/) | ✓ | | ✓ | | | [`awsLambdaWebIntegration`](./awslambdaweb/) | ✓ | | | ✓ | | [`consoleIntegration`](./console/) | ✓ | | | ✓ | @@ -97,3 +97,4 @@ Depending on whether an integration enhances the functionality of a particular r | [`typesafeIntegration`](./typesafe/) | ✓ | | ✓ | | | [`zodErrorsIntegration`](./zodErrors/) | | | | ✓ | | [`pinoIntegration`](./pino/) | | ✓ | | | +| [`serveTunnelIntegration`](./servetunnel/) | | | | | diff --git a/platform-includes/configuration/integrations/javascript.sveltekit.mdx b/platform-includes/configuration/integrations/javascript.sveltekit.mdx index 9bdc2edaba282b..d9ab94bd4c61ee 100644 --- a/platform-includes/configuration/integrations/javascript.sveltekit.mdx +++ b/platform-includes/configuration/integrations/javascript.sveltekit.mdx @@ -8,8 +8,8 @@ Depending on whether an integration enhances the functionality of a particular r ### Common Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | -| --------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | +| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | +| ---------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | | [`dedupeIntegration`](./dedupe/) | ✓ | ✓ | | | | [`eventFiltersIntegration`](./eventfilters/) | ✓ | ✓ | | | | [`functionToStringIntegration`](./functiontostring/) | ✓ | | | | @@ -20,36 +20,36 @@ Depending on whether an integration enhances the functionality of a particular r ### Browser Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Replay** | **Additional Context** | -| ----------------------------------------------------- | :--------------: | :--------: | :---------: | :--------: | :--------------------: | -| [`breadcrumbsIntegration`](./breadcrumbs/) | ✓ | | | | ✓ | -| [`browserApiErrorsIntegration`](./browserapierrors/) | ✓ | ✓ | | | | -| [`browserSessionIntegration`](./browsersession/) | ✓ | | | | ✓ | -| [`browserTracingIntegration`](./browsertracing/) | ✓ | | ✓ | | ✓ | -| [`fetchStreamPerformanceIntegration`](./fetchstreamperformance/) | | | ✓ | | | -| [`interactionsIntegration`](./interactions/) | | | ✓ | | | -| [`userTimingIntegration`](./usertiming/) | | | ✓ | | | -| [`webVitalsIntegration`](./webvitals/) | With BrowserTracing | | ✓ | | | -| [`globalHandlersIntegration`](./globalhandlers/) | ✓ | ✓ | | | | -| [`httpContextIntegration`](./httpcontext/) | ✓ | | | | ✓ | -| [`browserProfilingIntegration`](./browserprofiling/) | | | ✓ | | | -| [`contextLinesIntegration`](./contextlines/) | | ✓ | | | | -| [`featureFlagsIntegration`](./featureflags/) | | | | | ✓ | -| [`graphqlClientIntegration`](./graphqlclient/) | | | | | ✓ | -| [`httpClientIntegration`](./httpclient/) | | ✓ | | | | -| [`launchDarklyIntegration`](./launchdarkly/) | | | | | ✓ | -| [`moduleMetadataIntegration`](./modulemetadata/) | | | | | ✓ | -| [`openFeatureIntegration`](./openfeature/) | | | | | ✓ | -| [`replayCanvasIntegration`](./replaycanvas/) | | | | ✓ | | -| [`replayIntegration`](./replay/) | | | | ✓ | ✓ | -| [`reportingObserverIntegration`](./reportingobserver/) | | ✓ | | | | -| [`statsigIntegration`](./statsig/) | | | | | ✓ | -| [`unleashIntegration`](./unleash/) | | | | | ✓ | +| | **Auto Enabled** | **Errors** | **Tracing** | **Replay** | **Additional Context** | +| ---------------------------------------------------------------- | :-----------------: | :--------: | :---------: | :--------: | :--------------------: | +| [`breadcrumbsIntegration`](./breadcrumbs/) | ✓ | | | | ✓ | +| [`browserApiErrorsIntegration`](./browserapierrors/) | ✓ | ✓ | | | | +| [`browserSessionIntegration`](./browsersession/) | ✓ | | | | ✓ | +| [`browserTracingIntegration`](./browsertracing/) | ✓ | | ✓ | | ✓ | +| [`fetchStreamPerformanceIntegration`](./fetchstreamperformance/) | | | ✓ | | | +| [`interactionsIntegration`](./interactions/) | | | ✓ | | | +| [`userTimingIntegration`](./usertiming/) | | | ✓ | | | +| [`webVitalsIntegration`](./webvitals/) | With BrowserTracing | | ✓ | | | +| [`globalHandlersIntegration`](./globalhandlers/) | ✓ | ✓ | | | | +| [`httpContextIntegration`](./httpcontext/) | ✓ | | | | ✓ | +| [`browserProfilingIntegration`](./browserprofiling/) | | | ✓ | | | +| [`contextLinesIntegration`](./contextlines/) | | ✓ | | | | +| [`featureFlagsIntegration`](./featureflags/) | | | | | ✓ | +| [`graphqlClientIntegration`](./graphqlclient/) | | | | | ✓ | +| [`httpClientIntegration`](./httpclient/) | | ✓ | | | | +| [`launchDarklyIntegration`](./launchdarkly/) | | | | | ✓ | +| [`moduleMetadataIntegration`](./modulemetadata/) | | | | | ✓ | +| [`openFeatureIntegration`](./openfeature/) | | | | | ✓ | +| [`replayCanvasIntegration`](./replaycanvas/) | | | | ✓ | | +| [`replayIntegration`](./replay/) | | | | ✓ | ✓ | +| [`reportingObserverIntegration`](./reportingobserver/) | | ✓ | | | | +| [`statsigIntegration`](./statsig/) | | | | | ✓ | +| [`unleashIntegration`](./unleash/) | | | | | ✓ | ### Node.js Integrations -| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | -| --------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | +| | **Auto Enabled** | **Errors** | **Tracing** | **Additional Context** | +| ---------------------------------------------------------- | :--------------: | :--------: | :---------: | :--------------------: | | [`amqplibIntegration`](./amqplib/) | ✓ | | ✓ | | | [`awsLambdaWebIntegration`](./awslambdaweb/) | ✓ | | | ✓ | | [`consoleIntegration`](./console/) | ✓ | | | ✓ | @@ -97,3 +97,4 @@ Depending on whether an integration enhances the functionality of a particular r | [`typesafeIntegration`](./typesafe/) | ✓ | | ✓ | | | [`zodErrorsIntegration`](./zodErrors/) | | | | ✓ | | [`pinoIntegration`](./pino/) | | ✓ | | | +| [`serveTunnelIntegration`](./servetunnel/) | | | | | diff --git a/platform-includes/getting-started-complete/javascript.mdx b/platform-includes/getting-started-complete/javascript.mdx index 2730660e61b65e..1b1b1c3960a737 100644 --- a/platform-includes/getting-started-complete/javascript.mdx +++ b/platform-includes/getting-started-complete/javascript.mdx @@ -205,7 +205,7 @@ At this point, you should have integrated Sentry into your JavaScript applicatio - Explore [practical guides](/get-started/guides/) on what to monitor, log, track, and investigate after setup - Extend Sentry to your backend using one of our [SDKs](/) -- Set up tunneling to avoid ad blockers from blocking Sentry events +- Set up tunneling to avoid ad blockers from blocking Sentry events diff --git a/platform-includes/getting-started-node/javascript.nextjs.mdx b/platform-includes/getting-started-node/javascript.nextjs.mdx index 1ce1e7b97b13fd..09ba61db96a2db 100644 --- a/platform-includes/getting-started-node/javascript.nextjs.mdx +++ b/platform-includes/getting-started-node/javascript.nextjs.mdx @@ -97,7 +97,7 @@ At this point, you should have integrated Sentry into your Next.js application. - Explore [practical guides](/get-started/guides/) on what to monitor, log, track, and investigate after setup - Set up Agent Tracing to monitor agents built with the Vercel AI SDK, LangChain, and more -- Set up tunneling to avoid ad blockers blocking Sentry events, if you didn't enable it in the wizard +- Set up tunneling to avoid ad blockers blocking Sentry events, if you didn't enable it in the wizard diff --git a/platform-includes/getting-started-tunneling/javascript.astro.mdx b/platform-includes/getting-started-tunneling/javascript.astro.mdx index b99782cc63ad64..afe1a499ac32a3 100644 --- a/platform-includes/getting-started-tunneling/javascript.astro.mdx +++ b/platform-includes/getting-started-tunneling/javascript.astro.mdx @@ -16,4 +16,4 @@ Sentry.init({ }); ``` -Learn more about the options in `serveTunnelIntegration`. To set up your own tunnel endpoint instead, see our Troubleshooting page. +Learn more about the options in `serveTunnelIntegration`. To set up your own tunnel endpoint instead, see the Tunneling page. diff --git a/platform-includes/getting-started-tunneling/javascript.mdx b/platform-includes/getting-started-tunneling/javascript.mdx index 12c290d10e01f9..886a6e9ce67277 100644 --- a/platform-includes/getting-started-tunneling/javascript.mdx +++ b/platform-includes/getting-started-tunneling/javascript.mdx @@ -1,6 +1,6 @@ You can prevent ad blockers from blocking Sentry events using tunneling. Use the `tunnel` option in `Sentry.init` to add an API endpoint in your application that forwards Sentry events to Sentry servers. -This will send all events to the `tunnel` endpoint. However, the events need to be parsed and redirected to Sentry, so you'll need to do additional configuration on the server. The endpoint must forward only the request body to Sentry and accept only your own DSN. You can find a detailed explanation on how to do this on our Troubleshooting page. +This will send all events to the `tunnel` endpoint. However, the events need to be parsed and redirected to Sentry, so you'll need to do additional configuration on the server. The endpoint must forward only the request body to Sentry and accept only your own DSN. You can find a detailed explanation on how to do this on the Tunneling page. ```javascript {3} Sentry.init({ diff --git a/platform-includes/getting-started-tunneling/javascript.nuxt.mdx b/platform-includes/getting-started-tunneling/javascript.nuxt.mdx index ba7feb068a7f04..11d401510531d4 100644 --- a/platform-includes/getting-started-tunneling/javascript.nuxt.mdx +++ b/platform-includes/getting-started-tunneling/javascript.nuxt.mdx @@ -16,4 +16,4 @@ Sentry.init({ }); ``` -Learn more about the options in `serveTunnelIntegration`. To set up your own tunnel endpoint instead, see our Troubleshooting page. +Learn more about the options in `serveTunnelIntegration`. To set up your own tunnel endpoint instead, see the Tunneling page. diff --git a/platform-includes/getting-started-tunneling/javascript.sveltekit.mdx b/platform-includes/getting-started-tunneling/javascript.sveltekit.mdx index db41cd6335b34c..43e69cd3c094bf 100644 --- a/platform-includes/getting-started-tunneling/javascript.sveltekit.mdx +++ b/platform-includes/getting-started-tunneling/javascript.sveltekit.mdx @@ -16,4 +16,4 @@ Sentry.init({ }); ``` -Learn more about the options in `serveTunnelIntegration`. To set up your own tunnel endpoint instead, see our Troubleshooting page. +Learn more about the options in `serveTunnelIntegration`. To set up your own tunnel endpoint instead, see the Tunneling page. From b9425c4b54903328f27ac46d274dbf8d723fbe06 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Wed, 11 Nov 2026 15:27:39 +0100 Subject: [PATCH 14/17] docs(js): Document the managed tunnel served by the runtime Rewrites the tunneling docs around tunnel: true and serveTunnelIntegration, which the server SDKs answer in the runtime from 11.12.0 (11.13.0 for Next.js). The manual setup covers handleTunnelRequest and endpoints on other servers. The deprecated tunnelRoute options in Next.js and TanStack Start now point to the new setup, and the tunnel diagram is an SVG. --- .../common/configuration/img/tunnel.png | Bin 82168 -> 0 bytes .../integrations/servetunnel.mdx | 158 +--- .../common/configuration/options.mdx | 30 +- .../common/configuration/tunneling.mdx | 251 +++--- .../common/troubleshooting/tunnel.excalidraw | 733 ------------------ .../guides/cordova/configuration/options.mdx | 2 +- .../nextjs/configuration/build/index.mdx | 80 +- .../guides/nextjs/manual-setup/index.mdx | 27 +- .../nextjs/manual-setup/webpack-setup.mdx | 28 +- .../features/createSentryTunnelRoute.mdx | 13 +- .../features/sentryTanstackStart.mdx | 59 +- .../manual-setup/index.mdx | 23 +- .../tunnel-browser-init/javascript.astro.mdx | 6 + .../tunnel-browser-init/javascript.mdx | 6 + .../tunnel-browser-init/javascript.nextjs.mdx | 6 + .../tunnel-browser-init/javascript.nuxt.mdx | 6 + .../javascript.solidstart.mdx | 6 + .../javascript.sveltekit.mdx | 6 + .../javascript.tanstackstart-react.mdx | 6 + .../tunnel-manual-route/javascript.bun.mdx | 7 + .../javascript.cloudflare.mdx | 7 + .../tunnel-manual-route/javascript.deno.mdx | 7 + .../tunnel-manual-route/javascript.elysia.mdx | 7 + .../tunnel-manual-route/javascript.mdx | 43 + .../tunnel-manual-route/javascript.nextjs.mdx | 7 + .../javascript.sveltekit.mdx | 7 + .../tunnel-server-init/javascript.astro.mdx | 6 + .../tunnel-server-init/javascript.mdx | 6 + .../tunnel-server-init/javascript.nextjs.mdx | 15 + .../tunnel-server-init/javascript.nuxt.mdx | 6 + .../javascript.solidstart.mdx | 20 + .../javascript.sveltekit.mdx | 6 + .../javascript.tanstackstart-react.mdx | 6 + .../javascript.astro.mdx | 16 +- .../getting-started-tunneling/javascript.mdx | 8 +- .../javascript.nuxt.mdx | 16 +- .../javascript.sveltekit.mdx | 16 +- public/img/tunnel-flow.svg | 1 + 38 files changed, 406 insertions(+), 1247 deletions(-) delete mode 100644 docs/platforms/javascript/common/configuration/img/tunnel.png delete mode 100644 docs/platforms/javascript/common/troubleshooting/tunnel.excalidraw create mode 100644 platform-includes/configuration/tunnel-browser-init/javascript.astro.mdx create mode 100644 platform-includes/configuration/tunnel-browser-init/javascript.mdx create mode 100644 platform-includes/configuration/tunnel-browser-init/javascript.nextjs.mdx create mode 100644 platform-includes/configuration/tunnel-browser-init/javascript.nuxt.mdx create mode 100644 platform-includes/configuration/tunnel-browser-init/javascript.solidstart.mdx create mode 100644 platform-includes/configuration/tunnel-browser-init/javascript.sveltekit.mdx create mode 100644 platform-includes/configuration/tunnel-browser-init/javascript.tanstackstart-react.mdx create mode 100644 platform-includes/configuration/tunnel-manual-route/javascript.bun.mdx create mode 100644 platform-includes/configuration/tunnel-manual-route/javascript.cloudflare.mdx create mode 100644 platform-includes/configuration/tunnel-manual-route/javascript.deno.mdx create mode 100644 platform-includes/configuration/tunnel-manual-route/javascript.elysia.mdx create mode 100644 platform-includes/configuration/tunnel-manual-route/javascript.mdx create mode 100644 platform-includes/configuration/tunnel-manual-route/javascript.nextjs.mdx create mode 100644 platform-includes/configuration/tunnel-manual-route/javascript.sveltekit.mdx create mode 100644 platform-includes/configuration/tunnel-server-init/javascript.astro.mdx create mode 100644 platform-includes/configuration/tunnel-server-init/javascript.mdx create mode 100644 platform-includes/configuration/tunnel-server-init/javascript.nextjs.mdx create mode 100644 platform-includes/configuration/tunnel-server-init/javascript.nuxt.mdx create mode 100644 platform-includes/configuration/tunnel-server-init/javascript.solidstart.mdx create mode 100644 platform-includes/configuration/tunnel-server-init/javascript.sveltekit.mdx create mode 100644 platform-includes/configuration/tunnel-server-init/javascript.tanstackstart-react.mdx create mode 100644 public/img/tunnel-flow.svg diff --git a/docs/platforms/javascript/common/configuration/img/tunnel.png b/docs/platforms/javascript/common/configuration/img/tunnel.png deleted file mode 100644 index e0417e91203bdc4c754316a87b55dc349f23928b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 82168 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�BA5&DLqsl1ij_$6fvPoKe7qxtr;qcI;_01yGlY|3_M@lFX624&6UEy|+P0sR zAHus6X7ZaH9p1M6!+(=5zJ^8fo}XS{@Ce!{qu^0fB163!;a_5Z?;Yk&PC4K=;mckn+~ zv3&>4bpCrPIjq8F2`em0%Hq^KT{=iq9xs>{;T$PXs?YuJt&a1Wv5S8d4o>@lYyVT9 z;#Ny63qt3G+B4w1evKE|V+h|YvQ`5?eReKBIW zB-XPi`ycitls{(Xunpi`_VQ*%iS-g7O8D=lDsM1Np%@u$;9AG|_hiB7)?^17RcpOZ zGF%mAYdBfN4UFJWu=C!&GIxI3ZI==*Z;5~kU6ZBHj?gy{cUhBNhs|6CHLtOmS?=6d zU{?FxBW@f*!EsQA(7nK%D)wzBRJv+Ab|fYM*}xW|-