diff --git a/apps/landing/public/screenshots/guides/sentry-pricing-01-errors.webp b/apps/landing/public/screenshots/guides/sentry-pricing-01-errors.webp new file mode 100644 index 0000000000..08417ae815 Binary files /dev/null and b/apps/landing/public/screenshots/guides/sentry-pricing-01-errors.webp differ diff --git a/apps/landing/public/screenshots/guides/sentry-pricing-02-occurrences.webp b/apps/landing/public/screenshots/guides/sentry-pricing-02-occurrences.webp new file mode 100644 index 0000000000..23d2d827ae Binary files /dev/null and b/apps/landing/public/screenshots/guides/sentry-pricing-02-occurrences.webp differ diff --git a/apps/landing/src/pages/guides/index.astro b/apps/landing/src/pages/guides/index.astro index d4dcda146a..11b74932f3 100644 --- a/apps/landing/src/pages/guides/index.astro +++ b/apps/landing/src/pages/guides/index.astro @@ -56,6 +56,13 @@ const coreGuides = [ description: "The four metric types with examples, the handful worth watching, and how to collect and chart them.", read: "7 min", }, + { + href: "/guides/sentry-pricing", + label: "Pricing", + title: "Sentry pricing in 2026", + description: "The free Developer plan, Team and Business prices, per-unit rates for errors, spans and replays, and two worked monthly bills.", + read: "9 min", + }, { href: "/guides/dynatrace-pricing", label: "Pricing", diff --git a/apps/landing/src/pages/guides/sentry-pricing.astro b/apps/landing/src/pages/guides/sentry-pricing.astro new file mode 100644 index 0000000000..bc0bd5fa3d --- /dev/null +++ b/apps/landing/src/pages/guides/sentry-pricing.astro @@ -0,0 +1,782 @@ +--- +import Layout from "../../layouts/Layout.astro"; +import Nav from "../../components/Nav.astro"; +import Footer from "../../components/Footer.astro"; +import BottomCTA from "../../components/BottomCTA.astro"; +import Breadcrumb from "../../components/Breadcrumb.astro"; +import FAQ from "../../components/FAQ.astro"; +import SeoHead from "../../components/SeoHead.astro"; +import TableOfContents from "../../components/TableOfContents.astro"; +import { CONTRIBUTORS } from "../../lib/changelog-meta"; +import { highlight } from "../../lib/guide-highlight"; +import { getOffer, money, rateLabel } from "../../lib/pricing-offer"; + +const pageTitle = "Sentry pricing 2026: free plan, plans and costs | Maple"; +const pageDescription = + "Sentry pricing for 2026: the free Developer plan, Team and Business prices, per-unit rates for errors, spans and replays, and two worked monthly bills."; +const published = "2026-10-19"; +const updated = "2026-10-20"; +const updatedLabel = "October 20, 2026"; +const verified = "2026-10-20"; +const author = CONTRIBUTORS.jeremyfunk; +const authorUrl = `https://github.com/${author.github}`; + +const offer = await getOffer(); +const gb = offer.allotments.find((a) => a.unit === "gb"); +const sessions = offer.allotments.find((a) => a.unit === "sessions"); + +const toc = [ + { depth: 2, slug: "plans", text: "Plans and prices" }, + { depth: 3, slug: "business", text: "Team or Business" }, + { depth: 3, slug: "seer", text: "Seer add-on" }, + { depth: 2, slug: "free-plan", text: "The free plan" }, + { depth: 3, slug: "free-limits", text: "Free plan limits" }, + { depth: 3, slug: "self-hosted", text: "Self-hosted Sentry" }, + { depth: 2, slug: "usage", text: "How usage is billed" }, + { depth: 3, slug: "volume", text: "Errors and spans by volume" }, + { depth: 3, slug: "session-replay", text: "Session replay pricing" }, + { depth: 3, slug: "counts", text: "What counts" }, + { depth: 2, slug: "overage", text: "Past your quota" }, + { depth: 2, slug: "examples", text: "What a month costs" }, + { depth: 3, slug: "small-team", text: "Small team" }, + { depth: 3, slug: "mid-team", text: "Mid-sized team" }, + { depth: 3, slug: "reduce", text: "Lowering the bill" }, + { depth: 2, slug: "maple", text: "Maple pricing" }, +]; +const sectionNav = toc.filter((h) => h.depth === 2); + +// One color per billed category, reused by the badges, both charts and the bill tables. +const cats = { + base: { label: "Plan price", color: "var(--muted-foreground)" }, + errors: { label: "Errors", color: "var(--chart-error)" }, + spans: { label: "Spans", color: "var(--chart-2)" }, + replays: { label: "Replays", color: "var(--chart-5)" }, + logs: { label: "Logs and metrics", color: "var(--chart-3)" }, + other: { label: "Attachments and monitors", color: "var(--chart-4)" }, +} as const; +type Cat = keyof typeof cats; + +// Sentry's published rates per unit, verified on the date above. Each tier's rate +// applies only to the units inside that tier. [reserved, pay-as-you-go] +type Plan = "team" | "business"; +const errorTiers = [ + { upTo: 100_000, label: "50k to 100k", team: [0.00029, 0.0003625], business: [0.00089, 0.0011125] }, + { upTo: 500_000, label: "100k to 500k", team: [0.000175, 0.0002188], business: [0.0005, 0.000625] }, + { upTo: 10_000_000, label: "500k to 10M", team: [0.00015, 0.0001875], business: [0.0003, 0.000375] }, + { upTo: 20_000_000, label: "10M to 20M", team: [0.00013, 0.0001625], business: [0.00026, 0.000325] }, + { upTo: Infinity, label: "Above 20M", team: [0.00012, 0.00015], business: [0.00024, 0.0003] }, +]; +const spanTiers = [ + { upTo: 100_000_000, label: "5M to 100M", team: [0.0000016, 0.000002], business: [0.0000032, 0.000004] }, + { upTo: Infinity, label: "Above 100M", team: [0.0000014, 0.0000018], business: [0.0000029, 0.0000036] }, +]; +const replayTiers = [ + { upTo: 5_000, label: "50 to 5k", rates: [0.003, 0.00375] }, + { upTo: 100_000, label: "5k to 100k", rates: [0.00285, 0.003563] }, + { upTo: 900_000, label: "100k to 900k", rates: [0.002565, 0.003206] }, + { upTo: 4_500_000, label: "900k to 4.5M", rates: [0.002309, 0.002886] }, + { upTo: Infinity, label: "Above 4.5M", rates: [0.001962, 0.002453] }, +]; +const included = { errors: 50_000, spans: 5_000_000, replays: 50, logsGb: 5, attachmentsGb: 1, cron: 1, uptime: 1 }; +const flat = { logsGb: 0.5, attachmentsGb: 0.3125, cron: 0.78, uptime: 1 }; +const planPrice = { team: 29, business: 89 }; + +const tiered = (volume: number, from: number, tiers: { upTo: number; rate: number }[]) => { + let cost = 0; + let start = from; + for (const { upTo, rate } of tiers) { + if (volume <= start) break; + cost += (Math.min(volume, upTo) - start) * rate; + start = upTo; + } + return cost; +}; +const payg = (tiers: typeof errorTiers, plan: Plan) => tiers.map((t) => ({ upTo: t.upTo, rate: t[plan][1] })); +const errorCost = (volume: number, plan: Plan) => tiered(volume, included.errors, payg(errorTiers, plan)); +const spanCost = (volume: number, plan: Plan) => tiered(volume, included.spans, payg(spanTiers, plan)); +const replayCost = (volume: number) => tiered(volume, included.replays, replayTiers.map((t) => ({ upTo: t.upTo, rate: t.rates[1] }))); +const over = (volume: number, key: keyof typeof flat) => Math.max(0, volume - included[key]) * flat[key]; + +// Invoices round each line to the cent, then add the lines. +const cents = (n: number) => Math.round(n * 100 + 1e-6) / 100; +const usd = (n: number) => `$${n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; +const per1k = (rate: number) => `$${(rate * 1000).toFixed(4).replace(/0{1,2}$/, "")}`; +const per1m = (rate: number) => `$${(rate * 1_000_000).toFixed(2)}`; + +interface Usage { errors: number; spans: number; replays: number; logsGb: number; attachmentsGb: number; cron: number; uptime: number } +const bill = (plan: Plan, u: Usage) => { + const lines: { cat: Cat; label: string; calc: string; cost: number }[] = [ + { cat: "base", label: plan === "team" ? "Team plan" : "Business plan", calc: "Monthly price", cost: planPrice[plan] }, + { cat: "errors", label: "Errors", calc: `${(u.errors - included.errors).toLocaleString("en-US")} above the included 50k, tiered`, cost: cents(errorCost(u.errors, plan)) }, + { cat: "spans", label: "Spans", calc: `${(u.spans - included.spans) / 1_000_000}M above the included 5M, tiered`, cost: cents(spanCost(u.spans, plan)) }, + { cat: "replays", label: "Replays", calc: `${(u.replays - included.replays).toLocaleString("en-US")} above the included 50, tiered`, cost: cents(replayCost(u.replays)) }, + { cat: "logs", label: "Logs", calc: `${u.logsGb - included.logsGb} GB at $0.50`, cost: cents(over(u.logsGb, "logsGb")) }, + { cat: "other", label: "Attachments", calc: `${u.attachmentsGb - included.attachmentsGb} GB at $0.3125`, cost: cents(over(u.attachmentsGb, "attachmentsGb")) }, + { cat: "other", label: "Cron monitors", calc: `${u.cron - included.cron} at $0.78`, cost: cents(over(u.cron, "cron")) }, + { cat: "other", label: "Uptime monitors", calc: `${u.uptime - included.uptime} at $1.00`, cost: cents(over(u.uptime, "uptime")) }, + ].filter((l) => l.cost > 0); + const total = cents(lines.reduce((sum, l) => sum + l.cost, 0)); + const byCat = (Object.keys(cats) as Cat[]) + .map((cat) => ({ cat, cost: lines.filter((l) => l.cat === cat).reduce((sum, l) => sum + l.cost, 0) })) + .filter((c) => c.cost > 0); + return { lines, total, byCat }; +}; + +const small = bill("team", { errors: 150_000, spans: 20_000_000, replays: 2_000, logsGb: 15, attachmentsGb: 1, cron: 5, uptime: 3 }); +const midUsage = { errors: 1_000_000, spans: 200_000_000, replays: 50_000, logsGb: 100, attachmentsGb: 5, cron: 20, uptime: 10 }; +const mid = bill("business", midUsage); +const midOnTeam = bill("team", midUsage); +const billBars = [ + { label: "Small team, Team plan", bill: small }, + { label: "Mid-sized team, Business plan", bill: mid }, +]; + +const volumePanels = [ + { + cat: "errors" as Cat, + title: "Errors per month", + rows: [100_000, 250_000, 500_000, 1_000_000].map((v) => ({ label: v >= 1_000_000 ? `${v / 1_000_000}M` : `${v / 1000}k`, team: errorCost(v, "team"), business: errorCost(v, "business") })), + }, + { + cat: "spans" as Cat, + title: "Spans per month", + rows: [20_000_000, 50_000_000, 100_000_000, 200_000_000].map((v) => ({ label: `${v / 1_000_000}M`, team: spanCost(v, "team"), business: spanCost(v, "business") })), + }, +].map((p) => ({ ...p, max: Math.max(...p.rows.map((r) => r.business)) })); + +const replayExample = cents(replayCost(10_000)); + +const sentryInit = `import * as Sentry from "@sentry/browser" + +Sentry.init({ + dsn: "https://examplePublicKey@o0.ingest.sentry.io/0", + integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration()], + + // Errors: keep every one, drop known noise before it is sent + sampleRate: 1.0, + ignoreErrors: ["ResizeObserver loop completed with undelivered notifications"], + + // Spans: send 10% of traces + tracesSampleRate: 0.1, + + // Replays: record 1% of sessions, plus every session that hits an error + replaysSessionSampleRate: 0.01, + replaysOnErrorSampleRate: 1.0, + + // Nothing from local development reaches the quota + beforeSend(event) { + return event.environment === "development" ? null : event + }, +})`; + +const faqs = [ + { + question: "Is Sentry free?", + answer: + "Yes, for one user. The Developer plan costs $0 with no time limit and includes 5,000 errors, 5 million spans, 50 session replays, 5 GB of logs, 5 GB of application metrics, 1 GB of attachments, one cron monitor and one uptime monitor a month, with a 30-day lookback. It has no pay-as-you-go budget, so data past those quotas is dropped until the next billing cycle. Self-hosted Sentry is also free to run; you pay for the servers.", + }, + { + question: "How much does Sentry cost per month?", + answer: + `Team is $29 a month billed monthly or $26 a month billed annually. Business is $89 or $80. Enterprise is priced by sales. Usage above the included 50,000 errors, 5 million spans, 50 replays and 5 GB of logs is billed per unit on top. A five-person team with moderate traffic pays about ${usd(small.total)} a month on Team, and a team sending a million errors and 200 million spans pays about ${usd(mid.total)} on Business (prices verified ${verified}).`, + }, + { + question: "What counts as an error in Sentry?", + answer: + "Every captured exception the SDK sends is one error and counts against the error quota, including repeats of an issue you resolved or ignored. The issue that groups them does not count. Events dropped by inbound filters, rate limits, spike protection, SDK sampling or beforeSend do not count, and neither do repeats of an issue set to Delete & Discard.", + }, + { + question: "What happens when you exceed your Sentry quota?", + answer: + "Sentry spends reserved volume first, then your pay-as-you-go budget if you set one. When both are used up, new data in that category is dropped for the rest of the billing cycle. You are not charged for dropped data, and you have no monitoring for that category until the cycle resets.", + }, + { + question: "Is self-hosted Sentry free?", + answer: + "The software is free under the Functional Source License. The cost is the infrastructure and the time to run it: Sentry's self-hosted docs ask for at least 4 CPU cores, 16 GB of RAM plus 16 GB of swap (32 GB of RAM recommended) and 20 GB of free disk.", + }, + { + question: "How is Sentry session replay billed?", + answer: + "Per recorded session. Every plan includes 50 replays a month. Above that, Team and Business pay the same tiered rates, starting at $3.75 per 1,000 replays on pay-as-you-go and $3.00 per 1,000 reserved. Only sessions the SDK records are counted, so replaysSessionSampleRate sets the bill.", + }, + { + question: "What is the difference between Sentry Team and Business?", + answer: + "Both include the same base volume and unlimited users. Business adds anomaly detection, advanced inbound filtering, Code Owners, Relay, SAML and SCIM single sign-on, unlimited dashboards and 1,000 metric monitors instead of 20. It also charges about twice the Team rate per error and per span above the included volume. Replay, log, attachment and monitor rates are the same on both.", + }, +]; + +const siteUrl = Astro.site?.toString().replace(/\/$/, "") ?? "https://maple.dev"; +const articleJsonLd = { + "@context": "https://schema.org", + "@type": "TechArticle", + headline: "Sentry pricing in 2026", + description: pageDescription, + datePublished: published, + dateModified: updated, + author: { "@type": "Person", name: author.name, url: authorUrl }, + publisher: { + "@type": "Organization", + name: "Maple", + logo: { "@type": "ImageObject", url: `${siteUrl}/logo192.png` }, + }, + mainEntityOfPage: `${siteUrl}/guides/sentry-pricing`, +}; +--- + + + +