Skip to main content
General
Background Tasks

trigger.dev

Integrate trigger.dev with your application for reliable background task processing.

Open MarkdownFull AI corpusFeedback

trigger.dev is an open-source background jobs framework that lets you write reliable workflows in plain async code.

Setup

Visit trigger.dev and create a free account. Create a new project and note down your API key.

Add your trigger.dev API key to your environment variables:

.env
TRIGGER_SECRET_KEY=your_secret_key_here

For production, make sure to add the production API key to your deployment environment.

Install dependencies

Install the SDK, build package and CLI. Keep all three on the same version:

Terminal
npm install @trigger.dev/sdk@latest
npm install -D @trigger.dev/build@latest trigger.dev@latest

Commit the resolved versions from package.json and package-lock.json. Trigger.dev warns when the CLI, SDK and build package versions drift.

Configure trigger.dev

Create a trigger.config.ts file in the root of your project:

trigger.config.ts
import { defineConfig } from "@trigger.dev/sdk";

export default defineConfig({
  project: "your_project_id", // Replace with your actual project ID
  runtime: "node",
  logLevel: "log",
  maxDuration: 300,
  dirs: ["./src/trigger"],
});

Update your package.json to include trigger.dev scripts:

package.json
{
  "scripts": {
    "trigger:dev": "trigger dev",
    "trigger:deploy": "trigger deploy"
  }
}

Create your first task

Create a src/trigger directory and add your first task:

src/trigger/process-user-data.ts
import { task, logger, wait } from "@trigger.dev/sdk";
import * as z from "zod";

const ProcessUserDataSchema = z.object({
  userId: z.string(),
  operation: z.enum(["export", "analyze", "cleanup"]),
});

export const processUserDataTask = task({
  id: "process-user-data",
  run: async (payload: z.infer<typeof ProcessUserDataSchema>) => {
    const { userId, operation } = payload;

    logger.info("Starting user data processing", { userId, operation });

    switch (operation) {
      case "export":
        await wait.for({ seconds: 2 });
        logger.info("User data exported successfully");
        return { success: true, result: "Data exported to CSV" };

      case "analyze":
        await wait.for({ seconds: 5 });
        logger.info("User data analysis completed");
        return {
          success: true,
          result: { totalActions: 156, avgSessionTime: "4m 32s" },
        };

      case "cleanup":
        await wait.for({ seconds: 3 });
        logger.info("User data cleanup completed");
        return { success: true, result: "Removed 23 obsolete records" };

      default:
        throw new Error(`Unknown operation: ${operation}`);
    }
  },
});

Create a scheduled task:

src/trigger/daily-cleanup.ts
import { schedules, logger, wait } from "@trigger.dev/sdk";

export const dailyCleanupTask = schedules.task({
  id: "daily-cleanup",
  cron: "0 2 * * *",
  run: async () => {
    logger.info("Starting daily cleanup");

    // Cleanup old logs
    await wait.for({ seconds: 5 });
    logger.info("Logs cleaned up");

    // Cleanup temporary files
    await wait.for({ seconds: 3 });
    logger.info("Temp files cleaned up");

    // Generate daily reports
    await wait.for({ seconds: 8 });
    logger.info("Reports generated");

    return {
      success: true,
      cleanupTime: new Date().toISOString(),
      itemsProcessed: 1247,
    };
  },
});

A string cron is evaluated in UTC. Use the object form documented by Trigger.dev when the job must run in a named timezone. Reserve schedules.create() for dynamic schedules created by an authenticated server flow, not a fixed call executed when this module loads.

Test your task

You can test your tasks locally by running:

Terminal
npm run trigger:dev

This will deploy your tasks to trigger.dev in the development environment, allowing you to trigger them from the dashboard or programmatically.

Deploy your tasks

To deploy your tasks to production on trigger.dev, run:

Terminal
npm run trigger:deploy

You can also add this command as an automated deployment step in your CI/CD pipeline.

Add the TRIGGER_ACCESS_TOKEN secret to your repository secrets, which you can create in the trigger.dev dashboard.

