diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/.gitignore b/dev-packages/e2e-tests/test-applications/nestjs-fastify/.gitignore
new file mode 100644
index 000000000000..4b56acfbebf4
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/.gitignore
@@ -0,0 +1,56 @@
+# compiled output
+/dist
+/node_modules
+/build
+
+# Logs
+logs
+*.log
+npm-debug.log*
+pnpm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# OS
+.DS_Store
+
+# Tests
+/coverage
+/.nyc_output
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# temp directory
+.temp
+.tmp
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/.npmrc b/dev-packages/e2e-tests/test-applications/nestjs-fastify/.npmrc
new file mode 100644
index 000000000000..070f80f05092
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/.npmrc
@@ -0,0 +1,2 @@
+@sentry:registry=http://127.0.0.1:4873
+@sentry-internal:registry=http://127.0.0.1:4873
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/README.md b/dev-packages/e2e-tests/test-applications/nestjs-fastify/README.md
new file mode 100644
index 000000000000..63c3e90d9b1a
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/README.md
@@ -0,0 +1,85 @@
+
+   +
+
+
+[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
+[circleci-url]: https://circleci.com/gh/nestjs/nest
+
+  A progressive Node.js framework for building efficient and scalable server-side applications.
+    
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+   +
+     +
+   +
+
+  
+
+## Description
+
+[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
+
+## Project setup
+
+```bash
+$ yarn install
+```
+
+## Compile and run the project
+
+```bash
+# development
+$ yarn run start
+
+# watch mode
+$ yarn run start:dev
+
+# production mode
+$ yarn run start:prod
+```
+
+## Run tests
+
+```bash
+# unit tests
+$ yarn run test
+
+# e2e tests
+$ yarn run test:e2e
+
+# test coverage
+$ yarn run test:cov
+```
+
+## Resources
+
+Check out a few resources that may come in handy when working with NestJS:
+
+- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
+- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
+- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
+- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
+- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
+- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
+- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
+
+## Support
+
+Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
+
+## Stay in touch
+
+- Author - [Kamil MyĆliwiec](https://twitter.com/kammysliwiec)
+- Website - [https://nestjs.com](https://nestjs.com/)
+- Twitter - [@nestframework](https://twitter.com/nestframework)
+
+## License
+
+Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/nest-cli.json b/dev-packages/e2e-tests/test-applications/nestjs-fastify/nest-cli.json
new file mode 100644
index 000000000000..f9aa683b1ad5
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/nest-cli.json
@@ -0,0 +1,8 @@
+{
+  "$schema": "https://json.schemastore.org/nest-cli",
+  "collection": "@nestjs/schematics",
+  "sourceRoot": "src",
+  "compilerOptions": {
+    "deleteOutDir": true
+  }
+}
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/package.json b/dev-packages/e2e-tests/test-applications/nestjs-fastify/package.json
new file mode 100644
index 000000000000..6da132e74a4c
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/package.json
@@ -0,0 +1,48 @@
+{
+  "name": "nestjs-fastify",
+  "version": "0.0.1",
+  "private": true,
+  "scripts": {
+    "build": "nest build",
+    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
+    "start": "nest start",
+    "start:dev": "nest start --watch",
+    "start:debug": "nest start --debug --watch",
+    "start:prod": "node dist/main",
+    "clean": "npx rimraf node_modules pnpm-lock.yaml",
+    "test": "playwright test",
+    "test:build": "pnpm install",
+    "test:assert": "pnpm test"
+  },
+  "dependencies": {
+    "@nestjs/common": "^10.0.0",
+    "@nestjs/core": "^10.0.0",
+    "@nestjs/microservices": "^10.0.0",
+    "@nestjs/schedule": "^4.1.0",
+    "@nestjs/platform-fastify": "^10.0.0",
+    "@sentry/nestjs": "latest || *",
+    "reflect-metadata": "^0.2.0",
+    "rxjs": "^7.8.1",
+    "fastify": "^4.28.1"
+  },
+  "devDependencies": {
+    "@playwright/test": "^1.44.1",
+    "@sentry-internal/test-utils": "link:../../../test-utils",
+    "@nestjs/cli": "^10.0.0",
+    "@nestjs/schematics": "^10.0.0",
+    "@nestjs/testing": "^10.0.0",
+    "@types/node": "18.15.1",
+    "@types/supertest": "^6.0.0",
+    "@typescript-eslint/eslint-plugin": "^6.0.0",
+    "@typescript-eslint/parser": "^6.0.0",
+    "eslint": "^8.42.0",
+    "eslint-config-prettier": "^9.0.0",
+    "eslint-plugin-prettier": "^5.0.0",
+    "prettier": "^3.0.0",
+    "source-map-support": "^0.5.21",
+    "supertest": "^6.3.3",
+    "ts-loader": "^9.4.3",
+    "tsconfig-paths": "^4.2.0",
+    "typescript": "^5.1.3"
+  }
+}
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nestjs-fastify/playwright.config.mjs
new file mode 100644
index 000000000000..31f2b913b58b
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/playwright.config.mjs
@@ -0,0 +1,7 @@
+import { getPlaywrightConfig } from '@sentry-internal/test-utils';
+
+const config = getPlaywrightConfig({
+  startCommand: `pnpm start`,
+});
+
+export default config;
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/app.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/app.controller.ts
new file mode 100644
index 000000000000..33a6b1957d99
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/app.controller.ts
@@ -0,0 +1,124 @@
+import { Controller, Get, Param, ParseIntPipe, UseFilters, UseGuards, UseInterceptors } from '@nestjs/common';
+import { flush } from '@sentry/nestjs';
+import { AppService } from './app.service';
+import { AsyncInterceptor } from './async-example.interceptor';
+import { ExampleInterceptor1 } from './example-1.interceptor';
+import { ExampleInterceptor2 } from './example-2.interceptor';
+import { ExampleExceptionGlobalFilter } from './example-global-filter.exception';
+import { ExampleExceptionLocalFilter } from './example-local-filter.exception';
+import { ExampleLocalFilter } from './example-local.filter';
+import { ExampleGuard } from './example.guard';
+
+@Controller()
+@UseFilters(ExampleLocalFilter)
+export class AppController {
+  constructor(private readonly appService: AppService) {}
+
+  @Get('test-transaction')
+  testTransaction() {
+    return this.appService.testTransaction();
+  }
+
+  @Get('test-middleware-instrumentation')
+  testMiddlewareInstrumentation() {
+    return this.appService.testSpan();
+  }
+
+  @Get('test-guard-instrumentation')
+  @UseGuards(ExampleGuard)
+  testGuardInstrumentation() {
+    return {};
+  }
+
+  @Get('test-interceptor-instrumentation')
+  @UseInterceptors(ExampleInterceptor1, ExampleInterceptor2)
+  testInterceptorInstrumentation() {
+    return this.appService.testSpan();
+  }
+
+  @Get('test-async-interceptor-instrumentation')
+  @UseInterceptors(AsyncInterceptor)
+  testAsyncInterceptorInstrumentation() {
+    return this.appService.testSpan();
+  }
+
+  @Get('test-pipe-instrumentation/:id')
+  testPipeInstrumentation(@Param('id', ParseIntPipe) id: number) {
+    return { value: id };
+  }
+
+  @Get('test-exception/:id')
+  async testException(@Param('id') id: string) {
+    return this.appService.testException(id);
+  }
+
+  @Get('test-expected-400-exception/:id')
+  async testExpected400Exception(@Param('id') id: string) {
+    return this.appService.testExpected400Exception(id);
+  }
+
+  @Get('test-expected-500-exception/:id')
+  async testExpected500Exception(@Param('id') id: string) {
+    return this.appService.testExpected500Exception(id);
+  }
+
+  @Get('test-expected-rpc-exception/:id')
+  async testExpectedRpcException(@Param('id') id: string) {
+    return this.appService.testExpectedRpcException(id);
+  }
+
+  @Get('test-span-decorator-async')
+  async testSpanDecoratorAsync() {
+    return { result: await this.appService.testSpanDecoratorAsync() };
+  }
+
+  @Get('test-span-decorator-sync')
+  async testSpanDecoratorSync() {
+    return { result: await this.appService.testSpanDecoratorSync() };
+  }
+
+  @Get('kill-test-cron/:job')
+  async killTestCron(@Param('job') job: string) {
+    this.appService.killTestCron(job);
+  }
+
+  @Get('flush')
+  async flush() {
+    await flush();
+  }
+
+  @Get('example-exception-global-filter')
+  async exampleExceptionGlobalFilter() {
+    throw new ExampleExceptionGlobalFilter();
+  }
+
+  @Get('example-exception-local-filter')
+  async exampleExceptionLocalFilter() {
+    throw new ExampleExceptionLocalFilter();
+  }
+
+  @Get('test-service-use')
+  testServiceWithUseMethod() {
+    return this.appService.use();
+  }
+
+  @Get('test-service-transform')
+  testServiceWithTransform() {
+    return this.appService.transform();
+  }
+
+  @Get('test-service-intercept')
+  testServiceWithIntercept() {
+    return this.appService.intercept();
+  }
+
+  @Get('test-service-canActivate')
+  testServiceWithCanActivate() {
+    return this.appService.canActivate();
+  }
+
+  @Get('test-function-name')
+  testFunctionName() {
+    return this.appService.getFunctionName();
+  }
+}
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/app.module.ts
new file mode 100644
index 000000000000..3de3c82dc925
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/app.module.ts
@@ -0,0 +1,29 @@
+import { MiddlewareConsumer, Module } from '@nestjs/common';
+import { APP_FILTER } from '@nestjs/core';
+import { ScheduleModule } from '@nestjs/schedule';
+import { SentryGlobalFilter, SentryModule } from '@sentry/nestjs/setup';
+import { AppController } from './app.controller';
+import { AppService } from './app.service';
+import { ExampleGlobalFilter } from './example-global.filter';
+import { ExampleMiddleware } from './example.middleware';
+
+@Module({
+  imports: [SentryModule.forRoot(), ScheduleModule.forRoot()],
+  controllers: [AppController],
+  providers: [
+    AppService,
+    {
+      provide: APP_FILTER,
+      useClass: SentryGlobalFilter,
+    },
+    {
+      provide: APP_FILTER,
+      useClass: ExampleGlobalFilter,
+    },
+  ],
+})
+export class AppModule {
+  configure(consumer: MiddlewareConsumer): void {
+    consumer.apply(ExampleMiddleware).forRoutes('test-middleware-instrumentation');
+  }
+}
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/app.service.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/app.service.ts
new file mode 100644
index 000000000000..242b4c778a0e
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/app.service.ts
@@ -0,0 +1,113 @@
+import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
+import { RpcException } from '@nestjs/microservices';
+import { Cron, SchedulerRegistry } from '@nestjs/schedule';
+import type { MonitorConfig } from '@sentry/core';
+import * as Sentry from '@sentry/nestjs';
+import { SentryCron, SentryTraced } from '@sentry/nestjs';
+
+const monitorConfig: MonitorConfig = {
+  schedule: {
+    type: 'crontab',
+    value: '* * * * *',
+  },
+};
+
+@Injectable()
+export class AppService {
+  constructor(private schedulerRegistry: SchedulerRegistry) {}
+
+  testTransaction() {
+    Sentry.startSpan({ name: 'test-span' }, () => {
+      Sentry.startSpan({ name: 'child-span' }, () => {});
+    });
+  }
+
+  testSpan() {
+    // span that should not be a child span of the middleware span
+    Sentry.startSpan({ name: 'test-controller-span' }, () => {});
+  }
+
+  testException(id: string) {
+    throw new Error(`This is an exception with id ${id}`);
+  }
+
+  testExpected400Exception(id: string) {
+    throw new HttpException(`This is an expected 400 exception with id ${id}`, HttpStatus.BAD_REQUEST);
+  }
+
+  testExpected500Exception(id: string) {
+    throw new HttpException(`This is an expected 500 exception with id ${id}`, HttpStatus.INTERNAL_SERVER_ERROR);
+  }
+
+  testExpectedRpcException(id: string) {
+    throw new RpcException(`This is an expected RPC exception with id ${id}`);
+  }
+
+  @SentryTraced('wait and return a string')
+  async wait() {
+    await new Promise(resolve => setTimeout(resolve, 500));
+    return 'test';
+  }
+
+  async testSpanDecoratorAsync() {
+    return await this.wait();
+  }
+
+  @SentryTraced('return a string')
+  getString(): { result: string } {
+    return { result: 'test' };
+  }
+
+  @SentryTraced('return the function name')
+  getFunctionName(): { result: string } {
+    return { result: this.getFunctionName.name };
+  }
+
+  async testSpanDecoratorSync() {
+    const returned = this.getString();
+    // Will fail if getString() is async, because returned will be a Promise<>
+    return returned.result;
+  }
+
+  /*
+  Actual cron schedule differs from schedule defined in config because Sentry
+  only supports minute granularity, but we don't want to wait (worst case) a
+  full minute for the tests to finish.
+  */
+  @Cron('*/5 * * * * *', { name: 'test-cron-job' })
+  @SentryCron('test-cron-slug', monitorConfig)
+  async testCron() {
+    console.log('Test cron!');
+  }
+
+  /*
+  Actual cron schedule differs from schedule defined in config because Sentry
+  only supports minute granularity, but we don't want to wait (worst case) a
+  full minute for the tests to finish.
+  */
+  @Cron('*/5 * * * * *', { name: 'test-cron-error' })
+  @SentryCron('test-cron-error-slug', monitorConfig)
+  async testCronError() {
+    throw new Error('Test error from cron job');
+  }
+
+  async killTestCron(job: string) {
+    this.schedulerRegistry.deleteCronJob(job);
+  }
+
+  use() {
+    console.log('Test use!');
+  }
+
+  transform() {
+    console.log('Test transform!');
+  }
+
+  intercept() {
+    console.log('Test intercept!');
+  }
+
+  canActivate() {
+    console.log('Test canActivate!');
+  }
+}
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/async-example.interceptor.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/async-example.interceptor.ts
new file mode 100644
index 000000000000..ac0ee60acc51
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/async-example.interceptor.ts
@@ -0,0 +1,17 @@
+import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
+import * as Sentry from '@sentry/nestjs';
+import { tap } from 'rxjs';
+
+@Injectable()
+export class AsyncInterceptor implements NestInterceptor {
+  intercept(context: ExecutionContext, next: CallHandler) {
+    Sentry.startSpan({ name: 'test-async-interceptor-span' }, () => {});
+    return Promise.resolve(
+      next.handle().pipe(
+        tap(() => {
+          Sentry.startSpan({ name: 'test-async-interceptor-span-after-route' }, () => {});
+        }),
+      ),
+    );
+  }
+}
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example-1.interceptor.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example-1.interceptor.ts
new file mode 100644
index 000000000000..81c9f70d30e2
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example-1.interceptor.ts
@@ -0,0 +1,15 @@
+import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
+import * as Sentry from '@sentry/nestjs';
+import { tap } from 'rxjs';
+
+@Injectable()
+export class ExampleInterceptor1 implements NestInterceptor {
+  intercept(context: ExecutionContext, next: CallHandler) {
+    Sentry.startSpan({ name: 'test-interceptor-span-1' }, () => {});
+    return next.handle().pipe(
+      tap(() => {
+        Sentry.startSpan({ name: 'test-interceptor-span-after-route' }, () => {});
+      }),
+    );
+  }
+}
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example-2.interceptor.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example-2.interceptor.ts
new file mode 100644
index 000000000000..2cf9dfb9e043
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example-2.interceptor.ts
@@ -0,0 +1,10 @@
+import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
+import * as Sentry from '@sentry/nestjs';
+
+@Injectable()
+export class ExampleInterceptor2 implements NestInterceptor {
+  intercept(context: ExecutionContext, next: CallHandler) {
+    Sentry.startSpan({ name: 'test-interceptor-span-2' }, () => {});
+    return next.handle().pipe();
+  }
+}
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example-global-filter.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example-global-filter.exception.ts
new file mode 100644
index 000000000000..41981ba748fe
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example-global-filter.exception.ts
@@ -0,0 +1,5 @@
+export class ExampleExceptionGlobalFilter extends Error {
+  constructor() {
+    super('Original global example exception!');
+  }
+}
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example-global.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example-global.filter.ts
new file mode 100644
index 000000000000..fba749f2232c
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example-global.filter.ts
@@ -0,0 +1,19 @@
+import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter } from '@nestjs/common';
+import { FastifyReply, FastifyRequest } from 'fastify';
+import { ExampleExceptionGlobalFilter } from './example-global-filter.exception';
+
+@Catch(ExampleExceptionGlobalFilter)
+export class ExampleGlobalFilter implements ExceptionFilter {
+  catch(exception: BadRequestException, host: ArgumentsHost): void {
+    const ctx = host.switchToHttp();
+    const response = ctx.getResponse();
+    const request = ctx.getRequest();
+
+    response.status(400).send({
+      statusCode: 400,
+      timestamp: new Date().toISOString(),
+      path: request.url,
+      message: 'Example exception was handled by global filter!',
+    });
+  }
+}
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example-local-filter.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example-local-filter.exception.ts
new file mode 100644
index 000000000000..8f76520a3b94
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example-local-filter.exception.ts
@@ -0,0 +1,5 @@
+export class ExampleExceptionLocalFilter extends Error {
+  constructor() {
+    super('Original local example exception!');
+  }
+}
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example-local.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example-local.filter.ts
new file mode 100644
index 000000000000..aadf09983947
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example-local.filter.ts
@@ -0,0 +1,19 @@
+import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter } from '@nestjs/common';
+import { FastifyReply, FastifyRequest } from 'fastify';
+import { ExampleExceptionLocalFilter } from './example-local-filter.exception';
+
+@Catch(ExampleExceptionLocalFilter)
+export class ExampleLocalFilter implements ExceptionFilter {
+  catch(exception: BadRequestException, host: ArgumentsHost): void {
+    const ctx = host.switchToHttp();
+    const response = ctx.getResponse();
+    const request = ctx.getRequest();
+
+    response.status(400).send({
+      statusCode: 400,
+      timestamp: new Date().toISOString(),
+      path: request.url,
+      message: 'Example exception was handled by local filter!',
+    });
+  }
+}
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example.guard.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example.guard.ts
new file mode 100644
index 000000000000..e12bbdc4e994
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example.guard.ts
@@ -0,0 +1,10 @@
+import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
+import * as Sentry from '@sentry/nestjs';
+
+@Injectable()
+export class ExampleGuard implements CanActivate {
+  canActivate(context: ExecutionContext): boolean {
+    Sentry.startSpan({ name: 'test-guard-span' }, () => {});
+    return true;
+  }
+}
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example.middleware.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example.middleware.ts
new file mode 100644
index 000000000000..8eb319cef309
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/example.middleware.ts
@@ -0,0 +1,12 @@
+import { Injectable, NestMiddleware } from '@nestjs/common';
+import * as Sentry from '@sentry/nestjs';
+import { FastifyReply, FastifyRequest } from 'fastify';
+
+@Injectable()
+export class ExampleMiddleware implements NestMiddleware {
+  use(req: FastifyRequest, res: FastifyReply, next: () => void) {
+    // span that should be a child span of the middleware span
+    Sentry.startSpan({ name: 'test-middleware-span' }, () => {});
+    next();
+  }
+}
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/instrument.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/instrument.ts
new file mode 100644
index 000000000000..4f16ebb36d11
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/instrument.ts
@@ -0,0 +1,12 @@
+import * as Sentry from '@sentry/nestjs';
+
+Sentry.init({
+  environment: 'qa', // dynamic sampling bias to keep transactions
+  dsn: process.env.E2E_TEST_DSN,
+  tunnel: `http://localhost:3031/`, // proxy server
+  tracesSampleRate: 1,
+  transportOptions: {
+    // We expect the app to send a lot of events in a short time
+    bufferSize: 1000,
+  },
+});
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/main.ts
new file mode 100644
index 000000000000..7c7c6e4142d4
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/main.ts
@@ -0,0 +1,16 @@
+// Import this first
+import './instrument';
+
+// Import other modules
+import { NestFactory } from '@nestjs/core';
+import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
+import { AppModule } from './app.module';
+
+const PORT = 3030;
+
+async function bootstrap() {
+  const app = await NestFactory.create(AppModule, new FastifyAdapter());
+  await app.listen(PORT);
+}
+
+bootstrap();
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nestjs-fastify/start-event-proxy.mjs
new file mode 100644
index 000000000000..c6a92da76970
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/start-event-proxy.mjs
@@ -0,0 +1,6 @@
+import { startEventProxyServer } from '@sentry-internal/test-utils';
+
+startEventProxyServer({
+  port: 3031,
+  proxyServerName: 'nestjs-fastify',
+});
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/cron-decorator.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/cron-decorator.test.ts
new file mode 100644
index 000000000000..e352e8fdba8f
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/cron-decorator.test.ts
@@ -0,0 +1,81 @@
+import { expect, test } from '@playwright/test';
+import { waitForEnvelopeItem, waitForError } from '@sentry-internal/test-utils';
+
+test('Cron job triggers send of in_progress envelope', async ({ baseURL }) => {
+  const inProgressEnvelopePromise = waitForEnvelopeItem('nestjs-fastify', envelope => {
+    return (
+      envelope[0].type === 'check_in' &&
+      envelope[1]['monitor_slug'] === 'test-cron-slug' &&
+      envelope[1]['status'] === 'in_progress'
+    );
+  });
+
+  const okEnvelopePromise = waitForEnvelopeItem('nestjs-fastify', envelope => {
+    return (
+      envelope[0].type === 'check_in' &&
+      envelope[1]['monitor_slug'] === 'test-cron-slug' &&
+      envelope[1]['status'] === 'ok'
+    );
+  });
+
+  const inProgressEnvelope = await inProgressEnvelopePromise;
+  const okEnvelope = await okEnvelopePromise;
+
+  expect(inProgressEnvelope[1]).toEqual(
+    expect.objectContaining({
+      check_in_id: expect.any(String),
+      monitor_slug: 'test-cron-slug',
+      status: 'in_progress',
+      environment: 'qa',
+      monitor_config: {
+        schedule: {
+          type: 'crontab',
+          value: '* * * * *',
+        },
+      },
+      contexts: {
+        trace: {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+        },
+      },
+    }),
+  );
+
+  expect(okEnvelope[1]).toEqual(
+    expect.objectContaining({
+      check_in_id: expect.any(String),
+      monitor_slug: 'test-cron-slug',
+      status: 'ok',
+      environment: 'qa',
+      duration: expect.any(Number),
+      contexts: {
+        trace: {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+        },
+      },
+    }),
+  );
+
+  // kill cron so tests don't get stuck
+  await fetch(`${baseURL}/kill-test-cron/test-cron-job`);
+});
+
+test('Sends exceptions to Sentry on error in cron job', async ({ baseURL }) => {
+  const errorEventPromise = waitForError('nestjs-fastify', event => {
+    return !event.type && event.exception?.values?.[0]?.value === 'Test error from cron job';
+  });
+
+  const errorEvent = await errorEventPromise;
+
+  expect(errorEvent.exception?.values).toHaveLength(1);
+  expect(errorEvent.exception?.values?.[0]?.value).toBe('Test error from cron job');
+  expect(errorEvent.contexts?.trace).toEqual({
+    trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+    span_id: expect.stringMatching(/[a-f0-9]{16}/),
+  });
+
+  // kill cron so tests don't get stuck
+  await fetch(`${baseURL}/kill-test-cron/test-cron-error`);
+});
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/errors.test.ts
new file mode 100644
index 000000000000..4eea05edd36f
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/errors.test.ts
@@ -0,0 +1,167 @@
+import { expect, test } from '@playwright/test';
+import { waitForError, waitForTransaction } from '@sentry-internal/test-utils';
+
+test('Sends exception to Sentry', async ({ baseURL }) => {
+  const errorEventPromise = waitForError('nestjs-fastify', event => {
+    return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 123';
+  });
+
+  const response = await fetch(`${baseURL}/test-exception/123`);
+  expect(response.status).toBe(500);
+
+  const errorEvent = await errorEventPromise;
+
+  expect(errorEvent.exception?.values).toHaveLength(1);
+  expect(errorEvent.exception?.values?.[0]?.value).toBe('This is an exception with id 123');
+
+  expect(errorEvent.request).toEqual({
+    method: 'GET',
+    cookies: {},
+    headers: expect.any(Object),
+    url: 'http://localhost:3030/test-exception/123',
+  });
+
+  expect(errorEvent.transaction).toEqual('GET /test-exception/:id');
+
+  expect(errorEvent.contexts?.trace).toEqual({
+    parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+    trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+    span_id: expect.stringMatching(/[a-f0-9]{16}/),
+  });
+});
+
+test('Does not send HttpExceptions to Sentry', async ({ baseURL }) => {
+  let errorEventOccurred = false;
+
+  waitForError('nestjs-fastify', event => {
+    if (!event.type && event.exception?.values?.[0]?.value === 'This is an expected 400 exception with id 123') {
+      errorEventOccurred = true;
+    }
+
+    return event?.transaction === 'GET /test-expected-400-exception/:id';
+  });
+
+  waitForError('nestjs-fastify', event => {
+    if (!event.type && event.exception?.values?.[0]?.value === 'This is an expected 500 exception with id 123') {
+      errorEventOccurred = true;
+    }
+
+    return event?.transaction === 'GET /test-expected-500-exception/:id';
+  });
+
+  const transactionEventPromise400 = waitForTransaction('nestjs-fastify', transactionEvent => {
+    return transactionEvent?.transaction === 'GET /test-expected-400-exception/:id';
+  });
+
+  const transactionEventPromise500 = waitForTransaction('nestjs-fastify', transactionEvent => {
+    return transactionEvent?.transaction === 'GET /test-expected-500-exception/:id';
+  });
+
+  const response400 = await fetch(`${baseURL}/test-expected-400-exception/123`);
+  expect(response400.status).toBe(400);
+
+  const response500 = await fetch(`${baseURL}/test-expected-500-exception/123`);
+  expect(response500.status).toBe(500);
+
+  await transactionEventPromise400;
+  await transactionEventPromise500;
+
+  (await fetch(`${baseURL}/flush`)).text();
+
+  expect(errorEventOccurred).toBe(false);
+});
+
+test('Does not send RpcExceptions to Sentry', async ({ baseURL }) => {
+  let errorEventOccurred = false;
+
+  waitForError('nestjs-fastify', event => {
+    if (!event.type && event.exception?.values?.[0]?.value === 'This is an expected RPC exception with id 123') {
+      errorEventOccurred = true;
+    }
+
+    return event?.transaction === 'GET /test-expected-rpc-exception/:id';
+  });
+
+  const transactionEventPromise = waitForTransaction('nestjs-fastify', transactionEvent => {
+    return transactionEvent?.transaction === 'GET /test-expected-rpc-exception/:id';
+  });
+
+  const response = await fetch(`${baseURL}/test-expected-rpc-exception/123`);
+  expect(response.status).toBe(500);
+
+  await transactionEventPromise;
+
+  (await fetch(`${baseURL}/flush`)).text();
+
+  expect(errorEventOccurred).toBe(false);
+});
+
+test('Global exception filter registered in main module is applied and exception is not sent to Sentry', async ({
+  baseURL,
+}) => {
+  let errorEventOccurred = false;
+
+  waitForError('nestjs-fastify', event => {
+    if (!event.type && event.exception?.values?.[0]?.value === 'Example exception was handled by global filter!') {
+      errorEventOccurred = true;
+    }
+
+    return event?.transaction === 'GET /example-exception-global-filter';
+  });
+
+  const transactionEventPromise = waitForTransaction('nestjs-fastify', transactionEvent => {
+    return transactionEvent?.transaction === 'GET /example-exception-global-filter';
+  });
+
+  const response = await fetch(`${baseURL}/example-exception-global-filter`);
+  const responseBody = await response.json();
+
+  expect(response.status).toBe(400);
+  expect(responseBody).toEqual({
+    statusCode: 400,
+    timestamp: expect.any(String),
+    path: '/example-exception-global-filter',
+    message: 'Example exception was handled by global filter!',
+  });
+
+  await transactionEventPromise;
+
+  (await fetch(`${baseURL}/flush`)).text();
+
+  expect(errorEventOccurred).toBe(false);
+});
+
+test('Local exception filter registered in main module is applied and exception is not sent to Sentry', async ({
+  baseURL,
+}) => {
+  let errorEventOccurred = false;
+
+  waitForError('nestjs-fastify', event => {
+    if (!event.type && event.exception?.values?.[0]?.value === 'Example exception was handled by local filter!') {
+      errorEventOccurred = true;
+    }
+
+    return event?.transaction === 'GET /example-exception-local-filter';
+  });
+
+  const transactionEventPromise = waitForTransaction('nestjs-fastify', transactionEvent => {
+    return transactionEvent?.transaction === 'GET /example-exception-local-filter';
+  });
+
+  const response = await fetch(`${baseURL}/example-exception-local-filter`);
+  const responseBody = await response.json();
+
+  expect(response.status).toBe(400);
+  expect(responseBody).toEqual({
+    statusCode: 400,
+    timestamp: expect.any(String),
+    path: '/example-exception-local-filter',
+    message: 'Example exception was handled by local filter!',
+  });
+
+  await transactionEventPromise;
+
+  (await fetch(`${baseURL}/flush`)).text();
+
+  expect(errorEventOccurred).toBe(false);
+});
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/span-decorator.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/span-decorator.test.ts
new file mode 100644
index 000000000000..6efb193751b9
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/span-decorator.test.ts
@@ -0,0 +1,79 @@
+import { expect, test } from '@playwright/test';
+import { waitForTransaction } from '@sentry-internal/test-utils';
+
+test('Transaction includes span and correct value for decorated async function', async ({ baseURL }) => {
+  const transactionEventPromise = waitForTransaction('nestjs-fastify', transactionEvent => {
+    return (
+      transactionEvent?.contexts?.trace?.op === 'http.server' &&
+      transactionEvent?.transaction === 'GET /test-span-decorator-async'
+    );
+  });
+
+  const response = await fetch(`${baseURL}/test-span-decorator-async`);
+  const body = await response.json();
+
+  expect(body.result).toEqual('test');
+
+  const transactionEvent = await transactionEventPromise;
+
+  expect(transactionEvent.spans).toEqual(
+    expect.arrayContaining([
+      expect.objectContaining({
+        span_id: expect.stringMatching(/[a-f0-9]{16}/),
+        trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+        data: {
+          'sentry.origin': 'manual',
+          'sentry.op': 'wait and return a string',
+        },
+        description: 'wait',
+        parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+        start_timestamp: expect.any(Number),
+        status: 'ok',
+        op: 'wait and return a string',
+        origin: 'manual',
+      }),
+    ]),
+  );
+});
+
+test('Transaction includes span and correct value for decorated sync function', async ({ baseURL }) => {
+  const transactionEventPromise = waitForTransaction('nestjs-fastify', transactionEvent => {
+    return (
+      transactionEvent?.contexts?.trace?.op === 'http.server' &&
+      transactionEvent?.transaction === 'GET /test-span-decorator-sync'
+    );
+  });
+
+  const response = await fetch(`${baseURL}/test-span-decorator-sync`);
+  const body = await response.json();
+
+  expect(body.result).toEqual('test');
+
+  const transactionEvent = await transactionEventPromise;
+
+  expect(transactionEvent.spans).toEqual(
+    expect.arrayContaining([
+      expect.objectContaining({
+        span_id: expect.stringMatching(/[a-f0-9]{16}/),
+        trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+        data: {
+          'sentry.origin': 'manual',
+          'sentry.op': 'return a string',
+        },
+        description: 'getString',
+        parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+        start_timestamp: expect.any(Number),
+        status: 'ok',
+        op: 'return a string',
+        origin: 'manual',
+      }),
+    ]),
+  );
+});
+
+test('preserves original function name on decorated functions', async ({ baseURL }) => {
+  const response = await fetch(`${baseURL}/test-function-name`);
+  const body = await response.json();
+
+  expect(body.result).toEqual('getFunctionName');
+});
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts
new file mode 100644
index 000000000000..609e01709650
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts
@@ -0,0 +1,810 @@
+import { expect, test } from '@playwright/test';
+import { waitForTransaction } from '@sentry-internal/test-utils';
+
+test('Sends an API route transaction', async ({ baseURL }) => {
+  const pageloadTransactionEventPromise = waitForTransaction('nestjs-fastify', transactionEvent => {
+    return (
+      transactionEvent?.contexts?.trace?.op === 'http.server' &&
+      transactionEvent?.transaction === 'GET /test-transaction'
+    );
+  });
+
+  await fetch(`${baseURL}/test-transaction`);
+
+  const transactionEvent = await pageloadTransactionEventPromise;
+
+  expect(transactionEvent.contexts?.trace).toEqual({
+    data: {
+      'sentry.source': 'route',
+      'sentry.origin': 'auto.http.otel.http',
+      'sentry.op': 'http.server',
+      'sentry.sample_rate': 1,
+      url: 'http://localhost:3030/test-transaction',
+      'otel.kind': 'SERVER',
+      'http.response.status_code': 200,
+      'http.url': 'http://localhost:3030/test-transaction',
+      'http.host': 'localhost:3030',
+      'net.host.name': 'localhost',
+      'http.method': 'GET',
+      'http.scheme': 'http',
+      'http.target': '/test-transaction',
+      'http.user_agent': 'node',
+      'http.flavor': '1.1',
+      'net.transport': 'ip_tcp',
+      'net.host.ip': expect.any(String),
+      'net.host.port': expect.any(Number),
+      'net.peer.ip': expect.any(String),
+      'net.peer.port': expect.any(Number),
+      'http.status_code': 200,
+      'http.status_text': 'OK',
+      'http.route': '/test-transaction',
+    },
+    op: 'http.server',
+    span_id: expect.stringMatching(/[a-f0-9]{16}/),
+    status: 'ok',
+    trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+    origin: 'auto.http.otel.http',
+  });
+
+  expect(transactionEvent).toEqual(
+    expect.objectContaining({
+      spans: expect.arrayContaining([
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: {
+            'sentry.origin': 'manual',
+            'fastify.type': 'middleware',
+            'plugin.name': 'fastify -> @fastify/middie',
+            'hook.name': 'onRequest',
+          },
+          description: 'middleware - runMiddie',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          origin: 'manual',
+        },
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: {
+            'sentry.origin': 'auto.http.otel.fastify',
+            'sentry.op': 'request_handler.fastify',
+            'plugin.name': 'fastify -> @fastify/middie',
+            'fastify.type': 'request_handler',
+            'http.route': '/test-transaction',
+          },
+          description: '@fastify/middie',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          op: 'request_handler.fastify',
+          origin: 'auto.http.otel.fastify',
+        },
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: {
+            'sentry.origin': 'auto.http.otel.nestjs',
+            'sentry.op': 'request_context.nestjs',
+            component: '@nestjs/core',
+            'nestjs.version': expect.any(String),
+            'nestjs.type': 'request_context',
+            'http.method': 'GET',
+            'http.url': '/test-transaction',
+            'http.route': '/test-transaction',
+            'nestjs.controller': 'AppController',
+            'nestjs.callback': 'testTransaction',
+            url: '/test-transaction',
+          },
+          description: 'GET /test-transaction',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          op: 'request_context.nestjs',
+          origin: 'auto.http.otel.nestjs',
+        },
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: {
+            'sentry.origin': 'auto.middleware.nestjs',
+            'sentry.op': 'middleware.nestjs',
+          },
+          description: 'SentryTracingInterceptor',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          op: 'middleware.nestjs',
+          origin: 'auto.middleware.nestjs',
+        },
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: {
+            'sentry.origin': 'auto.middleware.nestjs',
+            'sentry.op': 'middleware.nestjs',
+          },
+          description: 'SentryTracingInterceptor',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          op: 'middleware.nestjs',
+          origin: 'auto.middleware.nestjs',
+        },
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: {
+            'sentry.origin': 'auto.http.otel.nestjs',
+            'sentry.op': 'handler.nestjs',
+            component: '@nestjs/core',
+            'nestjs.version': expect.any(String),
+            'nestjs.type': 'handler',
+            'nestjs.callback': 'testTransaction',
+          },
+          description: 'testTransaction',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          op: 'handler.nestjs',
+          origin: 'auto.http.otel.nestjs',
+        },
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: { 'sentry.origin': 'manual' },
+          description: 'test-span',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          origin: 'manual',
+        },
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: { 'sentry.origin': 'manual' },
+          description: 'child-span',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          origin: 'manual',
+        },
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: {
+            'sentry.origin': 'auto.middleware.nestjs',
+            'sentry.op': 'middleware.nestjs',
+          },
+          description: 'Interceptors - After Route',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          op: 'middleware.nestjs',
+          origin: 'auto.middleware.nestjs',
+        },
+      ]),
+      transaction: 'GET /test-transaction',
+      type: 'transaction',
+      transaction_info: {
+        source: 'route',
+      },
+    }),
+  );
+});
+
+test('API route transaction includes nest middleware span. Spans created in and after middleware are nested correctly', async ({
+  baseURL,
+}) => {
+  const pageloadTransactionEventPromise = waitForTransaction('nestjs-fastify', transactionEvent => {
+    return (
+      transactionEvent?.contexts?.trace?.op === 'http.server' &&
+      transactionEvent?.transaction === 'GET /test-middleware-instrumentation'
+    );
+  });
+
+  const response = await fetch(`${baseURL}/test-middleware-instrumentation`);
+  expect(response.status).toBe(200);
+
+  const transactionEvent = await pageloadTransactionEventPromise;
+
+  expect(transactionEvent).toEqual(
+    expect.objectContaining({
+      spans: expect.arrayContaining([
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: {
+            'sentry.op': 'middleware.nestjs',
+            'sentry.origin': 'auto.middleware.nestjs',
+          },
+          description: 'ExampleMiddleware',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          op: 'middleware.nestjs',
+          origin: 'auto.middleware.nestjs',
+        },
+      ]),
+    }),
+  );
+
+  const exampleMiddlewareSpan = transactionEvent.spans.find(span => span.description === 'ExampleMiddleware');
+  const exampleMiddlewareSpanId = exampleMiddlewareSpan?.span_id;
+
+  expect(transactionEvent).toEqual(
+    expect.objectContaining({
+      spans: expect.arrayContaining([
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: expect.any(Object),
+          description: 'test-controller-span',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          origin: 'manual',
+        },
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: expect.any(Object),
+          description: 'test-middleware-span',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          origin: 'manual',
+        },
+      ]),
+    }),
+  );
+
+  // verify correct span parent-child relationships
+  const testMiddlewareSpan = transactionEvent.spans.find(span => span.description === 'test-middleware-span');
+  const testControllerSpan = transactionEvent.spans.find(span => span.description === 'test-controller-span');
+
+  // 'ExampleMiddleware' is the parent of 'test-middleware-span'
+  expect(testMiddlewareSpan.parent_span_id).toBe(exampleMiddlewareSpanId);
+
+  // 'ExampleMiddleware' is NOT the parent of 'test-controller-span'
+  expect(testControllerSpan.parent_span_id).not.toBe(exampleMiddlewareSpanId);
+});
+
+test('API route transaction includes nest guard span and span started in guard is nested correctly', async ({
+  baseURL,
+}) => {
+  const transactionEventPromise = waitForTransaction('nestjs-fastify', transactionEvent => {
+    return (
+      transactionEvent?.contexts?.trace?.op === 'http.server' &&
+      transactionEvent?.transaction === 'GET /test-guard-instrumentation'
+    );
+  });
+
+  const response = await fetch(`${baseURL}/test-guard-instrumentation`);
+  expect(response.status).toBe(200);
+
+  const transactionEvent = await transactionEventPromise;
+
+  expect(transactionEvent).toEqual(
+    expect.objectContaining({
+      spans: expect.arrayContaining([
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: {
+            'sentry.op': 'middleware.nestjs',
+            'sentry.origin': 'auto.middleware.nestjs',
+          },
+          description: 'ExampleGuard',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          op: 'middleware.nestjs',
+          origin: 'auto.middleware.nestjs',
+        },
+      ]),
+    }),
+  );
+
+  const exampleGuardSpan = transactionEvent.spans.find(span => span.description === 'ExampleGuard');
+  const exampleGuardSpanId = exampleGuardSpan?.span_id;
+
+  expect(transactionEvent).toEqual(
+    expect.objectContaining({
+      spans: expect.arrayContaining([
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: expect.any(Object),
+          description: 'test-guard-span',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          origin: 'manual',
+        },
+      ]),
+    }),
+  );
+
+  // verify correct span parent-child relationships
+  const testGuardSpan = transactionEvent.spans.find(span => span.description === 'test-guard-span');
+
+  // 'ExampleGuard' is the parent of 'test-guard-span'
+  expect(testGuardSpan.parent_span_id).toBe(exampleGuardSpanId);
+});
+
+test('API route transaction includes nest pipe span for valid request', async ({ baseURL }) => {
+  const transactionEventPromise = waitForTransaction('nestjs-fastify', transactionEvent => {
+    return (
+      transactionEvent?.contexts?.trace?.op === 'http.server' &&
+      transactionEvent?.transaction === 'GET /test-pipe-instrumentation/:id' &&
+      transactionEvent?.request?.url?.includes('/test-pipe-instrumentation/123')
+    );
+  });
+
+  const response = await fetch(`${baseURL}/test-pipe-instrumentation/123`);
+  expect(response.status).toBe(200);
+
+  const transactionEvent = await transactionEventPromise;
+
+  expect(transactionEvent).toEqual(
+    expect.objectContaining({
+      spans: expect.arrayContaining([
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: {
+            'sentry.op': 'middleware.nestjs',
+            'sentry.origin': 'auto.middleware.nestjs',
+          },
+          description: 'ParseIntPipe',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          op: 'middleware.nestjs',
+          origin: 'auto.middleware.nestjs',
+        },
+      ]),
+    }),
+  );
+});
+
+test('API route transaction includes nest pipe span for invalid request', async ({ baseURL }) => {
+  const transactionEventPromise = waitForTransaction('nestjs-fastify', transactionEvent => {
+    return (
+      transactionEvent?.contexts?.trace?.op === 'http.server' &&
+      transactionEvent?.transaction === 'GET /test-pipe-instrumentation/:id' &&
+      transactionEvent?.request?.url?.includes('/test-pipe-instrumentation/abc')
+    );
+  });
+
+  const response = await fetch(`${baseURL}/test-pipe-instrumentation/abc`);
+  expect(response.status).toBe(400);
+
+  const transactionEvent = await transactionEventPromise;
+
+  expect(transactionEvent).toEqual(
+    expect.objectContaining({
+      spans: expect.arrayContaining([
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: {
+            'sentry.op': 'middleware.nestjs',
+            'sentry.origin': 'auto.middleware.nestjs',
+          },
+          description: 'ParseIntPipe',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'unknown_error',
+          op: 'middleware.nestjs',
+          origin: 'auto.middleware.nestjs',
+        },
+      ]),
+    }),
+  );
+});
+
+test('API route transaction includes nest interceptor spans before route execution. Spans created in and after interceptor are nested correctly', async ({
+  baseURL,
+}) => {
+  const pageloadTransactionEventPromise = waitForTransaction('nestjs-fastify', transactionEvent => {
+    return (
+      transactionEvent?.contexts?.trace?.op === 'http.server' &&
+      transactionEvent?.transaction === 'GET /test-interceptor-instrumentation'
+    );
+  });
+
+  const response = await fetch(`${baseURL}/test-interceptor-instrumentation`);
+  expect(response.status).toBe(200);
+
+  const transactionEvent = await pageloadTransactionEventPromise;
+
+  // check if interceptor spans before route execution exist
+  expect(transactionEvent).toEqual(
+    expect.objectContaining({
+      spans: expect.arrayContaining([
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: {
+            'sentry.op': 'middleware.nestjs',
+            'sentry.origin': 'auto.middleware.nestjs',
+          },
+          description: 'ExampleInterceptor1',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          op: 'middleware.nestjs',
+          origin: 'auto.middleware.nestjs',
+        },
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: {
+            'sentry.op': 'middleware.nestjs',
+            'sentry.origin': 'auto.middleware.nestjs',
+          },
+          description: 'ExampleInterceptor2',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          op: 'middleware.nestjs',
+          origin: 'auto.middleware.nestjs',
+        },
+      ]),
+    }),
+  );
+
+  // get interceptor spans
+  const exampleInterceptor1Span = transactionEvent.spans.find(span => span.description === 'ExampleInterceptor1');
+  const exampleInterceptor1SpanId = exampleInterceptor1Span?.span_id;
+  const exampleInterceptor2Span = transactionEvent.spans.find(span => span.description === 'ExampleInterceptor2');
+  const exampleInterceptor2SpanId = exampleInterceptor2Span?.span_id;
+
+  // check if manually started spans exist
+  expect(transactionEvent).toEqual(
+    expect.objectContaining({
+      spans: expect.arrayContaining([
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: expect.any(Object),
+          description: 'test-controller-span',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          origin: 'manual',
+        },
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: expect.any(Object),
+          description: 'test-interceptor-span-1',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          origin: 'manual',
+        },
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: expect.any(Object),
+          description: 'test-interceptor-span-2',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          origin: 'manual',
+        },
+      ]),
+    }),
+  );
+
+  // verify correct span parent-child relationships
+  const testInterceptor1Span = transactionEvent.spans.find(span => span.description === 'test-interceptor-span-1');
+  const testInterceptor2Span = transactionEvent.spans.find(span => span.description === 'test-interceptor-span-2');
+  const testControllerSpan = transactionEvent.spans.find(span => span.description === 'test-controller-span');
+
+  // 'ExampleInterceptor1' is the parent of 'test-interceptor-span-1'
+  expect(testInterceptor1Span.parent_span_id).toBe(exampleInterceptor1SpanId);
+
+  // 'ExampleInterceptor1' is NOT the parent of 'test-controller-span'
+  expect(testControllerSpan.parent_span_id).not.toBe(exampleInterceptor1SpanId);
+
+  // 'ExampleInterceptor2' is the parent of 'test-interceptor-span-2'
+  expect(testInterceptor2Span.parent_span_id).toBe(exampleInterceptor2SpanId);
+
+  // 'ExampleInterceptor2' is NOT the parent of 'test-controller-span'
+  expect(testControllerSpan.parent_span_id).not.toBe(exampleInterceptor2SpanId);
+});
+
+test('API route transaction includes exactly one nest interceptor span after route execution. Spans created in controller and in interceptor are nested correctly', async ({
+  baseURL,
+}) => {
+  const pageloadTransactionEventPromise = waitForTransaction('nestjs-fastify', transactionEvent => {
+    return (
+      transactionEvent?.contexts?.trace?.op === 'http.server' &&
+      transactionEvent?.transaction === 'GET /test-interceptor-instrumentation'
+    );
+  });
+
+  const response = await fetch(`${baseURL}/test-interceptor-instrumentation`);
+  expect(response.status).toBe(200);
+
+  const transactionEvent = await pageloadTransactionEventPromise;
+
+  // check if interceptor spans after route execution exist
+  expect(transactionEvent).toEqual(
+    expect.objectContaining({
+      spans: expect.arrayContaining([
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: {
+            'sentry.op': 'middleware.nestjs',
+            'sentry.origin': 'auto.middleware.nestjs',
+          },
+          description: 'Interceptors - After Route',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          op: 'middleware.nestjs',
+          origin: 'auto.middleware.nestjs',
+        },
+      ]),
+    }),
+  );
+
+  // check that exactly one after route span is sent
+  const allInterceptorSpansAfterRoute = transactionEvent.spans.filter(
+    span => span.description === 'Interceptors - After Route',
+  );
+  expect(allInterceptorSpansAfterRoute.length).toBe(1);
+
+  // get interceptor span
+  const exampleInterceptorSpanAfterRoute = transactionEvent.spans.find(
+    span => span.description === 'Interceptors - After Route',
+  );
+  const exampleInterceptorSpanAfterRouteId = exampleInterceptorSpanAfterRoute?.span_id;
+
+  // check if manually started span in interceptor after route exists
+  expect(transactionEvent).toEqual(
+    expect.objectContaining({
+      spans: expect.arrayContaining([
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: expect.any(Object),
+          description: 'test-interceptor-span-after-route',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          origin: 'manual',
+        },
+      ]),
+    }),
+  );
+
+  // verify correct span parent-child relationships
+  const testInterceptorSpanAfterRoute = transactionEvent.spans.find(
+    span => span.description === 'test-interceptor-span-after-route',
+  );
+  const testControllerSpan = transactionEvent.spans.find(span => span.description === 'test-controller-span');
+
+  // 'Interceptor - After Route' is the parent of 'test-interceptor-span-after-route'
+  expect(testInterceptorSpanAfterRoute.parent_span_id).toBe(exampleInterceptorSpanAfterRouteId);
+
+  // 'Interceptor - After Route' is NOT the parent of 'test-controller-span'
+  expect(testControllerSpan.parent_span_id).not.toBe(exampleInterceptorSpanAfterRouteId);
+});
+
+test('API route transaction includes nest async interceptor spans before route execution. Spans created in and after async interceptor are nested correctly', async ({
+  baseURL,
+}) => {
+  const pageloadTransactionEventPromise = waitForTransaction('nestjs-fastify', transactionEvent => {
+    return (
+      transactionEvent?.contexts?.trace?.op === 'http.server' &&
+      transactionEvent?.transaction === 'GET /test-async-interceptor-instrumentation'
+    );
+  });
+
+  const response = await fetch(`${baseURL}/test-async-interceptor-instrumentation`);
+  expect(response.status).toBe(200);
+
+  const transactionEvent = await pageloadTransactionEventPromise;
+
+  // check if interceptor spans before route execution exist
+  expect(transactionEvent).toEqual(
+    expect.objectContaining({
+      spans: expect.arrayContaining([
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: {
+            'sentry.op': 'middleware.nestjs',
+            'sentry.origin': 'auto.middleware.nestjs',
+          },
+          description: 'AsyncInterceptor',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          op: 'middleware.nestjs',
+          origin: 'auto.middleware.nestjs',
+        },
+      ]),
+    }),
+  );
+
+  // get interceptor spans
+  const exampleAsyncInterceptor = transactionEvent.spans.find(span => span.description === 'AsyncInterceptor');
+  const exampleAsyncInterceptorSpanId = exampleAsyncInterceptor?.span_id;
+
+  // check if manually started spans exist
+  expect(transactionEvent).toEqual(
+    expect.objectContaining({
+      spans: expect.arrayContaining([
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: expect.any(Object),
+          description: 'test-controller-span',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          origin: 'manual',
+        },
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: expect.any(Object),
+          description: 'test-async-interceptor-span',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          origin: 'manual',
+        },
+      ]),
+    }),
+  );
+
+  // verify correct span parent-child relationships
+  const testAsyncInterceptorSpan = transactionEvent.spans.find(
+    span => span.description === 'test-async-interceptor-span',
+  );
+  const testControllerSpan = transactionEvent.spans.find(span => span.description === 'test-controller-span');
+
+  // 'AsyncInterceptor' is the parent of 'test-async-interceptor-span'
+  expect(testAsyncInterceptorSpan.parent_span_id).toBe(exampleAsyncInterceptorSpanId);
+
+  // 'AsyncInterceptor' is NOT the parent of 'test-controller-span'
+  expect(testControllerSpan.parent_span_id).not.toBe(exampleAsyncInterceptorSpanId);
+});
+
+test('API route transaction includes exactly one nest async interceptor span after route execution. Spans created in controller and in async interceptor are nested correctly', async ({
+  baseURL,
+}) => {
+  const pageloadTransactionEventPromise = waitForTransaction('nestjs-fastify', transactionEvent => {
+    return (
+      transactionEvent?.contexts?.trace?.op === 'http.server' &&
+      transactionEvent?.transaction === 'GET /test-async-interceptor-instrumentation'
+    );
+  });
+
+  const response = await fetch(`${baseURL}/test-async-interceptor-instrumentation`);
+  expect(response.status).toBe(200);
+
+  const transactionEvent = await pageloadTransactionEventPromise;
+
+  // check if interceptor spans after route execution exist
+  expect(transactionEvent).toEqual(
+    expect.objectContaining({
+      spans: expect.arrayContaining([
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: {
+            'sentry.op': 'middleware.nestjs',
+            'sentry.origin': 'auto.middleware.nestjs',
+          },
+          description: 'Interceptors - After Route',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          op: 'middleware.nestjs',
+          origin: 'auto.middleware.nestjs',
+        },
+      ]),
+    }),
+  );
+
+  // check that exactly one after route span is sent
+  const allInterceptorSpansAfterRoute = transactionEvent.spans.filter(
+    span => span.description === 'Interceptors - After Route',
+  );
+  expect(allInterceptorSpansAfterRoute.length).toBe(1);
+
+  // get interceptor span
+  const exampleInterceptorSpanAfterRoute = transactionEvent.spans.find(
+    span => span.description === 'Interceptors - After Route',
+  );
+  const exampleInterceptorSpanAfterRouteId = exampleInterceptorSpanAfterRoute?.span_id;
+
+  // check if manually started span in interceptor after route exists
+  expect(transactionEvent).toEqual(
+    expect.objectContaining({
+      spans: expect.arrayContaining([
+        {
+          span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+          data: expect.any(Object),
+          description: 'test-async-interceptor-span-after-route',
+          parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+          start_timestamp: expect.any(Number),
+          timestamp: expect.any(Number),
+          status: 'ok',
+          origin: 'manual',
+        },
+      ]),
+    }),
+  );
+
+  // verify correct span parent-child relationships
+  const testInterceptorSpanAfterRoute = transactionEvent.spans.find(
+    span => span.description === 'test-async-interceptor-span-after-route',
+  );
+  const testControllerSpan = transactionEvent.spans.find(span => span.description === 'test-controller-span');
+
+  // 'Interceptor - After Route' is the parent of 'test-interceptor-span-after-route'
+  expect(testInterceptorSpanAfterRoute.parent_span_id).toBe(exampleInterceptorSpanAfterRouteId);
+
+  // 'Interceptor - After Route' is NOT the parent of 'test-controller-span'
+  expect(testControllerSpan.parent_span_id).not.toBe(exampleInterceptorSpanAfterRouteId);
+});
+
+test('Calling use method on service with Injectable decorator returns 200', async ({ baseURL }) => {
+  const response = await fetch(`${baseURL}/test-service-use`);
+  expect(response.status).toBe(200);
+});
+
+test('Calling transform method on service with Injectable decorator returns 200', async ({ baseURL }) => {
+  const response = await fetch(`${baseURL}/test-service-transform`);
+  expect(response.status).toBe(200);
+});
+
+test('Calling intercept method on service with Injectable decorator returns 200', async ({ baseURL }) => {
+  const response = await fetch(`${baseURL}/test-service-intercept`);
+  expect(response.status).toBe(200);
+});
+
+test('Calling canActivate method on service with Injectable decorator returns 200', async ({ baseURL }) => {
+  const response = await fetch(`${baseURL}/test-service-canActivate`);
+  expect(response.status).toBe(200);
+});
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tsconfig.build.json b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tsconfig.build.json
new file mode 100644
index 000000000000..64f86c6bd2bb
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tsconfig.build.json
@@ -0,0 +1,4 @@
+{
+  "extends": "./tsconfig.json",
+  "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
+}
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tsconfig.json b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tsconfig.json
new file mode 100644
index 000000000000..797d8abe0ead
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tsconfig.json
@@ -0,0 +1,21 @@
+{
+  "compilerOptions": {
+    "module": "CommonJS",
+    "declaration": true,
+    "removeComments": true,
+    "emitDecoratorMetadata": true,
+    "experimentalDecorators": true,
+    "allowSyntheticDefaultImports": true,
+    "target": "ES2021",
+    "sourceMap": true,
+    "outDir": "./dist",
+    "baseUrl": "./",
+    "incremental": true,
+    "skipLibCheck": true,
+    "strictNullChecks": false,
+    "noImplicitAny": false,
+    "strictBindCallApply": false,
+    "forceConsistentCasingInFileNames": false,
+    "noFallthroughCasesInSwitch": false,
+  }
+}
diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts
index 55e168c53963..e75054d3391d 100644
--- a/packages/nestjs/src/setup.ts
+++ b/packages/nestjs/src/setup.ts
@@ -23,6 +23,23 @@ import {
 import type { Observable } from 'rxjs';
 import { isExpectedError } from './helpers';
 
+// Partial extract of FastifyRequest interface
+// https://github.com/fastify/fastify/blob/87f9f20687c938828f1138f91682d568d2a31e53/types/request.d.ts#L41
+interface FastifyRequest {
+  routeOptions?: {
+    method?: string;
+    url?: string;
+  };
+}
+
+// Partial extract of ExpressRequest interface
+interface ExpressRequest {
+  route?: {
+    path?: string;
+  };
+  method?: string;
+}
+
 /**
  * Note: We cannot use @ syntax to add the decorators, so we add them directly below the classes as function wrappers.
  */
@@ -52,11 +69,15 @@ class SentryTracingInterceptor implements NestInterceptor {
     }
 
     if (context.getType() === 'http') {
-      const req = context.switchToHttp().getRequest();
-      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
-      if (req.route) {
-        // eslint-disable-next-line @sentry-internal/sdk/no-optional-chaining,@typescript-eslint/no-unsafe-member-access
-        getIsolationScope().setTransactionName(`${req.method?.toUpperCase() || 'GET'} ${req.route.path}`);
+      const req = context.switchToHttp().getRequest() as FastifyRequest | ExpressRequest;
+      if ('routeOptions' in req && req.routeOptions && req.routeOptions.url) {
+        // fastify case
+        getIsolationScope().setTransactionName(
+          `${(req.routeOptions.method || 'GET').toUpperCase()} ${req.routeOptions.url}`,
+        );
+      } else if ('route' in req && req.route && req.route.path) {
+        // express case
+        getIsolationScope().setTransactionName(`${(req.method || 'GET').toUpperCase()} ${req.route.path}`);
       }
     }
 
diff --git a/packages/node/src/integrations/tracing/nest/nest.ts b/packages/node/src/integrations/tracing/nest/nest.ts
index 1c63c22783aa..b5c9ea4bb61f 100644
--- a/packages/node/src/integrations/tracing/nest/nest.ts
+++ b/packages/node/src/integrations/tracing/nest/nest.ts
@@ -87,8 +87,15 @@ export function setupNestErrorHandler(app: MinimalNestJsApp, baseFilter: NestJsE
       }
 
       if (context.getType() === 'http') {
+        // getRequest() returns either a FastifyRequest or ExpressRequest, depending on the used adapter
         const req = context.switchToHttp().getRequest();
-        if (req.route) {
+        if ('routeOptions' in req && req.routeOptions && req.routeOptions.url) {
+          // fastify case
+          getIsolationScope().setTransactionName(
+            `${req.routeOptions.method?.toUpperCase() || 'GET'} ${req.routeOptions.url}`,
+          );
+        } else if ('route' in req && req.route && req.route.path) {
+          // express case
           getIsolationScope().setTransactionName(`${req.method?.toUpperCase() || 'GET'} ${req.route.path}`);
         }
       }
diff --git a/packages/node/src/integrations/tracing/nest/types.ts b/packages/node/src/integrations/tracing/nest/types.ts
index ed7e968a9600..a983832ac8c6 100644
--- a/packages/node/src/integrations/tracing/nest/types.ts
+++ b/packages/node/src/integrations/tracing/nest/types.ts
@@ -1,5 +1,22 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
 
+// Partial extract of FastifyRequest interface
+// https://github.com/fastify/fastify/blob/87f9f20687c938828f1138f91682d568d2a31e53/types/request.d.ts#L41
+interface FastifyRequest {
+  routeOptions?: {
+    method?: string;
+    url?: string;
+  };
+}
+
+// Partial extract of ExpressRequest interface
+interface ExpressRequest {
+  route?: {
+    path?: string;
+  };
+  method?: string;
+}
+
 export interface MinimalNestJsExecutionContext {
   getType: () => string;
 
@@ -7,12 +24,7 @@ export interface MinimalNestJsExecutionContext {
     // minimal request object
     // according to official types, all properties are required but
     // let's play it safe and assume they're optional
-    getRequest: () => {
-      route?: {
-        path?: string;
-      };
-      method?: string;
-    };
+    getRequest: () => FastifyRequest | ExpressRequest;
   };
 
   _sentryInterceptorInstrumented?: boolean;