Skip to main content
You can attach up to 256 KB of metadata to a run. Metadata is accessible from inside the run function, via the REST API, Realtime, and in the dashboard. Use it to store additional structured information — for example, user identifiers, progress state, intermediate results, or deployment status.

Setting metadata at trigger time

Pass a metadata object when triggering a run:
import { myTask } from "./trigger/my-task";

const handle = await myTask.trigger(
  { message: "hello world" },
  { metadata: { user: { name: "Eric", id: "user_1234" } } }
);

Reading metadata inside a run

Use metadata.current() to get the entire metadata object, or metadata.get(key) to retrieve a specific key:
/trigger/my-task.ts
import { task, metadata } from "@trigger.dev/sdk";

export const myTask = task({
  id: "my-task",
  run: async (payload: { message: string }) => {
    // Get the whole metadata object
    const currentMetadata = metadata.current();
    console.log(currentMetadata);

    // Get a specific key
    const user = metadata.get("user");
    console.log(user.name); // "Eric"
  },
});
Both methods work anywhere inside the run function — including helper functions called from it:
import { task, metadata } from "@trigger.dev/sdk";

export const myTask = task({
  id: "my-task",
  run: async (payload: { message: string }) => {
    doSomeWork();
  },
});

function doSomeWork() {
  metadata.set("progress", 0.5); // Works — called within the run context
}
Calling metadata.current() or metadata.get() outside of a run function always returns undefined. Calling update methods outside of a run is a no-op. This makes it safe to use metadata methods anywhere in shared utilities without guarding.
Metadata methods also work inside task lifecycle hooks:
import { task, metadata } from "@trigger.dev/sdk";

export const myTask = task({
  id: "my-task",
  run: async (payload: { message: string }) => {
    // run logic
  },
  onStart: async () => {
    metadata.set("progress", 0);
  },
  onSuccess: async () => {
    metadata.set("progress", 1.0);
  },
});

Updates API

All update methods (except flush and stream) are synchronous and non-blocking. The SDK periodically flushes changes to the database in the background, so you can call them as often as needed without impacting run performance.

set

Set the value of a specific key:
import { task, metadata } from "@trigger.dev/sdk";

export const myTask = task({
  id: "my-task",
  run: async (payload: { message: string }) => {
    metadata.set("progress", 0.1);
    // ... do work ...
    metadata.set("progress", 0.5);
    // ... do more work ...
    metadata.set("progress", 1.0);
  },
});

del

Delete a key from the metadata object:
import { task, metadata } from "@trigger.dev/sdk";

export const myTask = task({
  id: "my-task",
  run: async (payload: { message: string }) => {
    metadata.set("progress", 0.5);
    // Remove the key entirely
    metadata.del("progress");
  },
});

replace

Replace the entire metadata object:
import { task, metadata } from "@trigger.dev/sdk";

export const myTask = task({
  id: "my-task",
  run: async (payload: { message: string }) => {
    metadata.set("progress", 0.1);
    // Discard all existing metadata and start fresh
    metadata.replace({ user: { name: "Eric", id: "user_1234" } });
  },
});

append

Append a value to an array key (creates the array if the key does not exist):
import { task, metadata } from "@trigger.dev/sdk";

export const myTask = task({
  id: "my-task",
  run: async (payload: { message: string }) => {
    metadata.append("logs", "Step 1 complete");
    metadata.append("logs", "Step 2 complete");

    console.log(metadata.get("logs")); // ["Step 1 complete", "Step 2 complete"]
  },
});

remove

Remove a specific value from an array key:
import { task, metadata } from "@trigger.dev/sdk";

export const myTask = task({
  id: "my-task",
  run: async (payload: { message: string }) => {
    metadata.append("logs", "Step 1 complete");
    metadata.remove("logs", "Step 1 complete");

    console.log(metadata.get("logs")); // []
  },
});

increment / decrement

Atomically increment or decrement a numeric key:
import { task, metadata } from "@trigger.dev/sdk";

export const myTask = task({
  id: "my-task",
  run: async (payload: { message: string }) => {
    metadata.set("progress", 0.1);
    metadata.increment("progress", 0.4); // now 0.5
    metadata.decrement("progress", 0.2); // now 0.3
  },
});

flush

Force an immediate write to the database. Use this when you need the metadata to be persisted before a checkpoint or external call:
import { task, metadata } from "@trigger.dev/sdk";

export const myTask = task({
  id: "my-task",
  run: async (payload: { message: string }) => {
    metadata.set("progress", 0.5);
    // Ensure persisted before the next await
    await metadata.flush();
  },
});