.github/workflows/deploy-tasks.yml
name: Deploy to trigger.dev (prod)

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: lts/*
      - name: Install dependencies
        run: npm install
      - name: Deploy trigger tasks
        env:
          TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }}
        run: |
          npm run trigger:deploy

Triggering tasks

You can trigger tasks from your application using the trigger.dev SDK.

From an API route

Create an API route to handle task triggering:

app/api/tasks/process-user-data/route.ts
import { tasks } from "@trigger.dev/sdk";
import { NextResponse } from "next/server";
import * as z from "zod";
import { getSession } from "@/lib/auth/server";
import { processUserDataTask } from "@/src/trigger/process-user-data";

const processUserDataSchema = z.object({
  operation: z.enum(["export", "analyze", "cleanup"]),
});

export async function POST(request: Request) {
  const session = await getSession();

  if (!session) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const body = await request.json();
  const { operation } = processUserDataSchema.parse(body);

  const handle = await tasks.trigger<typeof processUserDataTask>(
    "process-user-data",
    { userId: session.user.id, operation },
  );

  return NextResponse.json({
    success: true,
    taskId: handle.id,
    message: "Background task started successfully",
  });
}

From a server action

app/actions/user-actions.ts
"use server";

import { tasks } from "@trigger.dev/sdk";
import { getSession } from "@/lib/auth/server";
import { processUserDataTask } from "@/src/trigger/process-user-data";

export async function processUserData(
  operation: "export" | "analyze" | "cleanup",
) {
  const session = await getSession();

  if (!session) {
    throw new Error("Unauthorized");
  }

  try {
    const handle = await tasks.trigger<typeof processUserDataTask>(
      "process-user-data",
      { userId: session.user.id, operation },
    );

    return {
      success: true,
      taskId: handle.id,
    };
  } catch (error) {
    console.error("Failed to trigger background task:", error);
    throw new Error("Failed to start background task");
  }
}

From the client

You can call the task endpoint from your React components:

components/process-data-button.tsx
"use client";

import { useMutation } from "@tanstack/react-query";

export function ProcessDataButton() {
  const { mutate: processData, isPending } = useMutation({
    mutationFn: async (operation: "export" | "analyze" | "cleanup") => {
      const response = await fetch("/api/tasks/process-user-data", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ operation }),
      });

      if (!response.ok) {
        throw new Error("Failed to start task");
      }

      return response.json();
    },
    onSuccess: (data) => {
      console.log("Task started:", data.taskId);
    },
  });

  return (
    <button
      onClick={() => processData("analyze")}
      disabled={isPending}
    >
      {isPending ? "Processing..." : "Analyze User Data"}
    </button>
  );
}

The authenticated server derives userId; the browser never chooses the task owner. For organization-scoped work, accept an organization ID only as a lookup key, verify membership with the shipped authorization helpers and persist the authorized tenant scope before triggering the task.

Monitoring and debugging

Dashboard access

Visit the trigger.dev dashboard to monitor your tasks:

  • View task execution logs and performance metrics
  • Track success and failure rates across all your tasks
  • Monitor task duration and resource usage
  • Replay failed tasks with a single click
  • Set up alerts for task failures or performance issues

Local development

During development, run your tasks locally while connected to trigger.dev:

Terminal
npm run trigger:dev

This allows you to:

  • Test tasks locally with real data
  • Debug with breakpoints and console logs
  • See immediate feedback as you develop

Best practices

Use descriptive task IDs

// ✅ Good - Clear and descriptive
id: 'user-data-export-csv';
id: 'weekly-newsletter-campaign';
id: 'cleanup-temp-files';

// ❌ Not so good - Generic and unclear
id: 'task1';
id: 'job';
id: 'process';

Include proper error handling

run: async (payload) => {
  try {
    const result = await processData(payload);
    logger.info("Task completed successfully", { result });
    return result;
  } catch (error) {
    logger.error("Task failed:", error.message);
    throw error; // Re-throw to trigger retry logic
  }
},

Use structured logging

logger.info('Processing started', {
  userId: payload.userId,
  operation: payload.operation,
  timestamp: new Date().toISOString()
});

Keep tasks focused

Instead of one massive task, create focused, single-purpose tasks that can be composed together for complex workflows.

Configure appropriate retries

Set retry policies based on your task's requirements:

// For critical operations
retry: {
  maxAttempts: 5,
  minTimeoutInMs: 2000,
  maxTimeoutInMs: 30000,
  factor: 2,
}

// For less critical operations
retry: {
  maxAttempts: 2,
  minTimeoutInMs: 1000,
  maxTimeoutInMs: 5000,
  factor: 1.5,
}

Next steps

With trigger.dev integrated into your application, you can now:

  • Handle long-running operations that would timeout in serverless functions
  • Schedule recurring tasks like reports, cleanups, and maintenance
  • Process background jobs reliably with automatic retries
  • Scale your application without worrying about task execution infrastructure

Ready to explore more advanced features? Check out the official documentation for additional capabilities like webhooks, batching, and custom integrations.