Trigger.dev launched in February 2023 as a “developer-first Zapier alternative” and pulled 745 points on Hacker News. Eight months later, V2 repositioned as a “Temporal alternative for TypeScript” and scored 172 points. That pivot exposes a real architectural gap: teams building long-running workflows in TypeScript want durable execution without learning Go or adopting a separate workflow DSL.
The shift from event-driven triggers to durable execution is not cosmetic. It changes how you handle state persistence, retry semantics, and task isolation. Trigger.dev V2 bets that TypeScript developers will trade Temporal’s battle-tested workflow engine for native language ergonomics and managed infrastructure.
What Changed Between V1 and V2
V1 focused on webhook-driven automation. You connected external services, defined event handlers, and chained actions. Think Zapier but with code. The feedback loop revealed that developers needed something different: long-running tasks that survive process crashes, retries that respect idempotency, and observability for multi-step workflows.
V2 reoriented around durable execution:
- Task primitives replace event handlers. A task is a TypeScript function wrapped in retry logic, state persistence, and timeout management.
- Managed execution handles container orchestration, so you deploy code without configuring Kubernetes or serverless cold starts.
- Observability built in with real-time tracing, step-level logs, and queue metrics.
The V2 architecture targets the same use cases as Temporal (background jobs, scheduled workflows, human-in-the-loop processes) but removes the polyglot complexity. You write TypeScript, deploy to Trigger.dev’s platform, and get durable execution without running your own Temporal cluster.
Durable Execution Plumbing
Durable execution means a task can pause, crash, or wait for external input and resume exactly where it left off. Temporal achieves this with event sourcing: every workflow step emits an event, and replay reconstructs state. Trigger.dev takes a different path.
State Persistence Model
Trigger.dev persists task state at explicit checkpoints. When you call await inside a task, the runtime snapshots the execution context (variables, call stack, pending promises) and writes it to durable storage. If the container dies, the next worker picks up the snapshot and resumes.
This differs from Temporal’s replay model:
| Aspect | Temporal | Trigger.dev V2 |
|---|---|---|
| State mechanism | Event sourcing with replay | Checkpoint snapshots |
| Determinism requirement | Strict (no random, no Date.now) | Relaxed (snapshots capture non-deterministic state) |
| Replay overhead | Grows with workflow history | Fixed per checkpoint |
| Debugging | Replay entire history | Inspect snapshot at failure point |
The checkpoint model trades replay flexibility for simpler mental models. You don’t need to worry about deterministic execution as long as your task can serialize its state.
Retry Semantics
Trigger.dev exposes retry configuration at the task level:
export const processOrder = task({
id: "process-order",
retry: {
maxAttempts: 5,
factor: 2,
minTimeout: 1000,
maxTimeout: 60000,
randomize: true
},
run: async ({ orderId }: { orderId: string }) => {
const order = await fetchOrder(orderId);
await chargePayment(order.paymentMethod);
await sendConfirmation(order.email);
return { status: "completed" };
}
});
Retries happen automatically on uncaught exceptions. The runtime applies exponential backoff with jitter. If a task exceeds maxAttempts, it moves to a dead-letter queue.
Temporal’s retry logic is more granular. You can define retry policies per activity, set different timeouts for start-to-close vs. schedule-to-start, and handle retries inside workflow logic. Trigger.dev simplifies this: one retry policy per task, applied uniformly.
Isolation and Execution Shape
Trigger.dev V2 runs tasks in ephemeral containers. Each task invocation spawns a new container, executes the function, and tears down. This provides process-level isolation but introduces cold start latency (typically 200-500ms for TypeScript runtimes).
For high-throughput workloads, Trigger.dev offers concurrency controls:
- Queue-level concurrency limits how many tasks from a queue run simultaneously.
- Task-level concurrency restricts parallel executions of a specific task.
This prevents resource exhaustion but requires tuning. If you set concurrency too low, tasks queue up. Too high, and you hit rate limits on downstream APIs.
Temporal uses worker pools with long-lived processes, so cold starts are rare. You pay for that with operational complexity: managing worker deployments, scaling policies, and cluster health.
Observability and Debugging
Trigger.dev’s dashboard shows real-time task execution:
- Step-level traces break down each
awaitcall with timing and payload. - Queue metrics display pending tasks, throughput, and error rates.
- Logs stream in real time, tagged by task ID and run ID.
When a task fails, you inspect the snapshot at the failure point. You see variable values, stack traces, and the exact line that threw. This beats replaying an entire workflow history to reproduce a bug.
Temporal’s observability is more powerful but heavier. You query workflow history with SQL-like syntax, visualize execution graphs, and replay workflows in a local environment. For complex workflows with hundreds of steps, this depth is necessary. For simpler tasks, it’s overkill.
When TypeScript Ergonomics Matter
Temporal’s Go-first design creates friction for JavaScript teams. You write workflows in TypeScript, but the Temporal server runs in Go. Type mismatches between the SDK and server surface as runtime errors. Debugging requires understanding both languages.
Trigger.dev eliminates that boundary. The entire stack is TypeScript: task definitions, runtime, and dashboard. You use standard npm packages, async/await patterns, and familiar debugging tools.
This matters for teams that:
- Build AI agents with multi-step reasoning loops.
- Orchestrate API calls with retries and rate limiting.
- Run scheduled jobs that depend on external state.
If your workflow logic fits in a single language, the polyglot tax of Temporal is wasted complexity.
Failure Modes and Operational Risks
Trigger.dev’s managed platform hides infrastructure but introduces vendor lock-in. If the service goes down, your tasks stop. If you hit usage limits, you wait for quota increases. Self-hosting is possible (the project is open source), but you lose the managed execution layer.
Checkpoint-based state persistence has edge cases:
- Large state snapshots slow down serialization. If your task holds a 10MB object in memory, every checkpoint writes 10MB.
- Non-serializable objects (like database connections or file handles) break snapshots. You must close and reopen them after each checkpoint.
- Snapshot corruption can happen if the runtime crashes mid-write. Trigger.dev mitigates this with atomic writes, but the risk exists.
Temporal’s event sourcing avoids these issues by replaying deterministic code. The trade-off is strict determinism requirements: no random numbers, no wall-clock time, no external I/O without activities.
Architecture Comparison
Here’s how the two systems handle a typical workflow: fetch data from an API, process it, and write results to a database.
Temporal approach:
- Define a workflow function that orchestrates activities.
- Each activity (API call, processing, database write) runs in a separate worker.
- The workflow emits events for each activity result.
- If a worker crashes, replay reconstructs state from events.
Trigger.dev approach:
- Define a task function with all steps inline.
- Each
awaittriggers a checkpoint. - If the container crashes, the next worker loads the checkpoint and resumes.
Temporal’s separation of workflows and activities enables fine-grained retries and parallel execution. Trigger.dev’s inline model is simpler but less flexible.
Code Example: AI Agent with Tool Calling
Trigger.dev’s V2 API fits multi-step AI workflows naturally:
export const researchAgent = task({
id: "research-agent",
run: async ({ topic }: { topic: string }) => {
const messages: CoreMessage[] = [
{ role: "user", content: `Research: ${topic}` }
];
for (let i = 0; i < 10; i++) {
const { text, toolCalls, steps } = await generateText({
model: anthropic("claude-opus-4-20250514"),
system: "You are a research assistant with web access.",
messages,
tools: { search, browse, analyze },
maxSteps: 5
});
if (!toolCalls.length) {
return { summary: text, stepsUsed: steps.length };
}
for (const call of toolCalls) {
const result = await executeTool(call);
messages.push({ role: "tool", content: result });
}
}
}
});
Each await generateText and await executeTool creates a checkpoint. If the model API times out or the container crashes, the task resumes with the message history intact. You don’t manually persist state or handle retries.
This pattern works for:
- Multi-turn agent conversations with external tool calls.
- Workflows that wait for human approval before proceeding.
- Scheduled tasks that aggregate data over hours or days.
Technical Verdict
Use Trigger.dev V2 when:
- Your team is TypeScript-native and wants to avoid polyglot orchestration.
- Workflows are linear or moderately branching (under 50 steps).
- You prefer managed infrastructure over self-hosted clusters.
- Cold start latency (200-500ms) is acceptable for your workload.
Avoid it when:
- You need sub-100ms task latency or high-throughput event processing.
- Workflows have complex branching, parallel execution, or saga patterns.
- You require strict determinism for compliance or auditing.
- You already run Temporal and have operational expertise.
Trigger.dev’s pivot from Zapier-style triggers to durable execution reflects real demand for TypeScript-native workflow tooling. The checkpoint model simplifies state management but sacrifices the replay flexibility that makes Temporal powerful for complex workflows. For AI agents, background jobs, and scheduled tasks, the trade-off is often worth it.