diff --git a/src/components/Products/Slides/TracesComparisonTable.tsx b/src/components/Products/Slides/TracesComparisonTable.tsx
new file mode 100644
index 000000000000..57182473e817
--- /dev/null
+++ b/src/components/Products/Slides/TracesComparisonTable.tsx
@@ -0,0 +1,108 @@
+import React from 'react'
+import OSTable from 'components/OSTable'
+import Logo from 'components/Logo'
+import { useApp } from '../../../context/App'
+
+// Small building blocks for the comparison cells
+const Check = () => ✓
+const Cross = () => ✗
+const CheckWith = ({ note }: { note: string }) => (
+
+ ✓ ({note})
+
+)
+
+const CompetitorName = ({ children }: { children: React.ReactNode }) => (
+ {children}
+)
+
+export default function TracesComparisonTable(): JSX.Element {
+ const { siteSettings } = useApp()
+ const isDark = siteSettings?.theme === 'dark'
+
+ const columns = [
+ { name: '', align: 'left' as const, width: 'minmax(180px, 1.6fr)' },
+ {
+ name: Better Stack,
+ align: 'center' as const,
+ width: '1fr',
+ },
+ {
+ name: Datadog,
+ align: 'center' as const,
+ width: '1fr',
+ },
+ {
+ name: Sentry,
+ align: 'center' as const,
+ width: '1fr',
+ },
+ {
+ name: ,
+ align: 'center' as const,
+ width: '1fr',
+ },
+ ]
+
+ // Section header row spanning the full width
+ const sectionRow = (label: string) => ({
+ cells: [
+ {
+ content: {label},
+ className: 'col-span-full !items-start text-left bg-accent font-bold',
+ },
+ ],
+ })
+
+ // Feature row: label + one cell per competitor (Better Stack, Datadog, Sentry, PostHog)
+ const featureRow = (label: React.ReactNode, values: React.ReactNode[]) => ({
+ cells: [{ content: label, className: '!items-start text-left font-semibold' }, ...values.map((content) => ({ content }))],
+ })
+
+ const rows = [
+ sectionRow('Tracing'),
+ featureRow('Distributed trace waterfall', [, , , ]),
+ featureRow('Service / dependency map', [, , , ]),
+ featureRow('Code-level profiling (flame graphs)', [, , , ]),
+ featureRow('Sampling & retention controls', [
+ ,
+ ,
+ Head sampling only,
+ Via OTel SDK,
+ ]),
+ sectionRow('Standards & setup'),
+ featureRow('Native OpenTelemetry ingestion', [
+ ,
+ ,
+ ,
+ ,
+ ]),
+ featureRow('No proprietary SDK required', [, Partial, Partial, ]),
+ featureRow('Instrumentation', [
+ eBPF,
+ Agent,
+ SDK,
+ OpenTelemetry,
+ ]),
+ sectionRow('One platform'),
+ featureRow('Errors, logs & session replay alongside traces', [, , , ]),
+ sectionRow('AI & self-driving'),
+ featureRow('AI opens a code fix PR', [, , , ]),
+ featureRow('Fix a bug and open a PR from Slack', [, , , ]),
+ sectionRow('Pricing & licensing'),
+ featureRow('Pricing model', [
+ Per GB ingested,
+ Per host + per span,
+ Per span,
+ Usage-based, no per-host fee,
+ ]),
+ featureRow('Free tier', [, Trial only, , ]),
+ featureRow('Open source', [, , Source-available (FSL), ]),
+ ]
+
+ return (
+
+
+
+ )
+}
diff --git a/src/components/Products/Slides/TracesSelfHealing.tsx b/src/components/Products/Slides/TracesSelfHealing.tsx
new file mode 100644
index 000000000000..1c5b66013a0e
--- /dev/null
+++ b/src/components/Products/Slides/TracesSelfHealing.tsx
@@ -0,0 +1,106 @@
+import React from 'react'
+import TabbedCarousel from 'components/TabbedCarousel'
+import type { TabbedCarouselTab } from 'components/TabbedCarousel'
+
+// "Your product, fixing itself" section. Uses the same tabbed carousel layout as
+// the "How a product improves itself" section on /self-driving. The section
+// headline + intro stay constant; each tab swaps in its own copy. Per-tab
+// illustrations will be added later.
+const SELF_HEALING_HEADLINE = 'Your product, fixing itself'
+const SELF_HEALING_INTRO =
+ 'Point a scout at your slowest traces. It runs on a schedule, and when a query or endpoint starts creeping up, it reports the regression and kicks off an investigation into why.'
+const TRACE_IS_THE_EVIDENCE =
+ 'Throughout, the trace is the evidence: what the scout watches to raise the alarm, and what the agent reads to land the fix on the right line.'
+
+const steps: {
+ value: string
+ label: string
+ color: string
+ activeText: string
+ progressBar: string
+ copy: string
+ image?: string
+}[] = [
+ {
+ value: 'scout',
+ label: 'Scout',
+ color: 'bg-blue',
+ activeText: 'text-white',
+ progressBar: 'bg-white shadow-[0_0_6px_2px_rgba(0,0,0,0.2)]',
+ copy: 'Turn on the APM scout. It watches latency and request volume per service, on a schedule, and files any regression as a report.',
+ image: 'https://res.cloudinary.com/dmukukwp6/image/upload/Scout_troop_Mock_b59fadf110.png',
+ },
+ {
+ value: 'signal',
+ label: 'Signal',
+ color: 'bg-red',
+ activeText: 'text-white',
+ progressBar: 'bg-white shadow-[0_0_6px_2px_rgba(0,0,0,0.2)]',
+ copy: 'Latency on GET /api/checkout starts climbing. The scout catches it and files a report. Nobody had to notice first.',
+ image: 'https://res.cloudinary.com/dmukukwp6/image/upload/Post_Hog_Inbox_Mock_6485bb0963.png',
+ },
+ {
+ value: 'investigate',
+ label: 'Investigate',
+ color: 'bg-purple',
+ activeText: 'text-white',
+ progressBar: 'bg-white shadow-[0_0_6px_2px_rgba(0,0,0,0.2)]',
+ copy: 'The agent pulls the slow traces, lines them up against the fast ones, and finds the span they share: the inventory service firing one DB query per cart item. Classic N+1.',
+ image: 'https://res.cloudinary.com/dmukukwp6/image/upload/Report_Investigate_Mock_2_76290ef07e.png',
+ },
+ {
+ value: 'pr',
+ label: 'PR',
+ color: 'bg-green',
+ activeText: 'text-white',
+ progressBar: 'bg-white shadow-[0_0_6px_2px_rgba(0,0,0,0.2)]',
+ copy: 'The agent fixes the exact query it located and opens a pull request, wired up with the instrumentation to measure whether it worked.',
+ image: 'https://res.cloudinary.com/dmukukwp6/image/upload/Git_Hub_PR_Mock_1_8c4240dc83.png',
+ },
+ {
+ value: 'merge',
+ label: 'Merge',
+ color: 'bg-yellow',
+ activeText: 'text-black',
+ progressBar: 'bg-black/70 shadow-[0_0_6px_2px_rgba(255,255,255,0.4)]',
+ copy: 'You review the diff in your Inbox and hit merge. Nothing ships until you do.',
+ image: 'https://res.cloudinary.com/dmukukwp6/image/upload/Git_Hub_PR_Merged_Mock_1_5b9cf8f4b5.png',
+ },
+]
+
+const TabPanel = ({ title, children, image }: { title: string; children: React.ReactNode; image?: string }) => (
+
+
{children}
+ {image && (
+

+ )}
+
+)
+
+const tabs: TabbedCarouselTab[] = steps.map((step) => ({
+ value: step.value,
+ label: step.label,
+ color: step.color,
+ activeText: step.activeText,
+ progressBar: step.progressBar,
+ content: (
+
+ {step.copy}
+
+ ),
+}))
+
+export default function TracesSelfHealing(): JSX.Element {
+ return (
+
+
+
{SELF_HEALING_HEADLINE}
+
{SELF_HEALING_INTRO}
+
+
+
+
{TRACE_IS_THE_EVIDENCE}
+
+
+ )
+}
diff --git a/src/components/Products/Slides/TracesSlackCallout.tsx b/src/components/Products/Slides/TracesSlackCallout.tsx
new file mode 100644
index 000000000000..4e57ff24e6d7
--- /dev/null
+++ b/src/components/Products/Slides/TracesSlackCallout.tsx
@@ -0,0 +1,50 @@
+import React from 'react'
+import { IconCheck } from '@posthog/icons'
+import { CallToAction } from 'components/CallToAction'
+
+const checklist = [
+ 'Find which span made a request slow',
+ 'Follow a failure across services',
+ 'Catch p99 you can’t reproduce',
+ 'Surface async work that usually hides',
+ 'Turn a trace into a PR',
+]
+
+export default function TracesSlackCallout(): JSX.Element {
+ return (
+
+
+
+
+
+ PostHog in Slack
+
+
+ Latency? @PostHog
+
+
+ Mention @PostHog on a slow endpoint. It pulls the traces, finds the span eating
+ your time, and opens a PR right there in the thread. You review without leaving the channel.
+
+
+ About the Slack app
+
+
+
+ {checklist.map((item) => (
+ -
+
+ {item}
+
+ ))}
+
+
+

+
+
+ )
+}
diff --git a/src/hooks/productData/traces.tsx b/src/hooks/productData/traces.tsx
new file mode 100644
index 000000000000..47a5c222fae5
--- /dev/null
+++ b/src/hooks/productData/traces.tsx
@@ -0,0 +1,159 @@
+import React from 'react'
+import { IconGanttChart, IconWarning, IconRewindPlay, IconFunnels } from '@posthog/icons'
+
+// "Catnip for agents" cards. Mirrors the "Automatic PostHog instrumentation"
+// section on the /code page: a 2-column grid of icon + title + description cards.
+// The trace card is highlighted since it's the one that hands over the location.
+const catnipSignals = [
+ {
+ icon: IconWarning,
+ color: 'text-white/60',
+ title: 'Error tracking',
+ description: 'A request broke.',
+ },
+ {
+ icon: IconRewindPlay,
+ color: 'text-white/60',
+ title: 'Session replay',
+ description: 'A user waited, then left.',
+ },
+ {
+ icon: IconFunnels,
+ color: 'text-white/60',
+ title: 'Funnel drop-off',
+ description: 'Checkout conversion fell.',
+ },
+ {
+ icon: IconGanttChart,
+ color: 'text-white',
+ title: 'A trace',
+ description: '“Checkout took 3.2s, and 2.8s of it was waiting on an N+1 query in the inventory service.”',
+ highlight: true,
+ },
+]
+
+export const traces = {
+ name: 'Traces',
+ Icon: IconGanttChart,
+ description: 'Distributed tracing that goes straight to the line that broke',
+ handle: 'traces',
+ type: 'traces',
+ slug: 'traces',
+ color: 'blue',
+ colorSecondary: 'sky-blue',
+ category: 'product_engineering',
+ status: 'beta',
+ seo: {
+ title: 'Traces – Distributed tracing with PostHog',
+ description:
+ 'Traces pinpoint the exact query that broke. PostHog Code opens the PR and sends it to your Inbox. You hit merge. That’s the whole job.',
+ },
+ overview: {
+ title: 'Straight to the line that broke',
+ description:
+ 'A trace pinpoints the slow query, failed API call, or service that broke the request. The agent traces it to the right line of code, opens a PR with the fix, and sends it to your inbox. You hit merge. That’s the whole job.',
+ textColor: 'text-white',
+ layout: 'stacked',
+ },
+ screenshots: {
+ overview: {
+ src: 'https://res.cloudinary.com/dmukukwp6/image/upload/Group_144145_2a408da79b.png',
+ alt: 'Traces overview',
+ classes: 'max-w-5xl mt-auto',
+ imgClasses: 'rounded-md',
+ classesMobile: '',
+ imgClassesMobile: '',
+ },
+ },
+ features: [
+ // Standalone split slide: copy on the left, screenshot on the right.
+ // Image pushed lower (mt-12) with rounded corners, matching the Logs
+ // "Full stack context" slide.
+ {
+ title: 'Trace waterfall',
+ handle: 'waterfall',
+ template: 'splitImage',
+ headline: 'Every span of a request, in one waterfall',
+ description:
+ '- One request becomes a tree of spans across every service, queue, and third-party call it touches.
- Follow the trace_id to see where the time went and which span actually broke – even across async boundaries, where stack traces give up.
- Built on OpenTelemetry: no proprietary SDK. Point your existing exporter at PostHog and you’re done.
- Traces land in the same project as your replays, errors, logs, and analytics – with correlated logs on every span.
',
+ images: [
+ {
+ src: 'https://res.cloudinary.com/dmukukwp6/image/upload/image_3_c7dd33ad13.png',
+ alt: 'A distributed trace shown as a waterfall of spans',
+ shadow: true,
+ className: 'justify-center items-center mt-24 rounded-md',
+ },
+ ],
+ },
+ // Standalone centered slide: why traces beat every other signal source
+ {
+ title: 'Catnip for agents',
+ handle: 'catnip',
+ template: 'grid',
+ headline: 'Catnip for agents',
+ description:
+ 'Every signal source tells self-driving that something is wrong. Traces are the only one that says where and why.',
+ features: [],
+ children: (
+
+
+ {catnipSignals.map(({ icon: Icon, color, title, description, highlight }) => (
+ -
+
+
+
{title}
+
{description}
+
+
+ ))}
+
+
+ The first three are symptoms. The trace is the one that hands an agent the right location. The
+ agent starts where the trace points and fixes the span that’s actually slow.
+
+
+ ),
+ },
+ ],
+ comparison: {
+ summary: {
+ them: [
+ {
+ title: 'You need a specialized, deep full-featured tracing today as a separate tool.',
+ },
+ {
+ title: 'Your workflow is infrastructure-first, built around hosts, dashboards, and on-call.',
+ },
+ {
+ title: 'You want the deepest trace tooling and are happy running a separate observability vendor for it.',
+ },
+ ],
+ us: [
+ {
+ title: 'You want traces in the same project as your errors, replays, logs, and product analytics – the context that powers self-driving.',
+ },
+ {
+ title: 'You want an agent that reads the trace to locate a fix and open the PR, from your Inbox or by tagging in Slack.',
+ },
+ {
+ title: 'You want OpenTelemetry-native tracing with no proprietary SDK to adopt.',
+ },
+ {
+ title: 'You’d rather pay for usage than per host.',
+ },
+ {
+ title: 'You’re already in PostHog and want one less tool to run.',
+ },
+ ],
+ },
+ },
+ presenterNotes: {
+ 'traces-comparison':
+ 'PostHog tracing is in alpha and free during alpha. Sentry’s native OTLP ingestion and its “Fix with Seer” Slack flow are both in beta. Datadog has no perpetual free APM tier (14-day trial), and it ingests OTLP but maps it to its own data model. Pricing accurate as of July 2026.',
+ },
+}
diff --git a/src/hooks/useProduct.ts b/src/hooks/useProduct.ts
index eb321b21af3a..713e2d0320cf 100644
--- a/src/hooks/useProduct.ts
+++ b/src/hooks/useProduct.ts
@@ -33,6 +33,7 @@ import {
} from '@posthog/icons'
import useProducts from './useProducts'
import { mcpAnalytics } from './productData/mcp_analytics'
+import { traces } from './productData/traces'
const dedupe = (products) => {
const deduped = {}
@@ -58,6 +59,7 @@ export default function useProduct({ handle }: { handle?: string } = {}) {
// name: 'Product analytics',
// slug: 'product-analytics',
// },
+ traces,
{
name: 'User interviews',
Icon: IconThoughtBubble,
diff --git a/src/pages/traces/index.tsx b/src/pages/traces/index.tsx
new file mode 100644
index 000000000000..01a97e035122
--- /dev/null
+++ b/src/pages/traces/index.tsx
@@ -0,0 +1,113 @@
+import React from 'react'
+import { useStaticQuery, graphql } from 'gatsby'
+import { SlidesTemplate, createSlideConfig } from 'components/Products/Slides'
+import TracesComparisonTable from 'components/Products/Slides/TracesComparisonTable'
+import TracesSlackCallout from 'components/Products/Slides/TracesSlackCallout'
+import TracesSelfHealing from 'components/Products/Slides/TracesSelfHealing'
+import { useContentData } from 'hooks/useContentData'
+
+const PRODUCT_HANDLE = 'traces'
+
+export default function Traces(): JSX.Element {
+ const contentData = useContentData()
+
+ // GraphQL query for product data
+ const data = useStaticQuery(graphql`
+ query {
+ allProductData {
+ nodes {
+ products {
+ name
+ type
+ unit
+ addons {
+ name
+ type
+ unit
+ plans {
+ name
+ plan_key
+ included_if
+ features {
+ key
+ name
+ description
+ limit
+ note
+ }
+ }
+ }
+ plans {
+ name
+ plan_key
+ free_allocation
+ included_if
+ features {
+ key
+ name
+ description
+ limit
+ note
+ }
+ tiers {
+ unit_amount_usd
+ up_to
+ }
+ }
+ }
+ }
+ }
+ }
+ `)
+
+ // Create slide configuration with custom templates.
+ // Building this page out slide by slide – start with just the hero.
+ const slides = createSlideConfig({
+ include: [
+ 'overview',
+ 'traces-self-healing',
+ 'features',
+ 'comparison-summary',
+ 'traces-comparison',
+ 'traces-slack',
+ 'getting-started',
+ ],
+ order: [
+ 'overview',
+ 'traces-self-healing',
+ 'features',
+ 'comparison-summary',
+ 'traces-comparison',
+ 'traces-slack',
+ 'getting-started',
+ ],
+ templates: {
+ overview: 'stacked',
+ },
+ custom: [
+ {
+ slug: 'traces-self-healing',
+ name: 'Your product, fixing itself',
+ component: TracesSelfHealing,
+ },
+ {
+ slug: 'traces-comparison',
+ name: 'Feature comparison',
+ component: TracesComparisonTable,
+ },
+ {
+ slug: 'traces-slack',
+ name: 'PostHog in Slack',
+ component: TracesSlackCallout,
+ },
+ ],
+ })
+
+ // Merge content data with product data
+ const mergedData = {
+ ...data,
+ ...contentData,
+ }
+
+ return
+}