Migrating production infrastructure? Get up to $10K in migration credits.

Apply now
Comparison
Platform
workflows

Render Workflows vs Trigger.dev: durable steps vs checkpointed tasks

Trigger.dev and Render Workflows both run long tasks on managed compute with automatic retries, but they make different parts of a run durable.

Trigger.dev makes the task process durable. When a task waits, Trigger.dev takes a snapshot of the whole process, shuts the machine down, and restores the snapshot on a new machine when the wait ends. If a task fails, the retry starts the task again from the beginning. Child tasks that you triggered with idempotency keys return their saved results instead of running again.

Render Workflows makes each step durable. Each task run gets its own instance, and you set a compute plan per task, up to 8 CPU and 32 GB (Trigger.dev's largest preset is 8 vCPU and 16 GB). Inside a task, each completed ctx.step() saves its result, so a retry or a recovery after an infrastructure failure skips the work that already finished.

This difference affects:

  • How you write tasks
  • What a retry runs again
  • How much CPU and memory each task can use
  • The cost of a task while it waits
  • How each product bills for usage

The short answer

  • Choose Trigger.dev if you're a TypeScript team, your runs wait for days, and you want built-in schedules and live run updates in your frontend.
  • Choose Render Workflows if your pipelines are in Python, a task needs more than 16 GB of memory, or you want completed steps to skip on retry without managing idempotency keys. Render also fits if you need managed Postgres and web service hosting for your application.
  • Use both by running TypeScript orchestration and long waits on Trigger.dev and the heavy Python tasks on Render.

Render Workflows vs Trigger.dev at a glance

Dimension
Trigger.dev
Render Workflows
Durability model
Checkpoints the task process during waits, and a retry starts the task from the beginning
Each task run gets its own instance, and completed ctx.step() steps return saved results on retry and recovery
Languages
TypeScript, with Python scripts run from TypeScript tasks through a build extension
TypeScript and Python
Max task duration
Project-wide maxDuration of CPU time, which you can override or remove (timeout.None) per task or run
24 hours per task run
Max compute per task
8 vCPU and 16 GB of RAM
8 CPU and 32 GB of RAM, with larger plans on request
Code constraints
No sandbox, so tasks are ordinary Node.js or Bun functions
No sandbox, so tasks are ordinary functions
Waiting and pausing
wait.for(), wait.until(), and wait.forToken() pause a run, and waits longer than 5 seconds don't bill compute
No pause primitive, so a run keeps its instance while it waits
Fan-out
batchTriggerAndWait(), up to 1,000 items per batch with SDK 4.3.1 or later
ctx.run() with asyncio.gather or Promise.all
Payload size
3 MB per trigger payload, and 10 MB per task output
4 MB of arguments per task run
Scheduling
Built-in scheduled tasks with cron expressions and time zones
Trigger tasks from a Render cron job
Data and networking
Public internet by default, with static IPs on paid plans and AWS PrivateLink on Pro and up
Private network to Render Postgres, Key Value, and private services
Deploys and previews
Deploys with the Trigger.dev CLI, and runs stay on the version that started them
Separate workflow service, declared with type: workflow in Blueprints, and not included in preview environments
Pricing model
Compute per second by machine size, plus $0.25 per 10,000 runs, on plans with monthly credits
Compute per run by the second, plus task state retention
Compliance
SOC 2 Type 2, and a HIPAA BAA as a paid add-on
SOC 2 Type 2 and ISO 27001, but Workflows is unavailable in HIPAA-enabled workspaces

What is Trigger.dev?

Trigger.dev is an open-source platform for background jobs and AI workflows in TypeScript. Version 4 became generally available on August 18, 2025. You define tasks with task() from the @trigger.dev/sdk package, run them locally with the Trigger.dev CLI, and deploy them with trigger.dev deploy, which builds a container image of your tasks. Trigger.dev Cloud runs each task on a machine size that you choose, and you set the maximum run duration yourself, or remove the limit entirely.

A task can wait for a fixed time with wait.for() or wait.until(), or wait for a person or an external system with wait.forToken(). A task can also start child tasks and wait for their results with triggerAndWait() and batchTriggerAndWait(). Trigger.dev includes scheduled tasks, a dashboard with traces for every run, OpenTelemetry export, and Realtime, which streams run status and output to your frontend. The code is Apache 2.0 licensed, and you can self-host it with Docker Compose or Kubernetes.

What is Render Workflows?

Render Workflows is an end-to-end orchestration and execution engine for long-running, distributed tasks. You use the Render SDK to mark Python or TypeScript functions as tasks, then deploy them as a workflow service from a Git repository. When you trigger a task, Render starts the run in its own isolated instance, retries it on failure, and removes the instance when the run completes.

Each task sets its own retry, timeout, and compute plan, and each execution of a task is a run. Inside a task, ctx.step() runs a plain function or a registered task in the same process, as a sequential step with its own retries. A task can also call other tasks with ctx.run(). Each chained run gets its own instance and returns its result to the parent, and you fan out with asyncio.gather or Promise.all. You can start runs from the SDK, the REST API, the Render CLI, or the Render Dashboard, which shows active and completed runs for each task with logs. Use render workflows dev to run and test a local task server before you deploy.

Where does durability live: in checkpoints or in steps?

Trigger.dev saves the state of a waiting process, and Render Workflows saves the result of each completed step. As a result, the two products repeat different work after a failure.

Trigger.dev uses CRIU (Checkpoint/Restore In Userspace) to save a task's memory and process state while it waits on a child task or a long wait.for(). Per its docs, a wait.for() or wait.until() shorter than 60 seconds doesn't create a checkpoint. A checkpoint protects a run during a wait, but not during a failure. When a task throws an error, the retry "starts from the beginning, but leverages cached results for completed subtasks," as long as you triggered those subtasks with an idempotency key. Inline code outside a child task runs again on every retry. For side effects in that code, Trigger.dev's idempotency docs suggest that you record each side effect in your own database, or pass an idempotency key derived from the run ID to the external API.

In Render Workflows, every task run is its own instance, and ctx.step() makes inline code durable. When a task re-executes after a retry or an infrastructure failure, each completed step returns its saved result instead of running again. You pick the compute plan per task, from the default flex plan (up to 1 CPU and 4 GB of RAM) to 8c-32g (8 CPU and 32 GB of RAM). You set the timeout per task anywhere from 30 seconds to 24 hours.

Trigger.dev machines range from micro (0.25 vCPU and 0.25 GB of RAM) to large-2x (8 vCPU and 16 GB of RAM), and the default is small-1x (0.5 vCPU and 0.5 GB of RAM). A task that fits in 16 GB of memory and finishes within 24 hours runs on either product.

Consider a pipeline that indexes uploaded contracts. For each batch, it parses 2,000 PDFs with a Python library, runs a self-hosted embedding model that needs 24 GB of RAM, and writes the vectors to Postgres.

On Trigger.dev, tasks are written in TypeScript or JavaScript, and Python runs as a separate script through the Python build extension. You can retry the call that launches the script, but not the Python functions inside it. If parsing succeeds and the embedding step fails, a retry runs the whole script again. The embedding step also needs more than the 16 GB available on Trigger.dev's largest machine, so you would split the model into smaller batches or host it on a separate service.

On Render Workflows, the embedding work is one Python task on a larger plan:

The parent task runs on the small default plan, and each batch gets 8 CPUs and 32 GB of RAM. If a batch fails, Render retries that run on its own instance.

Choose Trigger.dev if your tasks are TypeScript, fit in 16 GB of memory, and spend much of their time waiting. Choose Render Workflows if a task needs more than 16 GB of memory or runs Python, or if you want inline steps to skip on retry.

Comparison by dimension

Programming model

Trigger.dev tasks are ordinary Node.js or Bun functions with full access to npm packages. When a task waits on a child task or a timer, Trigger.dev checkpoints the whole process and later restores it, so your code has no determinism rules. Each triggerAndWait() call returns a result object that holds the child's output. Trigger.dev's docs warn against running triggerAndWait() calls in parallel with Promise.all(). To fan out, you pass all items to batchTriggerAndWait(). A failed task retries from the start, so code before the failure runs again. To keep a side effect from repeating, move it into a child task and trigger it with an idempotency key.

Render Workflows tasks are also ordinary functions with full access to their language runtime and packages. To make inline work durable, wrap it in ctx.step(). Steps run one after another in the same process as their task, and a step can wrap a plain function or a registered task. Each step has its own retry settings, and Render saves the step's result when it completes. If the task runs again because of a retry or an infrastructure failure, completed steps return their saved results instead of running again, so their side effects don't repeat. Render also checks each replay for determinism. If a replay doesn't match the original run, for example because steps run in a different order, the replay fails instead of mixing old and new results. Code outside a step runs again on every retry, so make that code safe to repeat, for example by using upserts in database writes. To run work in parallel on separate instances, chain tasks with ctx.run() instead.

Choose Trigger.dev if you want no determinism rules and you're comfortable using child tasks and idempotency keys to keep side effects from repeating. Choose Render Workflows if you want inline steps that run once, even when the task retries.

Languages

Render Workflows has generally available SDKs for Python and TypeScript. Trigger.dev tasks are TypeScript and run on Node.js or Bun. To run Python, you add the Python build extension, and a TypeScript task calls python.runScript() or python.runInline() from the @trigger.dev/python package. The Python code runs as a subprocess, so Trigger.dev's retries, waits, and child tasks apply only at the TypeScript level.

Choose Trigger.dev if your codebase is TypeScript. Choose Render Workflows if your pipelines are in Python, which is common for AI, data, and machine learning work, and you want task boundaries and retries inside the Python code.

Waiting and human-in-the-loop

Trigger.dev is built for long waits. wait.for() and wait.until() pause a run for a time or until a date, and waits longer than 5 seconds don't count toward compute usage. wait.forToken() pauses a run until its token is completed, either by your code calling wait.completeToken() (for example, when a person approves a request) or by an external system sending a POST request to the token's callback URL. A timer wait shorter than 60 seconds still holds the run's concurrency slot, because Trigger.dev checkpoints the run only 60 seconds into the wait.

Render Workflows has no pause or sleep primitive yet. A waiting run holds its instance until the run completes or reaches its timeout, which can be at most 24 hours. On the flex plan, Render bills for the CPU and memory a run uses, so a waiting run costs little. To wait for approval on Render, end the run and start a new one when the approval arrives.

Choose Trigger.dev if your runs wait days or weeks for people or webhooks. Choose Render Workflows if each wait fits within the 24-hour task timeout, or you can split a longer wait into two runs.

Data and networking

Render Workflows task runs can connect to Render Postgres, Render Key Value, and private services in the same region over the private network, with no public internet hop. If the workflow belongs to a network-isolated environment, its runs can't reach other services in that environment over the private network.

Unless you set up private networking, Trigger.dev tasks reach your database over the public internet. Paid plans offer static IP addresses for databases that use IP allowlists, and Pro and Enterprise plans offer AWS PrivateLink to connect to resources in your AWS account (up to four connections per organization).

Choose Render Workflows if you also need managed Postgres and web service hosting for your application. Choose Trigger.dev if your data is in AWS and you can set up PrivateLink or IP allowlists.

Developer workflow

Trigger.dev keeps tasks in your repository, usually in a trigger directory. The trigger.dev dev command runs tasks on your machine and connects to Trigger.dev Cloud, which registers your tasks and manages their runs. The trigger.dev deploy command builds and deploys a new version, and each run stays on the version it started on, including its retries. Trigger.dev includes preview branches on the Hobby and Pro plans.

Render Workflows deploys as a separate workflow service from a Git repository, and you declare it in a Blueprint with type: workflow. Compute plans live in task code, so the Blueprint has no plan field. Workflow services aren't included in preview environments. Run render workflows dev to test tasks locally.

Choose Trigger.dev if you want preview branches for your tasks. Choose Render Workflows if you want workflows deployed from Git next to your other Render services.

Pricing model

Trigger.dev bills compute per second by machine size, per its pricing page. The default small-1x machine costs $0.0000338 per second (about $0.12 per hour), large-1x (4 vCPU and 8 GB) costs about $1.22 per hour, and large-2x (8 vCPU and 16 GB) costs about $2.45 per hour. Each run also costs $0.000025, or $0.25 per 10,000 runs, and runs in the development environment are free. Paid plans cost $10 per month (Hobby) or $50 per month (Pro), and each includes the same amount in usage credits. Plans also cap concurrent runs, at 50 on Hobby and 200 on Pro. Pro adds 50 more concurrent runs for $10 per month.

Render Workflows bills compute for each run by the second, per its limits and pricing page. The flex plan bills $0.20 per CPU-hour of CPU time used and $0.05 per GB-hour of RAM, up to $0.40 per hour. Fixed plans bill a flat hourly rate, from $0.40 per hour for 2c-4g to $2.50 per hour for 8c-32g. At 4 CPU and 8 GB, Render costs $1.00 per hour, and at 8 CPU, Render costs $2.50 per hour with twice the memory of Trigger.dev's largest machine. Task state retention costs $0.25 per GB. Render queues and provisions runs at no additional cost.

Choose Trigger.dev if your runs pause for long periods, such as multi-day flows. A timed wait longer than 5 seconds doesn't bill compute, and Trigger.dev checkpoints the run and frees its machine after 60 seconds or when it waits on a subtask. Choose Render Workflows if you don't want a per-run fee, or if you need more than 16 GB of RAM per run. On the flex plan, a run that waits on an LLM or an external API bills almost no CPU, though it still bills for the RAM it uses.

Security and compliance

Both Trigger.dev and Render hold SOC 2 Type 2 reports, and Render holds ISO 27001 certification. Trigger.dev provides its SOC 2 report and penetration test report on Enterprise. Trigger.dev offers a HIPAA business associate agreement (BAA) as a $500 per month add-on on the Hobby and Pro plans, and as an add-on on Enterprise. Render offers a BAA on Scale and Enterprise plans, but Render Workflows isn't available in HIPAA-enabled workspaces yet.

Choose Trigger.dev if your workflows process protected health information and Trigger.dev confirms that your BAA covers them. Choose Render Workflows if those processes don't handle protected health information and your requirements are covered by SOC 2 Type 2 and ISO 27001.

AI and agent workloads

Many agents wait for approvals or callbacks and run heavy compute between them. Trigger.dev targets TypeScript agents. It can expose tasks as AI SDK tools, stream agent output to your frontend with Realtime, and pause a run for human approval with wait.forToken(). Render Workflows targets the compute between those waits. A task can run model inference on 8 CPUs with 32 GB of RAM, parse a large document set, or crawl for hours, and it can read and write to Postgres over the private network. Most Python agent frameworks run in Render tasks without a subprocess.

Choose Trigger.dev if your agents are TypeScript and run for days or weeks. Choose Render Workflows if your agents are Python or need more than 16 GB of memory per task.

When to choose Trigger.dev

  • Your tasks are TypeScript, and you want them in the same repository as your app.
  • Runs wait hours, days, or weeks for timers, approvals, or webhooks, and you don't want to pay for compute while they wait.
  • Every task fits in 16 GB of memory.
  • You want built-in scheduled tasks with time zones, or live run updates in your frontend.
  • You want open-source code that you can self-host.
  • Your workflows handle protected health information, and your Trigger.dev BAA covers them.

When to choose Render Workflows

  • Individual tasks are heavy: long-running model calls, video and image processing, large file parsing, or long scrapes.
  • A task needs more than 16 GB of memory, and you don't want to split it or manage a separate service for it.
  • Your pipelines are in Python and you want a generally available SDK.
  • You want inline steps that return saved results on retry, without idempotency keys or a table of completed side effects.
  • Your tasks read and write Postgres or a Redis-compatible key-value store, or need private networking between services.
  • You want one compute meter per run, with per-task plan choices and no per-run fee.

How do you use Render Workflows and Trigger.dev together?

Run the orchestration on Trigger.dev and send the heavy steps to Render. A Trigger.dev task can create a waitpoint token, start a Render task that reports to it, and pause without compute until the task finishes:

  1. Your app triggers a Trigger.dev task.
  2. The task creates a waitpoint token with wait.createToken().
  3. The task starts a Render task with the Render SDK and passes the token's callback URL as an argument.
  4. The Render task runs on the compute plan it needs, writes results to Render Postgres, and posts a summary to the callback URL.
  5. The Trigger.dev task resumes when the request arrives and continues with notifications or approvals.

Give the Render task a callback_url parameter. When the task finishes, it sends a POST request with a JSON body to that URL, and the body becomes the token's output. Store RENDER_API_KEY as an environment variable in your Trigger.dev project.

Render's startTask() has no idempotency key, so the code checks isCached to keep a retry from starting a second Render run. The check misses one case. If an attempt fails after it creates the token but before startTask() returns, the retry finds the cached token, skips startTask(), and waits until the token times out. To handle that case, save the Render taskRunId in your own database after startTask() returns, and check for that ID instead of isCached.

Moving from Trigger.dev to Render Workflows

Each Trigger.dev task() becomes a Render task, and its machine setting becomes the task's compute plan. Each child task that you call with triggerAndWait() becomes a ctx.run() call, which runs the child on its own instance. To replace batchTriggerAndWait(), wrap several ctx.run() calls in asyncio.gather or Promise.all. Inline code that you protected with idempotency keys or a table of completed side effects becomes ctx.step() calls. When Render replays a task, each completed step returns its saved result instead of running again. Code outside a step runs again on every replay, so make it safe to repeat.

Render has no equivalent of wait.for(), wait.until(), or wait.forToken() yet. A run can wait in place, but it keeps its instance until it finishes or reaches its timeout. For a long wait, split the work into two runs. To replace wait.forToken(), start the second run from the webhook handler that would have completed the token.

Replace each scheduled task with a Render cron job that triggers the task through the SDK. A run can last at most 24 hours, so split any run that can take longer. Start with the Render Workflows tutorial or run render workflows init to scaffold a project.

Frequently asked questions