| name | trigger-advanced-tasks |
| description | Comprehensive rules to help you write advanced Trigger.dev tasks |
Source Cursor rule: .cursor/rules/trigger.advanced-tasks.mdc.
Original file scope: **/trigger/**/*.ts.
Original Cursor alwaysApply: false.
Trigger.dev Advanced Tasks (v4)
Advanced patterns and features for writing tasks
Tags & Organization
import { task, tags } from '@trigger.dev/sdk';
export const processUser = task({
id: 'process-user',
run: async (payload: { userId: string; orgId: string }, { ctx }) => {
await tags.add(`user_${payload.userId}`);
await tags.add(`org_${payload.orgId}`);
return { processed: true };
},
});
await processUser.trigger(
{ userId: '123', orgId: 'abc' },
{ tags: ['priority', 'user_123', 'org_abc'] },
);
for await (const run of runs.subscribeToRunsWithTag('user_123')) {
console.log(`User task ${run.id}: ${run.status}`);
}
Tag Best Practices:
- Use prefixes:
user_123, org_abc, video:456
- Max 10 tags per run, 1-128 characters each
- Tags don't propagate to child tasks automatically
Concurrency & Queues
import { task, queue } from '@trigger.dev/sdk';
const emailQueue = queue({
name: 'email-processing',
concurrencyLimit: 5,
});
export const oneAtATime = task({
id: 'sequential-task',
queue: { concurrencyLimit: 1 },
run: async (payload) => {
},
});
export const processUserData = task({
id: 'process-user-data',
run: async (payload: { userId: string }) => {
await childTask.trigger(payload, {
queue: {
name: `user-${payload.userId}`,
concurrencyLimit: 2,
},
});
},
});
export const emailTask = task({
id: 'send-email',
queue: emailQueue,
run: async (payload: { to: string }) => {
},
});
Error Handling & Retries
import { task, retry, AbortTaskRunError } from '@trigger.dev/sdk';
export const resilientTask = task({
id: 'resilient-task',
retry: {
maxAttempts: 10,
factor: 1.8,
minTimeoutInMs: 500,
maxTimeoutInMs: 30_000,
randomize: false,
},
catchError: async ({ error, ctx }) => {
if (error.code === 'FATAL_ERROR') {
throw new AbortTaskRunError('Cannot retry this error');
}
console.error(`Task ${ctx.task.id} failed:`, error);
return { retryAt: new Date(Date.now() + 60000) };
},
run: async (payload) => {
const result = await retry.onThrow(
async () => {
return await unstableApiCall(payload);
},
{ maxAttempts: 3 },
);
const response = await retry.fetch('https://api.example.com', {
retry: {
maxAttempts: 5,
condition: (response, error) => {
return response?.status === 429 || response?.status >= 500;
},
},
});
return result;
},
});
Machines & Performance
export const heavyTask = task({
id: 'heavy-computation',
machine: { preset: 'large-2x' },
maxDuration: 1800,
run: async (payload, { ctx }) => {
if (ctx.machine.preset === 'large-2x') {
return await parallelProcessing(payload);
}
return await standardProcessing(payload);
},
});
await heavyTask.trigger(payload, {
machine: { preset: 'medium-1x' },
});
Machine Presets:
micro: 0.25 vCPU, 0.25 GB RAM
small-1x: 0.5 vCPU, 0.5 GB RAM (default)
small-2x: 1 vCPU, 1 GB RAM
medium-1x: 1 vCPU, 2 GB RAM
medium-2x: 2 vCPU, 4 GB RAM
large-1x: 4 vCPU, 8 GB RAM
large-2x: 8 vCPU, 16 GB RAM
Idempotency
import { task, idempotencyKeys } from '@trigger.dev/sdk';
export const paymentTask = task({
id: 'process-payment',
retry: {
maxAttempts: 3,
},
run: async (payload: { orderId: string; amount: number }) => {
const idempotencyKey = await idempotencyKeys.create(`payment-${payload.orderId}`);
await chargeCustomer.trigger(payload, {
idempotencyKey,
idempotencyKeyTTL: '24h',
});
},
});
import { createHash } from 'node:crypto';
function createPayloadHash(payload: any): string {
const hash = createHash('sha256');
hash.update(JSON.stringify(payload));
return hash.digest('hex');
}
export const deduplicatedTask = task({
id: 'deduplicated-task',
run: async (payload) => {
const payloadHash = createPayloadHash(payload);
const idempotencyKey = await idempotencyKeys.create(payloadHash);
await processData.trigger(payload, { idempotencyKey });
},
});
Metadata & Progress Tracking
import { task, metadata } from '@trigger.dev/sdk';
export const batchProcessor = task({
id: 'batch-processor',
run: async (payload: { items: any[] }, { ctx }) => {
const totalItems = payload.items.length;
metadata
.set('progress', 0)
.set('totalItems', totalItems)
.set('processedItems', 0)
.set('status', 'starting');
const results = [];
for (let i = 0; i < payload.items.length; i++) {
const item = payload.items[i];
const result = await processItem(item);
results.push(result);
const progress = ((i + 1) / totalItems) * 100;
metadata
.set('progress', progress)
.increment('processedItems', 1)
.append('logs', `Processed item ${i + 1}/${totalItems}`)
.set('currentItem', item.id);
}
metadata.set('status', 'completed');
return { results, totalProcessed: results.length };
},
});
export const childTask = task({
id: 'child-task',
run: async (payload, { ctx }) => {
metadata.parent.set('childStatus', 'processing');
metadata.root.increment('childrenCompleted', 1);
return { processed: true };
},
});
Advanced Triggering
Frontend Triggering (React)
'use client';
import { useTaskTrigger } from '@trigger.dev/react-hooks';
import type { myTask } from '../trigger/tasks';
function TriggerButton({ accessToken }: { accessToken: string }) {
const { submit, handle, isLoading } = useTaskTrigger<typeof myTask>('my-task', { accessToken });
return (
<button onClick={() => submit({ data: 'from frontend' })} disabled={isLoading}>
Trigger Task
</button>
);
}
Large Payloads
export const largeDataTask = task({
id: 'large-data-task',
run: async (payload: { dataUrl: string }) => {
const response = await fetch(payload.dataUrl);
const largeData = await response.json();
return { processed: largeData.length };
},
});
await largeDataTask.trigger({
dataUrl: 'https://s3.amazonaws.com/bucket/large-file.json?presigned=true',
});
Advanced Options
await myTask.trigger(payload, {
delay: '2h30m',
ttl: '24h',
priority: 100,
tags: ['urgent', 'user_123'],
metadata: { source: 'api', version: 'v2' },
queue: {
name: 'priority-queue',
concurrencyLimit: 10,
},
idempotencyKey: 'unique-operation-id',
idempotencyKeyTTL: '1h',
machine: { preset: 'large-1x' },
maxAttempts: 5,
});
Hidden Tasks
const internalProcessor = task({
id: 'internal-processor',
run: async (payload: { data: string }) => {
return { processed: payload.data.toUpperCase() };
},
});
export const publicWorkflow = task({
id: 'public-workflow',
run: async (payload: { input: string }) => {
const result = await internalProcessor.triggerAndWait({
data: payload.input,
});
if (result.ok) {
return { output: result.output.processed };
}
throw new Error('Internal processing failed');
},
});
Logging & Tracing
import { task, logger } from '@trigger.dev/sdk';
export const tracedTask = task({
id: 'traced-task',
run: async (payload, { ctx }) => {
logger.info('Task started', { userId: payload.userId });
const user = await logger.trace(
'fetch-user',
async (span) => {
span.setAttribute('user.id', payload.userId);
span.setAttribute('operation', 'database-fetch');
const userData = await database.findUser(payload.userId);
span.setAttribute('user.found', !!userData);
return userData;
},
{ userId: payload.userId },
);
logger.debug('User fetched', { user: user.id });
try {
const result = await processUser(user);
logger.info('Processing completed', { result });
return result;
} catch (error) {
logger.error('Processing failed', {
error: error.message,
userId: payload.userId,
});
throw error;
}
},
});
Usage Monitoring
import { logger, task, usage } from '@trigger.dev/sdk';
export const monitoredTask = task({
id: 'monitored-task',
run: async (payload) => {
const currentUsage = await usage.getCurrent();
logger.info('Current cost', {
costInCents: currentUsage.costInCents,
durationMs: currentUsage.durationMs,
});
const { result, compute } = await usage.measure(async () => {
return await expensiveOperation(payload);
});
logger.info('Operation cost', {
costInCents: compute.costInCents,
durationMs: compute.durationMs,
});
return result;
},
});
Run Management
await runs.cancel('run_123');
await runs.replay('run_123');
const run = await runs.retrieve('run_123');
console.log(`Cost: ${run.costInCents} cents, Duration: ${run.durationMs}ms`);
Best Practices
- Concurrency: Use queues to prevent overwhelming external services
- Retries: Configure exponential backoff for transient failures
- Idempotency: Always use for payment/critical operations
- Metadata: Track progress for long-running tasks
- Machines: Match machine size to computational requirements
- Tags: Use consistent naming patterns for filtering
- Large Payloads: Use external storage for files > 10MB
- Error Handling: Distinguish between retryable and fatal errors
Design tasks to be stateless, idempotent, and resilient to failures. Use metadata for state tracking and queues for resource management.