--- 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 ```ts import { task, tags } from '@trigger.dev/sdk'; export const processUser = task({ id: 'process-user', run: async (payload: { userId: string; orgId: string }, { ctx }) => { // Add tags during execution await tags.add(`user_${payload.userId}`); await tags.add(`org_${payload.orgId}`); return { processed: true }; }, }); // Trigger with tags await processUser.trigger( { userId: '123', orgId: 'abc' }, { tags: ['priority', 'user_123', 'org_abc'] }, // Max 10 tags per run ); // Subscribe to tagged runs 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 ```ts import { task, queue } from '@trigger.dev/sdk'; // Shared queue for related tasks const emailQueue = queue({ name: 'email-processing', concurrencyLimit: 5, // Max 5 emails processing simultaneously }); // Task-level concurrency export const oneAtATime = task({ id: 'sequential-task', queue: { concurrencyLimit: 1 }, // Process one at a time run: async (payload) => { // Critical section - only one instance runs }, }); // Per-user concurrency export const processUserData = task({ id: 'process-user-data', run: async (payload: { userId: string }) => { // Override queue with user-specific concurrency await childTask.trigger(payload, { queue: { name: `user-${payload.userId}`, concurrencyLimit: 2, }, }); }, }); export const emailTask = task({ id: 'send-email', queue: emailQueue, // Use shared queue run: async (payload: { to: string }) => { // Send email logic }, }); ``` ## Error Handling & Retries ```ts import { task, retry, AbortTaskRunError } from '@trigger.dev/sdk'; export const resilientTask = task({ id: 'resilient-task', retry: { maxAttempts: 10, factor: 1.8, // Exponential backoff multiplier minTimeoutInMs: 500, maxTimeoutInMs: 30_000, randomize: false, }, catchError: async ({ error, ctx }) => { // Custom error handling if (error.code === 'FATAL_ERROR') { throw new AbortTaskRunError('Cannot retry this error'); } // Log error details console.error(`Task ${ctx.task.id} failed:`, error); // Allow retry by returning nothing return { retryAt: new Date(Date.now() + 60000) }; // Retry in 1 minute }, run: async (payload) => { // Retry specific operations const result = await retry.onThrow( async () => { return await unstableApiCall(payload); }, { maxAttempts: 3 }, ); // Conditional HTTP retries 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 ```ts export const heavyTask = task({ id: 'heavy-computation', machine: { preset: 'large-2x' }, // 8 vCPU, 16 GB RAM maxDuration: 1800, // 30 minutes timeout run: async (payload, { ctx }) => { // Resource-intensive computation if (ctx.machine.preset === 'large-2x') { // Use all available cores return await parallelProcessing(payload); } return await standardProcessing(payload); }, }); // Override machine when triggering await heavyTask.trigger(payload, { machine: { preset: 'medium-1x' }, // Override for this run }); ``` **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 ```ts import { task, idempotencyKeys } from '@trigger.dev/sdk'; export const paymentTask = task({ id: 'process-payment', retry: { maxAttempts: 3, }, run: async (payload: { orderId: string; amount: number }) => { // Automatically scoped to this task run, so if the task is retried, the idempotency key will be the same const idempotencyKey = await idempotencyKeys.create(`payment-${payload.orderId}`); // Ensure payment is processed only once await chargeCustomer.trigger(payload, { idempotencyKey, idempotencyKeyTTL: '24h', // Key expires in 24 hours }); }, }); // Payload-based idempotency 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 ```ts 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; // Initialize progress metadata 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]; // Process item const result = await processItem(item); results.push(result); // Update progress const progress = ((i + 1) / totalItems) * 100; metadata .set('progress', progress) .increment('processedItems', 1) .append('logs', `Processed item ${i + 1}/${totalItems}`) .set('currentItem', item.id); } // Final status metadata.set('status', 'completed'); return { results, totalProcessed: results.length }; }, }); // Update parent metadata from child task export const childTask = task({ id: 'child-task', run: async (payload, { ctx }) => { // Update parent task metadata metadata.parent.set('childStatus', 'processing'); metadata.root.increment('childrenCompleted', 1); return { processed: true }; }, }); ``` ## Advanced Triggering ### Frontend Triggering (React) ```tsx '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('my-task', { accessToken }); return ( ); } ``` ### Large Payloads ```ts // For payloads > 512KB (max 10MB) export const largeDataTask = task({ id: 'large-data-task', run: async (payload: { dataUrl: string }) => { // Trigger.dev automatically handles large payloads // For > 10MB, use external storage const response = await fetch(payload.dataUrl); const largeData = await response.json(); return { processed: largeData.length }; }, }); // Best practice: Use presigned URLs for very large files await largeDataTask.trigger({ dataUrl: 'https://s3.amazonaws.com/bucket/large-file.json?presigned=true', }); ``` ### Advanced Options ```ts await myTask.trigger(payload, { delay: '2h30m', // Delay execution ttl: '24h', // Expire if not started within 24 hours priority: 100, // Higher priority (time offset in seconds) 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 ```ts // Hidden task - not exported, only used internally const internalProcessor = task({ id: 'internal-processor', run: async (payload: { data: string }) => { return { processed: payload.data.toUpperCase() }; }, }); // Public task that uses hidden task export const publicWorkflow = task({ id: 'public-workflow', run: async (payload: { input: string }) => { // Use hidden task internally const result = await internalProcessor.triggerAndWait({ data: payload.input, }); if (result.ok) { return { output: result.output.processed }; } throw new Error('Internal processing failed'); }, }); ``` ## Logging & Tracing ```ts 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 }); // Custom trace with attributes 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 ```ts import { logger, task, usage } from '@trigger.dev/sdk'; export const monitoredTask = task({ id: 'monitored-task', run: async (payload) => { // Get current run cost const currentUsage = await usage.getCurrent(); logger.info('Current cost', { costInCents: currentUsage.costInCents, durationMs: currentUsage.durationMs, }); // Measure specific operation 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 ```ts // Cancel runs await runs.cancel('run_123'); // Replay runs with same payload await runs.replay('run_123'); // Retrieve run with cost details 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.