---
title: Trigger.dev
description: Configure Trigger.dev for an Orders CSV task and check worker publication.
pagination: false
sidebar:
  icon: workflow
---

Trigger.dev needs a project, environment API key, and published worker generation alongside the RELKIT job binding. This page shows the RELKIT side and the exact
registration checks; it is a compatibility guide, not a passing setup tutorial. For a
verified local run today, use [Inngest](/docs/modules/jobs/providers/inngest) or
[Effect MQ](/docs/modules/jobs/providers/effect-mq).

> **Before you begin**
>
> You need Bun 1.3.10 and Docker. Start where `relkit-trigger` does not already exist. The task
> returns CSV text; it does not write a file or read a database.

## Configure an existing app

The `--jobs trigger-docker` creation tuple is currently rejected before staging because its native
and timing evidence is incomplete. Begin with an existing project and install the selected adapter,
native SDK and Docker materializer before applying the configuration below:

{/* Generated by apps/docs/scripts/generate.ts. Do not edit. */}

From this guide's application/package root, install only its selected requirements:

```sh
bun add @relkit/docker@0.7.12 @relkit/local@0.7.12 @relkit/trigger@0.8.1 @trigger.dev/sdk@4.7.2
```

Keep these owner-specific versions and the lockfile together. Runtime SDKs belong in `dependencies`; build/type tooling belongs in `devDependencies`. Installing an adapter does not supply its optional SDK peers or enable the feature's configuration.

This aligns declarations with the adapter's current SDK contract. It does not repair the unresolved
provider/server compatibility or publish a worker.

`jobs.default` connects the provider; `defaults.jobs` selects it. Extend the existing config while
retaining its own ID and server settings.

```ts title="relkit.config.ts"
/** Declares task application defaults with inspection disabled until selected. */
import { defineApp } from "@relkit/app/config";
import env from "@app/platform/env.js";
import { docker } from "@relkit/docker";
import { trigger } from "@relkit/trigger";

export default defineApp({
  id: "relkit-trigger",
  env,
  server: { port: 3000, maxBodyBytes: 1_048_576 },
  telemetry: { redaction: { mode: "development-redacted", maxBytes: 65_536 } },
  jobs: { default: docker(trigger()) },
  defaults: { jobs: "default" },
});
```

## What provider setup is missing

