Trigger.dev launched V2 after pivoting from a Zapier-style automation builder (V1) to a workflow orchestration platform. The HN community framed it as a “Temporal alternative for TypeScript devs,” exposing a real gap: developers want durable execution without Temporal’s operational overhead, but they need more than simple task queues. The question is whether Trigger.dev’s inability to run workflows across multiple languages or data centers matters for your use case.
This article compares the plumbing. How does each system persist workflow state, schedule retries, handle long-running tasks, and expose failure modes? What does deployment look like, and where do the trade-offs land?
State Persistence and Checkpointing
Temporal persists every workflow decision to an event history store. When a worker crashes, the new worker replays the entire event log to reconstruct in-memory state. Developers write deterministic workflow code, and the framework guarantees that replaying the same events produces the same state. This requires:
- A persistent database (Cassandra, PostgreSQL, or MySQL) for event storage
- Deterministic workflow functions (no random UUIDs, no direct I/O)
- Explicit activity boundaries for non-deterministic operations
Trigger.dev uses a different model. It checkpoints state automatically after each task step. Developers write normal TypeScript functions with async/await, and the SDK serializes the execution context at each await boundary. When a retry happens, the system resumes from the last checkpoint instead of replaying from the beginning.
Key difference: Temporal replays history to rebuild state. Trigger.dev snapshots state directly. This means Trigger.dev can handle non-deterministic code (random values, timestamps) without breaking retries, but it also means the state snapshot must be serializable. Complex objects with circular references or native handles won’t survive a checkpoint.
Retry and Failure Recovery
Both systems retry failed steps, but the mechanics differ.
Temporal retries:
- Activity retries are configurable per activity (max attempts, backoff strategy)
- Workflow retries happen at the workflow level, not per step
- Failed activities can return errors that the workflow handles explicitly
- Retry state lives in the event history, so changing retry policy mid-flight requires workflow versioning
Trigger.dev retries:
- Retries happen per task, with exponential backoff by default
- Developers can configure
retryoptions in the task definition - Failed tasks preserve partial progress via checkpoints
- Changing retry logic requires redeploying the task, but in-flight executions use the old logic
Trigger.dev’s checkpoint model means partial progress is always preserved. If a task completes 8 of 10 steps and crashes, the retry starts at step 9. Temporal requires you to structure workflows so that activities are idempotent, because a replay might re-execute the same activity code (though the framework skips the actual execution and returns the cached result from the event history).
Long-Running Task Handling
Temporal uses durable timers. A workflow can sleep for days or weeks, and the timer lives in the event history. When the timer fires, the workflow resumes. This works because the workflow itself is fundamentally a state machine, and the actual execution happens in workers that can start and stop freely.
Trigger.dev uses a similar approach but exposes it differently. Developers call wait.for() to pause execution, and the platform schedules a resume event. The task worker can shut down, and the platform will spin up a new worker when the wait completes. This is simpler to reason about than Temporal’s replay model, but it requires trusting the platform’s scheduler.
For webhooks or external events, Temporal uses signals (external messages that append to the event history). Trigger.dev uses wait.forEvent(), which registers a listener and suspends the task until the event arrives. Both approaches work, but Temporal’s signal model is more flexible because signals can arrive at any point in the workflow, not only at explicit wait points.
Deployment and Operational Shape
Temporal deployment requires:
- Temporal Server (Go binary or Kubernetes deployment)
- Persistent database for event history
- Worker processes (your code) that poll task queues
- Optional: Temporal UI, metrics exporters, and archival storage
Self-hosting means managing database backups, server upgrades, and worker autoscaling. Temporal Cloud eliminates server management but still requires deploying and scaling your own worker processes.
Trigger.dev deployment is simpler:
- Hosted control plane (managed by Trigger.dev)
- Your code runs in Trigger.dev-managed workers (or self-hosted workers in enterprise mode)
- No database to manage
- Built-in observability UI
The trade-off: you’re locked into Trigger.dev’s runtime. Temporal workers are simple polling processes that you can run anywhere. Trigger.dev workers run in the platform’s infrastructure (or your own Kubernetes cluster if you pay for self-hosting).
Observability and Debugging
Temporal exposes:
- Full event history for every workflow execution
- Workflow state at any point in time via replay
- Metrics via Prometheus exporters
- Stack traces for failed activities
- Built-in distributed tracing through event history correlation
Debugging a Temporal workflow means reading the event history and replaying it locally. This is powerful but requires understanding the replay model. The event history provides complete tracing across workflow boundaries because every decision and activity result is logged.
Trigger.dev exposes:
- Real-time task execution logs in the web UI
- Checkpoint snapshots showing state at each step
- Retry history and failure reasons
- Built-in tracing without extra instrumentation
- Task-level observability with automatic span creation
Trigger.dev’s UI is more accessible for developers who don’t want to learn a new debugging model. However, tracing across task boundaries requires correlating task IDs manually, since each task execution is treated as an independent unit. Temporal’s event history automatically links all activities within a workflow, making it easier to trace complex multi-step processes.
Architectural Comparison: Temporal vs Trigger.dev
| Dimension | Temporal | Trigger.dev |
|---|---|---|
| State model | Event history replay | Checkpoint snapshots |
| Deployment | Self-hosted or Temporal Cloud (workers still self-managed) | Hosted control plane with managed workers |
| Retry granularity | Per activity, configurable | Per task, configurable |
| Long-running tasks | Durable timers in event history | Platform-scheduled resume events |
| Observability | Event history, Prometheus metrics, automatic workflow tracing | Built-in UI, real-time logs, task-level spans |
| Language support | Polyglot (Go, Java, Python, TypeScript, PHP) | TypeScript only |
| Operational overhead | High (database, server, workers) | Low (hosted platform) |
| Vendor lock-in | Self-hostable open-source (requires operational expertise) | High (proprietary runtime) |
Code Example: Retry and State Preservation
Here’s how a multi-step task with retries looks in Trigger.dev V3:
import { task, wait } from "@trigger.dev/sdk/v3";
export const processOrder = task({
id: "process-order",
retry: {
maxAttempts: 3,
factor: 2,
minTimeoutInMs: 1000,
},
run: async (payload: { orderId: string }) => {
// Each await boundary creates an automatic checkpoint
const order = await db.orders.findUnique({
where: { id: payload.orderId },
});
const charge = await stripe.charges.create({
amount: order.total,
currency: "usd",
source: order.paymentToken,
});
// Task suspends here until webhook arrives or timeout
const fulfillment = await wait.forEvent("order.fulfilled", {
filter: { orderId: payload.orderId },
timeoutInSeconds: 604800, // 7 days
});
await sendEmail({
to: order.customerEmail,
subject: "Order shipped",
body: `Tracking: ${fulfillment.trackingNumber}`,
});
return { success: true, trackingNumber: fulfillment.trackingNumber };
},
});
If the Stripe charge fails, the retry starts at step 2. The order fetch doesn’t re-run. If the task crashes while waiting for the fulfillment webhook, the platform reschedules the wait and resumes when the webhook arrives.
In Temporal, you’d structure this as a workflow with three activities (fetch, charge, email) and a signal handler for the fulfillment event. The workflow code would be deterministic, and the activities would handle the actual I/O. Retries would happen at the activity level, and the workflow would replay the entire decision history on each worker restart.
Failure Modes and Limits
Trigger.dev failure modes:
- Checkpoint serialization fails if state contains non-serializable objects. When this happens, the task enters an error state and retries with exponential backoff. If serialization continues to fail, the task reaches max retry attempts and stops.
- Platform outage means all tasks pause indefinitely until the platform recovers. There is no local fallback in the standard tier; tasks resume from their last checkpoint once service is restored.
- Task timeout limits depend on pricing tier (5 minutes to unlimited)
- No cross-task transactions (each task is independent)
Temporal failure modes:
- Event history grows unbounded for long-running workflows (requires archival strategy)
- Workflow versioning is required when changing workflow logic mid-flight
- Database outage stops all workflow progress
- Replay can be slow for workflows with thousands of events
Trigger.dev’s checkpoint model avoids unbounded history growth but requires careful state management. Temporal’s replay model avoids serialization issues but requires deterministic code and versioning discipline.
Technical Verdict
Use Trigger.dev when:
- Your team is TypeScript-native and wants minimal operational overhead
- You need durable execution for background jobs, webhooks, or scheduled tasks
- You’re okay with vendor lock-in for simpler developer experience
- Your workflows fit within the platform’s timeout and concurrency limits
Use Temporal when:
- You need polyglot support (multiple languages in the same workflow)
- You require full control over deployment and data residency
- Your workflows are complex enough to justify the operational investment
- You need cross-workflow transactions or advanced versioning
Avoid Trigger.dev when:
- You need to run workflows in your own infrastructure (without enterprise tier)
- Your state includes complex objects that don’t serialize cleanly
- You need sub-second task latency (platform overhead adds approximately 100ms)
Avoid Temporal when:
- Your team lacks the expertise to manage distributed systems
- You’re building simple background jobs that don’t need durable execution
- You want to ship fast without learning a new programming model
The real trade-off is operational complexity versus developer experience. Trigger.dev bets that most teams would rather write normal TypeScript and let the platform handle durability. Temporal’s design assumes teams building complex workflows need full control over the execution model. Both are right for different audiences.