All Skills > SDK Setup > NestJS SDK
Sentry NestJS SDK
Opinionated wizard that scans your NestJS project and guides you through complete Sentry setup.
Invoke This Skill When
- User asks to "add Sentry to NestJS" or "setup Sentry" in a NestJS app
- User wants error monitoring, tracing, profiling, logging, metrics, or crons in NestJS
- User mentions or Sentry + NestJS
- User wants to monitor NestJS controllers, services, guards, microservices, or background jobs
Note: SDK versions and APIs below reflect
10.x (NestJS 8–11 supported).
Always verify against
docs.sentry.io/platforms/node/guides/nestjs/ before implementing.
Phase 1: Detect
Run these commands to understand the project before making recommendations:
bash
# Confirm NestJS project
grep -E '"@nestjs/core"' package.json 2>/dev/null
# Check NestJS version
node -e "console.log(require('./node_modules/@nestjs/core/package.json').version)" 2>/dev/null
# Check existing Sentry
grep -i sentry package.json 2>/dev/null
ls src/instrument.ts 2>/dev/null
grep -r "Sentry.init\|@sentry" src/main.ts src/instrument.ts 2>/dev/null
# Check for existing Sentry DI wrapper (common in enterprise NestJS)
grep -rE "SENTRY.*TOKEN|SentryProxy|SentryService" src/ libs/ 2>/dev/null
# Check for config-class-based init (vs env-var-based)
grep -rE "class SentryConfig|SentryConfig" src/ libs/ 2>/dev/null
# Check if SentryModule.forRoot() is already registered in a shared module
grep -rE "SentryModule\.forRoot|SentryProxyModule" src/ libs/ 2>/dev/null
# Detect HTTP adapter (default is Express)
grep -E "FastifyAdapter|@nestjs/platform-fastify" package.json src/main.ts 2>/dev/null
# Detect GraphQL
grep -E '"@nestjs/graphql"|"apollo-server"' package.json 2>/dev/null
# Detect microservices
grep '"@nestjs/microservices"' package.json 2>/dev/null
# Detect WebSockets
grep -E '"@nestjs/websockets"|"socket.io"' package.json 2>/dev/null
# Detect task queues / scheduled jobs
grep -E '"@nestjs/bull"|"@nestjs/bullmq"|"@nestjs/schedule"|"bullmq"|"bull"' package.json 2>/dev/null
# Detect databases
grep -E '"@prisma/client"|"typeorm"|"mongoose"|"pg"|"mysql2"' package.json 2>/dev/null
# Detect AI libraries
grep -E '"openai"|"@anthropic-ai"|"langchain"|"@langchain"|"@google/generative-ai"|"ai"' package.json 2>/dev/null
# Check for companion frontend
ls -d ../frontend ../web ../client ../ui 2>/dev/null
What to note:
- Is already installed? If yes, check if exists and is called — may just need feature config.
- Sentry DI wrapper detected? → The project wraps Sentry behind a DI token (e.g. ) for testability. Use the injected proxy for all runtime Sentry calls (, , ) instead of importing directly in controllers, services, and processors. Only should import directly.
- Config class detected? → The project uses a typed config class for options (e.g. loaded from YAML or ). Any new SDK options must be added to the config type — do not hardcode values that should be configurable per environment.
- already registered? → If it's in a shared module (e.g. a Sentry proxy module), do not add it again in — this causes duplicate interceptor registration.
- Express (default) or Fastify adapter? Express is fully supported; Fastify works but has known edge cases.
- GraphQL detected? → handles it natively.
- Microservices detected? → Recommend RPC exception filter.
- Task queues / ? → Recommend crons.
- AI libraries? → Auto-instrumented, zero config.
- Prisma? → Requires manual .
- Companion frontend? → Triggers Phase 4 cross-link.
Phase 2: Recommend
Based on what you found, present a concrete proposal. Don't ask open-ended questions — lead with a recommendation:
Always recommended (core coverage):
- ✅ Error Monitoring — captures unhandled exceptions across HTTP, GraphQL, RPC, and WebSocket contexts
- ✅ Tracing — auto-instruments middleware, guards, pipes, interceptors, filters, and route handlers
Recommend when detected:
- ✅ Profiling — production apps where CPU performance matters ()
- ✅ Logging — structured Sentry Logs + optional console capture
- ✅ Crons — , Bull, or BullMQ detected
- ✅ Metrics — business KPIs or SLO tracking
- ✅ AI Monitoring — OpenAI/Anthropic/LangChain/etc. detected (auto-instrumented, zero config)
Recommendation matrix:
| Feature | Recommend when... | Reference |
|---|
| Error Monitoring | Always — non-negotiable baseline | ${SKILL_ROOT}/references/error-monitoring.md
|
| Tracing | Always — NestJS lifecycle is auto-instrumented | ${SKILL_ROOT}/references/tracing.md
|
| Profiling | Production + CPU-sensitive workloads | ${SKILL_ROOT}/references/profiling.md
|
| Logging | Always; enhanced for structured log aggregation | ${SKILL_ROOT}/references/logging.md
|
| Metrics | Custom business KPIs or SLO tracking | ${SKILL_ROOT}/references/metrics.md
|
| Crons | , Bull, or BullMQ detected | ${SKILL_ROOT}/references/crons.md
|
| AI Monitoring | OpenAI/Anthropic/LangChain/etc. detected | ${SKILL_ROOT}/references/ai-monitoring.md
|
Propose: "I recommend Error Monitoring + Tracing + Logging. Want Profiling, Crons, or AI Monitoring too?"
Phase 3: Guide
Install
bash
# Core SDK (always required — includes @sentry/node)
npm install @sentry/nestjs
# With profiling support (optional)
npm install @sentry/nestjs @sentry/profiling-node
⚠️
Do NOT install alongside —
re-exports everything from
. Installing both causes duplicate registration.
Three-File Setup (Required)
NestJS requires a specific three-file initialization pattern because the Sentry SDK must patch Node.js modules (via OpenTelemetry) before NestJS loads them.
Before creating new files, check Phase 1 results:
- If already exists → modify it, don't create a new one.
- If a config class drives → read options from the config instead of hardcoding env vars.
- If a Sentry DI wrapper exists → use it for runtime calls instead of importing directly in services/controllers.
Step 1: Create
typescript
import * as Sentry from "@sentry/nestjs";
// Optional: add profiling
// import { nodeProfilingIntegration } from "@sentry/profiling-node";
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.SENTRY_ENVIRONMENT ?? "production",
release: process.env.SENTRY_RELEASE,
sendDefaultPii: true,
// Tracing — lower to 0.1–0.2 in high-traffic production
tracesSampleRate: 1.0,
// Profiling (requires @sentry/profiling-node)
// integrations: [nodeProfilingIntegration()],
// profileSessionSampleRate: 1.0,
// profileLifecycle: "trace",
// Structured logs (SDK ≥ 9.41.0)
enableLogs: true,
});
Config-driven : If Phase 1 found a typed config class (e.g.
), read options from it instead of using raw
. This is common in NestJS apps that use
or custom config loaders:
typescript
import * as Sentry from "@sentry/nestjs";
import { loadConfiguration } from "./config";
const config = loadConfiguration();
Sentry.init({
dsn: config.sentry.dsn,
environment: config.sentry.environment ?? "production",
release: config.sentry.release,
sendDefaultPii: config.sentry.sendDefaultPii ?? true,
tracesSampleRate: config.sentry.tracesSampleRate ?? 1.0,
profileSessionSampleRate: config.sentry.profilesSampleRate ?? 1.0,
profileLifecycle: "trace",
enableLogs: true,
});
When adding new SDK options (e.g.
,
), add them to the config type so they can be configured per environment.
Step 2: Import FIRST in
typescript
// instrument.ts MUST be the very first import — before NestJS or any other module
import "./instrument";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Enable graceful shutdown — flushes Sentry events on SIGTERM/SIGINT
app.enableShutdownHooks();
await app.listen(3000);
}
bootstrap();
Why first? OpenTelemetry must monkey-patch
,
, database drivers, and other modules before they load. Any module that loads before
will not be auto-instrumented.
Step 3: Register and in
typescript
import { Module } from "@nestjs/common";
import { APP_FILTER } from "@nestjs/core";
import { SentryModule, SentryGlobalFilter } from "@sentry/nestjs/setup";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
@Module({
imports: [
SentryModule.forRoot(), // Registers SentryTracingInterceptor globally
],
controllers: [AppController],
providers: [
AppService,
{
provide: APP_FILTER,
useClass: SentryGlobalFilter, // Captures all unhandled exceptions
},
],
})
export class AppModule {}
What each piece does:
- — registers as a global , enabling HTTP transaction naming
- — extends ; captures exceptions across HTTP, GraphQL (rethrows without reporting), and RPC contexts
⚠️
Do NOT register twice. If Phase 1 found it already imported in a shared library module (e.g. a
or
), do not add it again in
. Duplicate registration causes every span to be intercepted twice, bloating trace data.
⚠️ Two entrypoints, different imports:
- → SDK init, capture APIs, decorators (, , )
- → NestJS DI constructs (, )
Never import
from
(main entrypoint) — it loads
before OpenTelemetry patches it, breaking auto-instrumentation.
ESM Setup (Node ≥ 18.19.0)
For ESM applications, use
instead of a file import:
javascript
// instrument.mjs
import * as Sentry from "@sentry/nestjs";
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 1.0,
});
json
// package.json
{
"scripts": {
"start": "node --import ./instrument.mjs -r ts-node/register src/main.ts"
}
}
Or via environment:
bash
NODE_OPTIONS="--import ./instrument.mjs" npm run start
Exception Filter Options
Choose the approach that fits your existing architecture:
Option A: No existing global filter — use (recommended)
Already covered in Step 3 above. This is the simplest option.
Option B: Existing custom global filter — add @SentryExceptionCaptured()
decorator
typescript
import { Catch, ExceptionFilter, ArgumentsHost } from "@nestjs/common";
import { SentryExceptionCaptured } from "@sentry/nestjs";
@Catch()
export class YourExistingFilter implements ExceptionFilter {
@SentryExceptionCaptured() // Wraps catch() to auto-report exceptions
catch(exception: unknown, host: ArgumentsHost): void {
// Your existing error handling continues unchanged
}
}
Option C: Specific exception type — manual capture
typescript
import { ArgumentsHost, Catch } from "@nestjs/common";
import { BaseExceptionFilter } from "@nestjs/core";
import * as Sentry from "@sentry/nestjs";
@Catch(ExampleException)
export class ExampleExceptionFilter extends BaseExceptionFilter {
catch(exception: ExampleException, host: ArgumentsHost) {
Sentry.captureException(exception);
super.catch(exception, host);
}
}
Option D: Microservice RPC exceptions
typescript
import { Catch, RpcExceptionFilter, ArgumentsHost } from "@nestjs/common";
import { Observable, throwError } from "rxjs";
import { RpcException } from "@nestjs/microservices";
import * as Sentry from "@sentry/nestjs";
@Catch(RpcException)
export class SentryRpcFilter implements RpcExceptionFilter<RpcException> {
catch(exception: RpcException, host: ArgumentsHost): Observable<any> {
Sentry.captureException(exception);
return throwError(() => exception.getError());
}
}
Decorators
— Instrument any method
typescript
import { Injectable } from "@nestjs/common";
import { SentryTraced } from "@sentry/nestjs";
@Injectable()
export class OrderService {
@SentryTraced("order.process")
async processOrder(orderId: string): Promise<void> {
// Automatically wrapped in a Sentry span
}
@SentryTraced() // Defaults to op: "function"
async fetchInventory() { ... }
}
@SentryCron(slug, config?)
— Monitor scheduled jobs
typescript
import { Injectable } from "@nestjs/common";
import { Cron } from "@nestjs/schedule";
import { SentryCron } from "@sentry/nestjs";
@Injectable()
export class ReportService {
@Cron("0 * * * *")
@SentryCron("hourly-report", {
// @SentryCron must come AFTER @Cron
schedule: { type: "crontab", value: "0 * * * *" },
checkinMargin: 2, // Minutes before marking missed
maxRuntime: 10, // Max runtime in minutes
timezone: "UTC",
})
async generateReport() {
// Check-in sent automatically on start/success/failure
}
}
Background Job Scope Isolation
Background jobs share the default isolation scope — wrap with
Sentry.withIsolationScope()
to prevent cross-contamination:
typescript
import * as Sentry from "@sentry/nestjs";
import { Injectable } from "@nestjs/common";
import { Cron, CronExpression } from "@nestjs/schedule";
@Injectable()
export class JobService {
@Cron(CronExpression.EVERY_HOUR)
handleCron() {
Sentry.withIsolationScope(() => {
Sentry.setTag("job", "hourly-sync");
this.doWork();
});
}
}
Apply
to:
,
,
,
, and any code outside the request lifecycle.
Working with Sentry DI Wrappers
Some NestJS projects wrap Sentry behind a dependency injection token (e.g.
) for testability and decoupling. If Phase 1 detected this pattern,
use the injected service for all runtime Sentry calls — do not import
directly in controllers, services, or processors.
typescript
import { Controller, Inject } from "@nestjs/common";
import { SENTRY_PROXY_TOKEN, type SentryProxyService } from "./sentry-proxy";
@Controller("orders")
export class OrderController {
constructor(
@Inject(SENTRY_PROXY_TOKEN) private readonly sentry: SentryProxyService,
private readonly orderService: OrderService,
) {}
@Post()
async createOrder(@Body() dto: CreateOrderDto) {
return this.sentry.startSpan(
{ name: "createOrder", op: "http" },
async () => this.orderService.create(dto),
);
}
}
Where direct import is still correct:
- — always uses
import * as Sentry from "@sentry/nestjs"
for
- Standalone scripts and exception filters that run outside the DI container
Verification
Add a test endpoint to confirm events reach Sentry:
typescript
import { Controller, Get } from "@nestjs/common";
import * as Sentry from "@sentry/nestjs";
@Controller()
export class DebugController {
@Get("/debug-sentry")
triggerError() {
throw new Error("My first Sentry error from NestJS!");
}
@Get("/debug-sentry-span")
triggerSpan() {
return Sentry.startSpan({ op: "test", name: "NestJS Test Span" }, () => {
return { status: "span created" };
});
}
}
Hit
and check the Sentry Issues dashboard within seconds.
For Each Agreed Feature
Walk through features one at a time. Load the reference, follow its steps, verify before moving on:
| Feature | Reference file | Load when... |
|---|
| Error Monitoring | ${SKILL_ROOT}/references/error-monitoring.md
| Always (baseline) |
| Tracing | ${SKILL_ROOT}/references/tracing.md
| Always (NestJS routes are auto-traced) |
| Profiling | ${SKILL_ROOT}/references/profiling.md
| CPU-intensive production apps |
| Logging | ${SKILL_ROOT}/references/logging.md
| Structured log aggregation needed |
| Metrics | ${SKILL_ROOT}/references/metrics.md
| Custom KPIs / SLO tracking |
| Crons | ${SKILL_ROOT}/references/crons.md
| Scheduled jobs or task queues |
| AI Monitoring | ${SKILL_ROOT}/references/ai-monitoring.md
| OpenAI/Anthropic/LangChain detected |
For each feature:
Read ${SKILL_ROOT}/references/<feature>.md
, follow steps exactly, verify it works.
Configuration Reference
Key Options
| Option | Type | Default | Purpose |
|---|
| | — | SDK disabled if empty; env: |
| | | e.g., ; env: |
| | — | e.g., ; env: |
| | | Include IP addresses and request headers |
| | — | Transaction sample rate; disables tracing |
| | — | Custom per-transaction sampling (overrides rate) |
| | — | URLs to propagate / headers to |
| | — | Continuous profiling session rate (SDK ≥ 10.27.0) |
| | | = auto-start profiler with spans; = call / |
| | | Send structured logs to Sentry (SDK ≥ 9.41.0) |
| | | Error message patterns to suppress |
| | | Transaction name patterns to suppress |
| | — | Hook to mutate or drop error events |
| | — | Hook to mutate or drop transaction events |
| | — | Hook to mutate or drop log events |
| | | Verbose SDK debug output |
| | | Max breadcrumbs per event |
Environment Variables
| Variable | Maps to | Notes |
|---|
| | Used if not passed to |
| | Also auto-detected from git SHA, Heroku, CircleCI |
| | Falls back to |
| CLI/source maps | For npx @sentry/wizard@latest -i sourcemaps
|
| CLI/source maps | Organization slug |
| CLI/source maps | Project slug |
Auto-Enabled Integrations
These integrations activate automatically when their packages are detected — no
needed:
| Auto-enabled | Notes |
|---|
| Outgoing HTTP calls via // |
| Express adapter (default NestJS) |
| NestJS lifecycle (middleware, guards, pipes, interceptors, handlers) |
onUncaughtExceptionIntegration
| Uncaught exceptions |
onUnhandledRejectionIntegration
| Unhandled promise rejections |
| OpenAI SDK (when installed) |
| Anthropic SDK (when installed) |
| LangChain (when installed) |
| GraphQL (when package present) |
| driver |
| / |
| MongoDB / Mongoose |
| / |
Integrations Requiring Manual Setup
| Integration | When to add | Code |
|---|
| Profiling desired | import { nodeProfilingIntegration } from "@sentry/profiling-node"
|
| Prisma ORM used | integrations: [Sentry.prismaIntegration()]
|
consoleLoggingIntegration
| Capture console output | integrations: [Sentry.consoleLoggingIntegration()]
|
localVariablesIntegration
| Capture local var values in errors | integrations: [Sentry.localVariablesIntegration()]
|
Verification
Test that Sentry is receiving events:
typescript
// Add a test endpoint (remove before production)
@Get("/debug-sentry")
getError() {
throw new Error("My first Sentry error!");
}
Or send a test message without crashing:
typescript
import * as Sentry from "@sentry/nestjs";
Sentry.captureMessage("NestJS Sentry SDK test");
If nothing appears:
- Set in — prints SDK internals to stdout
- Verify env var is set in the running process
- Check that is the first line in
- Confirm is imported in
- Check DSN format:
https://<key>@o<org>.ingest.sentry.io/<project>
Phase 4: Cross-Link
After completing NestJS setup, check for a companion frontend missing Sentry:
bash
ls -d ../frontend ../web ../client ../ui 2>/dev/null
cat ../frontend/package.json ../web/package.json 2>/dev/null \
| grep -E '"react"|"svelte"|"vue"|"next"|"nuxt"'
If a frontend exists without Sentry, suggest the matching skill:
Troubleshooting
| Issue | Solution |
|---|
| Events not appearing | Set , verify , check is imported first |
| Malformed DSN error | Format: https://<key>@o<org>.ingest.sentry.io/<project>
|
| Exceptions not captured | Ensure is registered via in |
| Auto-instrumentation not working | must be the first import in — before all NestJS imports |
| Profiling not starting | Requires + profileSessionSampleRate > 0
+ installed |
| not working | Requires SDK ≥ 9.41.0 |
| No traces appearing | Verify is set (not ) |
| Too many transactions | Lower or use to drop health checks |
| Fastify + GraphQL issues | Known edge cases — see GitHub #13388; prefer Express for GraphQL |
| Background job events mixed | Wrap job body in Sentry.withIsolationScope(() => { ... })
|
| Prisma spans missing | Add integrations: [Sentry.prismaIntegration()]
to |
| ESM syntax errors | Set registerEsmLoaderHooks: false
(disables ESM hooks; also disables auto-instrumentation for ESM modules) |
| breaks instrumentation | Must import from , never from |
| RPC exceptions not captured | Add dedicated (see Option D in exception filter section) |
| WebSocket exceptions not captured | Use @SentryExceptionCaptured()
on gateway / |
| not triggering | Decorator order matters — MUST come after |
| TypeScript path alias issues | Ensure are configured so resolves from location |
| ESLint error | Many projects ban namespace imports. Use named imports (import { startSpan, captureException } from "@sentry/nestjs"
) or use the project's DI proxy instead |
| vs | is deprecated in SDK 10.x. Use + profileLifecycle: "trace"
instead |
| Duplicate spans on every request | registered in multiple modules. Ensure it's only called once — check shared/library modules |
| Config property not recognized in | When using a typed config class, new SDK options must be added to the config type definition and the project rebuilt before TypeScript recognizes them |
Version Requirements
| Feature | Minimum SDK Version |
|---|
| package | 8.0.0 |
| decorator | 8.15.0 |
| decorator | 8.16.0 |
| Event Emitter auto-instrumentation | 8.39.0 |
| (unified) | 8.40.0 |
| API () | 9.41.0 |
| 10.27.0 |
| Node.js requirement | ≥ 18 |
| Node.js for ESM | ≥ 18.19.0 |
| NestJS compatibility | 8.x – 11.x |