Trigger's [official Docker Compose setup](https://trigger.dev/docs/self-hosting/docker)
runs a webapp, database, Redis, and worker supervisor. After bringing that stack up, an
operator creates a project, obtains a development environment API key, configures the SDK
base URL and key, and deploys a worker containing the task before triggering a run. These
are provider-side steps; the RELKIT binding above does not replace them. The generated
`trigger-docker` recipe instead starts a pinned webapp image with PostgreSQL and a RELKIT
worker, generates a random `projectRef` and `secretKey`, and passes them to the SDK. It does
not create those credentials in Trigger.dev or publish a worker generation. Its pinned
webapp also fails its current environment and port configuration. Running the official
Compose stack separately does **not** make this generated RELKIT profile compatible by
itself. The adapter currently imports `@trigger.dev/sdk/v3`; Trigger's
[self-hosting guidance](https://trigger.dev/docs/self-hosting/overview) says 4.5.0 is the
last server release supporting v3 tasks. The server, SDK, task publication, and project
credentials must be aligned together. Do not paste a production Trigger key into the
generated source or assume that `local up` completing means a task is registered there.

## Add the Orders boundary

These instructions extend an existing application; they do not create a jobs starter.
For a grouped minimal app, create `src/services/orders/functions`, `tasks` and `jobs`.
Add the following boundary and health function. In a compatible flat app, use its
existing `src/orders/` root consistently instead. If Orders already exists, extend
its public functions rather than replacing the boundary.

```ts title="src/services/orders/service.ts"
/** Declares the orders service boundary and its public members. */
import { defineService } from "@relkit/app/services";
import exportOrders from "@app/services/orders/jobs/export-orders.job.js";
import exportOrdersTask from "@app/services/orders/tasks/export-orders.task.js";
import health from "@app/services/orders/functions/health.function.js";

export default defineService({
  functions: { health },
  tasks: { exportOrdersTask },
  jobs: { exportOrders },
});
```

```ts title="src/services/orders/functions/health.function.ts"
/** Supplies the orders service with a validated health result independent of job execution. */
import { defineFunction } from "@relkit/app/functions";
import { z } from "@relkit/app/schema";

export default defineFunction({
  id: "orders.health",
  input: z.object({}),
  output: z.object({ ok: z.literal(true) }),
  handler: async () => ({ ok: true as const }),
});
```

Add `src/services/orders/jobs/export-orders.job.ts` from the maintained task fixture:

```ts title="src/services/orders/jobs/export-orders.job.ts"
/** Registers the export task as a public job exposing trigger, lookup, list and watch with progress/output fields. */
import { defineJob } from "@relkit/app/jobs";
import exportOrders from "@app/services/orders/tasks/export-orders.task.js";

const exportOrdersJob = defineJob({
  id: "orders.export-orders-job",
  name: "exportOrders",
  task: exportOrders,
  client: {
    public: true,
    operations: ["trigger", "get", "list", "watch"],
    fields: ["status", "progress", "output"],
  },
});

export default exportOrdersJob;
```

For this provider, set that job's `client.operations` to `["trigger"]` before checking.

The task file below supplies the job's imported target. Create the sample input file
when absent. Keep existing durable task/job IDs and bump the task version when
changing an already deployed contract.

## Read the task before running it

**Add or update** `src/services/orders/tasks/export-orders.task.ts` with this task. It
validates order lines and computes CSV text and a total in cents. The `durable` declaration
does not by itself prove checkpoint or resume support. The sample IDs and SKUs contain no
commas or newlines; escape fields for arbitrary text. This new app keeps task version `1`.
The explicit job exposes `exportOrders`; keep one job with this public name.

```ts title="src/services/orders/tasks/export-orders.task.ts"
import { defineTask } from "@relkit/app/tasks";
import { z } from "@relkit/app/schema";

const order = z.object({
  id: z.string().min(1),
  sku: z.string().min(1),
  quantity: z.number().int().positive(),
  unitPriceCents: z.number().int().nonnegative(),
});

const exportOrders = defineTask({
  id: "orders.export-orders",
  version: "1",
  execution: "durable",
  input: z.object({ orders: z.array(order) }),
  output: z.object({ csv: z.string(), orderCount: z.number().int(), totalCents: z.number().int() }),
  handler: async ({ orders }, context) => {
    const lines = ["order_id,sku,quantity,total_cents"];
    let totalCents = 0;
    for (const item of orders) {
      const lineTotal = item.quantity * item.unitPriceCents;
      totalCents += lineTotal;
      lines.push([item.id, item.sku, item.quantity, lineTotal].join(","));
    }
    context.log.info("Orders CSV prepared", { orderCount: orders.length, totalCents });
    return { csv: lines.join("\n") + "\n", orderCount: orders.length, totalCents };
  },
});

export default exportOrders;
```

**Create or update** `examples/export-orders.json` with these two rows. A completed task should
return `orderCount: 2`, `totalCents: 2100`, and CSV line totals `900` and `1200`.

```json title="examples/export-orders.json"
{
  "orders": [
    { "id": "order-1", "sku": "tea", "quantity": 2, "unitPriceCents": 450 },
    { "id": "order-2", "sku": "mug", "quantity": 1, "unitPriceCents": 1200 }
  ]
}
```

## Check the local profile before submitting

The task source can pass RELKIT checks, but these commands are a diagnostic gate. If
`local status` reports an unhealthy provider, stop here; a RELKIT `jobs list` result only
proves that the compiler discovered the job, not that Trigger.dev registered it.

```sh title="Terminal — relkit-trigger"
bun run check
bun run typecheck
bun run relkit local up --detach
bun run relkit local status
docker ps --filter name=service-trigger --format '{{.Names}} {{.Status}} {{.Ports}}'
```

If the Trigger container exits, stop here and inspect its logs. If a future compatibility
fix makes the provider and worker ready, start `bun run dev`, then use `jobs list` to
confirm RELKIT discovered `exportOrders`, then submit the input. An accepted receipt is
only an enqueueing signal. Require **all three** before claiming the integration works:
the task appears in Trigger.dev's project dashboard, its native run reaches `COMPLETED`,
and the run output includes `orderCount: 2` and `totalCents: 2100`.

```sh title="Second terminal — relkit-trigger"
bun run relkit jobs capabilities
bun run relkit --json jobs list
bun run relkit --json jobs trigger --job exportOrders --input-file examples/export-orders.json --operation-id orders-csv-1
bun run relkit jobs runs get --run-id <runId>
```

Replace `<runId>` with the accepted handle only if submission succeeds. Compare it with the
native Trigger.dev run ID and worker logs. No screenshot of a completed native run is shown
here because this profile did not reach that state in a fresh app. A screenshot of
`jobs list`, an accepted receipt, or a healthy Docker container would not prove that the
task was published and executed.

**Current verification limit:** The local-source task passes `check` and `typecheck`, but
the pinned Trigger image requires service environment variables absent from the current
recipe and listens on a different port. Once the image was cached, `local up` briefly
reported healthy while the Trigger container exited. Adding the SDK manually allowed the
RELKIT backend to start during a diagnostic run, but submission returned an unknown outcome
and the worker never became ready. We therefore have **no proof** that this task was
registered with Trigger.dev or executed there. This provider's runtime compatibility remains
**unverified** in the [capability evidence](/docs/modules/jobs/providers). Do not rely on
checkpoint resume, durable sleep, observation, or cancellation until the selected environment
reports and demonstrates them. Keep worker publication and definition registration staged so
an unsuccessful replacement does not remove the active worker generation. Continue with
[provider comparison](/docs/modules/jobs/providers) to choose a profile by verified capability.
