Trigger.dev started as a Zapier alternative in February 2023 (745 HN points), then pivoted seven months later to a Temporal alternative for TypeScript developers (172 points). The V2 rewrite signals a fundamental architectural shift: from webhook-driven integrations to durable execution primitives. The question is how they handle workflow state, retries, and long-running tasks without adopting Temporal’s event-sourcing model or requiring a separate Go-based orchestrator.
The Pivot: From Integrations to Execution Primitives
V1 focused on pre-built integrations and trigger definitions. V2 exposes lower-level primitives:
- Task definitions with automatic retry logic
- Durable delays that survive process restarts
- Concurrency controls and queue management
- Observability hooks for tracing and monitoring
The shift reflects what early users actually needed: not another integration marketplace, but a way to write reliable background jobs in the same language as their application code.
State Persistence Without Event Sourcing
Temporal persists every workflow decision as an immutable event log. Trigger.dev takes a checkpoint-based approach:
- Execution checkpoints: The engine snapshots task state at specific boundaries (before retries, after delays, at fan-out points).
- Idempotency keys: Each task invocation gets a unique identifier. Retries replay from the last successful checkpoint rather than re-executing the entire workflow.
- Database-backed state: Task state lives in Postgres or compatible stores. No separate event history service.
This trades Temporal’s full auditability for simpler infrastructure. You lose the ability to replay arbitrary historical states, but you gain a deployment model that fits existing TypeScript stacks.
TypeScript-Native Workflow Primitives
The V2 API exposes workflows as TypeScript functions decorated with task metadata:
import { task } from "@trigger.dev/sdk/v3";
export const processOrder = task({
id: "process-order",
retry: {
maxAttempts: 3,
factor: 2,
minTimeout: 1000,
},
run: async (payload: { orderId: string }) => {
// Step 1: Charge payment
const charge = await stripe.charges.create({
amount: payload.amount,
currency: "usd",
});
// Step 2: Wait for fulfillment (durable delay)
await wait.for({ seconds: 3600 });
// Step 3: Send confirmation
await sendEmail({
to: payload.email,
subject: "Order shipped",
});
return { orderId: payload.orderId, status: "complete" };
},
});
Key primitives:
wait.for(): Durable delays that persist across restarts. The engine checkpoints before the wait, then reschedules the continuation.retryconfig: Declarative backoff policies. The engine tracks attempt counts in the checkpoint.runfunction: Standard async TypeScript. No special DSL or code generation.
Scheduling and Fan-Out Patterns
Trigger.dev handles three common orchestration patterns:
Cron Schedules
export const dailyReport = task({
id: "daily-report",
schedule: {
cron: "0 9 * * *",
timezone: "America/New_York",
},
run: async () => {
// Durable cron without external scheduler
},
});
The engine stores schedule metadata in the task definition. No separate cron daemon required.
Batch Fan-Out
export const batchProcess = task({
id: "batch-process",
run: async (payload: { items: string[] }) => {
const results = await Promise.all(
payload.items.map((item) =>
processItem.triggerAndWait({ item })
)
);
return results;
},
});
triggerAndWait() spawns child tasks and blocks until all complete. The parent task checkpoints after each child finishes, so partial failures don’t lose progress.
Human-in-the-Loop
export const approvalFlow = task({
id: "approval-flow",
run: async (payload: { documentId: string }) => {
await sendApprovalRequest(payload.documentId);
// Wait up to 7 days for approval
const approved = await wait.forEvent({
event: "document.approved",
timeout: { days: 7 },
});
if (!approved) {
throw new Error("Approval timeout");
}
return { status: "approved" };
},
});
The wait.forEvent() primitive suspends the task until an external event arrives or the timeout expires. The engine checkpoints the suspended state and resumes when the event fires.
Versioning and Hot-Reloading
Temporal requires explicit workflow versioning because the orchestrator replays history against the current code. Trigger.dev avoids this by checkpointing execution state rather than decision logs.
When you deploy new task code:
- In-flight tasks continue running the old version. The checkpoint includes a reference to the deployed code version.
- New tasks start with the latest code.
- No replay conflicts because the engine doesn’t re-execute completed steps.
This simplifies deployments but creates a different problem: you can’t easily patch bugs in running workflows. If a task is suspended for days, it will resume with the old buggy code.
Tradeoffs: Language-Native vs. Polyglot Orchestrators
| Dimension | Trigger.dev (TypeScript-native) | Temporal (Polyglot) |
|---|---|---|
| Deployment | Single Node.js process | Separate orchestrator cluster + workers |
| State model | Checkpoint-based | Event-sourced history |
| Language support | TypeScript only | Go, Java, Python, PHP, .NET |
| Versioning | Code version per checkpoint | Explicit workflow versioning |
| Auditability | Task execution logs | Full decision history replay |
| Infra complexity | Postgres + task queue | Temporal server + Cassandra/Postgres |
| Failure recovery | Retry from last checkpoint | Replay from event log |
Trigger.dev wins on simplicity and TypeScript ergonomics. Temporal wins on auditability and cross-language workflows.
Observability and Failure Modes
Trigger.dev exposes real-time task traces through a web dashboard:
- Execution timeline: Shows each checkpoint, retry, and delay.
- Logs and errors: Captured per task invocation.
- Queue depth: Monitors backlog and concurrency limits.
Common failure modes:
- Checkpoint corruption: If the database write fails mid-checkpoint, the task may retry from an inconsistent state. Mitigation: transactional checkpoint writes.
- Clock skew: Durable delays rely on wall-clock time. If the system clock jumps, scheduled tasks may fire early or late.
- Memory leaks in long tasks: Unlike Temporal’s deterministic replay, Trigger.dev tasks run as normal async functions. A memory leak in a multi-hour task will eventually crash the worker.
- Versioning drift: If a task is suspended for weeks, it resumes with outdated code. No automatic migration path.
Security Boundaries
Trigger.dev runs user code in the same process as the orchestration engine. This creates two risks:
- Resource exhaustion: A runaway task can starve other tasks. Mitigation: per-task memory and CPU limits (requires containerization).
- Credential leakage: Tasks share the same runtime environment. Secrets must be scoped per task, not per process.
Temporal isolates workflow logic (deterministic, sandboxed) from activity logic (side effects, unrestricted). Trigger.dev collapses this boundary, trading isolation for simplicity.
Deployment Shape
Trigger.dev offers two deployment models:
Managed Cloud
- Hosted task queue and execution workers
- Automatic scaling based on queue depth
- Built-in observability dashboard
Self-Hosted
- Docker container with Postgres backend
- Requires manual scaling and monitoring
- Suitable for air-gapped or compliance-sensitive environments
Both models use the same TypeScript SDK. The main difference is who operates the infrastructure.
Technical Verdict
Use Trigger.dev when:
- Your team writes TypeScript and wants workflow primitives without learning a new orchestration DSL.
- You need durable retries and delays but don’t require full event-sourcing auditability.
- You want to deploy background jobs alongside your Next.js or Express app without running a separate orchestrator cluster.
- Your workflows are hours to days long, not weeks to months.
Avoid Trigger.dev when:
- You need cross-language workflows (Python ML models calling Java services).
- You require full audit trails for compliance (financial transactions, healthcare).
- Your workflows run for weeks and must survive code changes mid-execution.
- You need deterministic replay for debugging complex state machines.
Trigger.dev is a pragmatic choice for TypeScript-native teams building AI agents, media pipelines, or human-in-the-loop workflows. It trades Temporal’s rigor for deployment simplicity. If you can tolerate checkpoint-based recovery instead of event-sourced replay, the architecture fits cleanly into existing Node.js stacks.