Fluent API

All update methods return the updater object, so you can chain them:
import { task, metadata } from "@trigger.dev/sdk";

export const myTask = task({
  id: "my-task",
  run: async (payload: { message: string }) => {
    metadata
      .set("progress", 0.1)
      .append("logs", "Step 1 complete")
      .increment("progress", 0.4)
      .set("status", "running");
  },
});

Parent & root updates

A child task can update the metadata of its parent or the root task. This is useful for rolling up progress across a task hierarchy.
/trigger/tasks.ts
import { task, metadata } from "@trigger.dev/sdk";

export const myParentTask = task({
  id: "my-parent-task",
  run: async (payload: { message: string }) => {
    metadata.set("progress", 0);
    await childTask.triggerAndWait({ message: "hello world" });
  },
});

export const childTask = task({
  id: "child-task",
  run: async (payload: { message: string }) => {
    // Update the parent task's metadata
    metadata.parent.set("progress", 0.5);

    // Update the root task's metadata (same as parent when depth is 1)
    metadata.root.set("progress", 0.5);
  },
});
All update methods are available on metadata.parent and metadata.root, and they support chaining:
metadata.parent
  .set("progress", 0.5)
  .append("logs", "Step 1 complete")
  .increment("processedRows", 1);

Example: tracking progress across parallel child tasks

/trigger/csv-processing.ts
import { batch, metadata, schemaTask } from "@trigger.dev/sdk";
import { z } from "zod";

const CSVRow = z.object({ id: z.string() });
const UploadedFileData = z.object({ url: z.string() });

export const handleCSVRow = schemaTask({
  id: "handle-csv-row",
  schema: CSVRow,
  run: async (row, { ctx }) => {
    // Increment counter and record run ID in the parent task
    metadata.parent.increment("processedRows", 1).append("rowRuns", ctx.run.id);
    return row;
  },
});

export const handleCSVUpload = schemaTask({
  id: "handle-csv-upload",
  schema: UploadedFileData,
  run: async (file) => {
    metadata.set("status", "fetching");
    const rows = await fetch(file.url).then((r) => r.json());

    metadata.set("status", "processing").set("totalRows", rows.length);

    const results = await batch.triggerAndWait<typeof handleCSVRow>(
      rows.map((row: { id: string }) => ({ id: "handle-csv-row", payload: row }))
    );

    metadata.set("status", "complete");
    return { file, results };
  },
});
Combined with Realtime, this pattern gives you a live progress bar in your frontend.

Inspecting metadata

Dashboard

Metadata for a run is displayed in the run details view in the Trigger.dev dashboard.

API

Retrieve a run’s metadata using runs.retrieve():
import { runs } from "@trigger.dev/sdk";

const run = await runs.retrieve("run_1234");
console.log(run.metadata);
See the runs.retrieve() reference for full details.

Type-safe metadata

Metadata is loosely typed by default — it accepts any JSON-serializable object. Wrap the metadata API with a Zod schema for full type safety:
import { task, metadata } from "@trigger.dev/sdk";
import { z } from "zod";

const TaskMetadata = z.object({
  user: z.object({
    name: z.string(),
    id: z.string(),
  }),
  startedAt: z.coerce.date(), // coerce string → Date on retrieval
});

type TaskMetadata = z.infer<typeof TaskMetadata>;

function getTypedMetadata(): TaskMetadata {
  return TaskMetadata.parse(metadata.current());
}

export const myTask = task({
  id: "my-task",
  run: async (payload: { message: string }) => {
    const meta = getTypedMetadata();
    console.log(meta.user.name); // string
    console.log(meta.startedAt); // Date
  },
});
Values like Date are serialized to strings when stored. Use z.coerce.date() or equivalent to deserialize them on retrieval.
Constraints on the metadata value:
// ❌ Top-level arrays are not supported
await myTask.trigger(payload, { metadata: [{ id: 1 }] });

// ❌ Plain strings are not supported
await myTask.trigger(payload, { metadata: "some string" });

// ❌ Functions and class instances are not supported
await myTask.trigger(payload, { metadata: { fn: () => {} } });

// ✅ Any JSON-serializable object
await myTask.trigger(payload, { metadata: { user: { name: "Eric" }, date: new Date() } });

Size limit

The maximum metadata size is 256 KB. Exceeding this throws an error. If you are self-hosting, you can increase the limit with the TASK_RUN_METADATA_MAXIMUM_SIZE environment variable (value in bytes).

Build docs developers (and LLMs) love