Karamihan sa mga production application ay nangangailangan ng trabaho na hindi kasya sa request/response cycle: pagpapadala ng email, pagpoproseso ng uploads, pagpapatakbo ng AI pipelines, pag-sync ng third-party data, paggawa ng reports. Ang tradisyonal na sagot ay isang queue (Redis, SQS, RabbitMQ), isang fleet ng workers, isang scheduler, at isang marupok na hilera ng glue code na nasisira sa bawat deploy.
Pinipiga ng Trigger.dev ang stack na iyon sa isang TypeScript SDK. Sumusulat ka ng functions, tinatawag mo sila kahit saan, at hinahawakan ng platform ang queueing, retries, observability, scheduling, at durable execution. Gumagana ang tasks hangga't kinakailangan - walang 10-second serverless timeout, walang nawawalang trabaho sa redeploys.
Bakit Trigger.dev
Ang shift sa 2026 ay durable execution. Dapat makaligtas ang workflows sa restarts, crashes, deploys, at rate limits. Dapat din nilang i-stream ang progress sa UI nang real time at i-pause para sa human input. Muling itinayo ang Trigger.dev sa paligid ng mga kinakailangang ito sa version 3 at patuloy na pinapalawak ang AI infrastructure surface nito.
Simple lang ang model: tinutukoy mo ang tasks bilang exports, kinukuha sila ng SDK, ini-schedule at pinapatakbo sila ng platform sa isolated containers, at ang run state ay napapanatili upang maaari mong ipagpatuloy, subukang muli, at obserbahan.
Pagsisimula
Initialize ng project
npx trigger.dev@latest login npx trigger.dev@latest init
Lumilikha ito ng isang trigger.config.ts file at isang trigger/ directory na may sample na tasks. Ang config file ang pinagmumulan ng katotohanan para sa iyong project: aling mga directory ang naglalaman ng tasks, build settings, lifecycle hooks, at runtime options.
// trigger.config.ts import { defineConfig } from "@trigger.dev/sdk"; export default defineConfig({ project: "proj_abc123", runtime: "node", logLevel: "log", maxDuration: 3600, retries: { enabledInDev: true, default: { maxAttempts: 3, factor: 2, minTimeoutInMs: 1000, maxTimeoutInMs: 30_000, }, }, dirs: ["./trigger"], });
Patakbuhin ang tasks lokal
npx trigger.dev@latest dev
Kumokonekta ang dev server sa cloud, nagrerehistro ng iyong tasks, at nag-stream ng runs sa pamamagitan ng iyong lokal na code. Naglalagay ka ng breakpoints sa iyong editor at tinatamaan ang mga ito sa totoong triggers - ang parehong loop na gagamitin mo sa anumang normal na Node.js project.
Pagdedefine ng Task
Ang Task ay isang object na ini-export gamit ang isang natatanging id at isang run function. Sinusuri ng SDK ang exports sa kabuuan ng dirs at awtomatiko silang ini-rehistro.
// trigger/send-welcome-email.ts import { task } from "@trigger.dev/sdk"; import { Resend } from "resend"; const resend = new Resend(process.env.RESEND_API_KEY); export const sendWelcomeEmail = task({ id: "send-welcome-email", retry: { maxAttempts: 5, factor: 1.8, minTimeoutInMs: 500, maxTimeoutInMs: 30_000, }, run: async (payload: { email: string; name: string }) => { const { data, error } = await resend.emails.send({ from: "hello@spinny.dev", to: payload.email, subject: `Welcome, ${payload.name}`, html: `<p>Glad you are here, ${payload.name}.</p>`, }); if (error) throw error; return { messageId: data?.id }; }, });
Tatlong bagay na dapat tandaan:
- Walang timeout sa run body. Pinamamahalaan ng platform ang oras ng pagpapatupad sa pamamagitan ng
maxDurationsa config, hindi sa runtime. - Throws ay retries. Hinuhuli ng SDK ang mga exception at muling pinapatakbo nang exponential backoff ayon sa
retrypolicy. - Pinapanatili ang return value. Ang ibang tasks at ang frontend mo ay maaaring magbasa ng
run.outputmula sa kahit saan.
Pag-trigger ng Tasks
Tatawagin mo ang task mula sa iyong backend, mga API route mo, o ibang task.
import { sendWelcomeEmail } from "@/trigger/send-welcome-email"; const handle = await sendWelcomeEmail.trigger( { email: "user@example.com", name: "Alex" }, { idempotencyKey: `welcome-${userId}`, concurrencyKey: `tenant-${tenantId}`, queue: { name: "emails", concurrencyLimit: 50 }, delay: "30s", ttl: "10m", } ); console.log(handle.id); // run_xyz - gamitin para subaybayan o ipakita ang progress
Ang mga opsyon ay nagbubukas ng maraming gawi sa isang tawag:
idempotencyKey- kung mayroon nang run na may parehong key, ibinabalik ng SDK ang umiiral na handle imbes na duplicate ang trabaho.concurrencyKey- ini-serialize ang runs na nagbabahagi ng key upang hindi mo lalagpas ang per-tenant rate limit.queue.concurrencyLimit- global cap para sa queue sa lahat ng keys.delay- inaiskedyul ang run sa isang oras sa hinaharap.ttl- kung hindi nagsimula ang run sa oras na iyon, awtomatikong mag-e-expire.
Batch trigger
Para sa fan-out workloads, tumatanggap ang batchTrigger ng hanggang 500 items kada tawag at lumilikha ng isang run kada item.
await sendWelcomeEmail.batchTrigger( newUsers.map((u) => ({ payload: { email: u.email, name: u.name }, options: { idempotencyKey: `welcome-${u.id}` }, })) );
Naka-iskedyul na Tasks
Ang Cron jobs ay nagiging first-class declarations. Ang schedule mismo ay isang hiwalay na object na maaari mong ikabit sa task nang maraming beses.
// trigger/daily-digest.ts import { schedules } from "@trigger.dev/sdk"; export const dailyDigest = schedules.task({ id: "daily-digest", cron: "0 9 * * *", run: async (payload) => { console.log("Scheduled at:", payload.timestamp); console.log("Last run:", payload.lastTimestamp); console.log("Timezone:", payload.timezone); console.log("Next 5 runs:", payload.upcoming); await sendDigestForDate(payload.timestamp); }, });
Para sa per-tenant schedules - sabihin nating, isang cron kada customer - dynamic mong nilikha sila sa pamamagitan ng management API.
import { schedules } from "@trigger.dev/sdk"; await schedules.create({ task: "daily-digest", cron: "0 9 * * *", timezone: "America/New_York", externalId: `customer_${customerId}`, deduplicationKey: `digest-${customerId}`, });
Ginagawang idempotent ng deduplicationKey ang tawag: ang muling pagpapatakbo ng parehong code sa deploy time ay hindi nag-iipon ng duplicate na schedules.
Queues, Concurrency, at Idempotency
Tatlong primitives ang sumasaklaw sa karamihan ng pangangailangan ng rate-limiting at ordering.
Karaniwang pattern: isang queue kada tenant na may maliit na per-key concurrency upang igalang ang rate limit ng vendor, plus isang idempotency key upang gawing ligtas ang retries.
await syncShopifyOrders.trigger( { shopId }, { queue: { name: `shopify-${shopId}`, concurrencyLimit: 2 }, concurrencyKey: shopId, idempotencyKey: `sync-${shopId}-${Date.now() / 60_000 | 0}`, } );
Waits at Long-Running Work
Ang Tasks ay maaaring i-pause nang hindi nagpapanatili ng connection o nagsusunog ng compute. Pinapanatili ng platform ang state at ipinagpapatuloy ang function kapag natapos ang wait.
import { wait } from "@trigger.dev/sdk"; export const onboarding = task({ id: "onboarding", run: async (payload: { userId: string }) => { await sendWelcomeEmail.triggerAndWait({ userId: payload.userId }); await wait.for({ days: 1 }); await sendTipsEmail.trigger({ userId: payload.userId }); await wait.until({ date: oneWeekFromSignup(payload.userId) }); await sendUpgradeOffer.trigger({ userId: payload.userId }); }, });
Ang triggerAndWait ay ang killer feature: nagti-trigger ito ng isang child task at sini-suspend ang parent hanggang sa matapos ang child. Bumubuo ka ng tasks gaya ng async functions, ngunit ang orchestration ay tumatakbo nang matatag sa loob ng mga araw o linggo.
Human-in-the-loop sa wait.forToken
Para sa approval flows at AI gates, ang wait.forToken ay nag-pa-pause hanggang sa tumawag pabalik ang iyong application na may resulta.
import { task, wait } from "@trigger.dev/sdk"; export const publishPost = task({ id: "publish-post", run: async (payload: { draftId: string }) => { const draft = await generateAIContent(payload.draftId); const token = await wait.createToken({ timeout: "7d" }); await notifyEditor({ draftId: draft.id, token: token.id }); const decision = await wait.forToken<{ approved: boolean; notes?: string }>( token.id ); if (decision.approved) { return await publish(draft); } return await applyFeedback(draft, decision.notes); }, });
Binubuksan ng editor ang isang UI, sinusuri ang draft, ki-click ang Approve, at kinukumpleto ng iyong backend ang token. Ipinagpapatuloy ng task kung saan ito tumigil - kahit na lumipas ang oras o mga araw.
Lifecycle Hooks
Maaari mong ikabit ang init, onStart, onSuccess, at onFailure sa isang task o globally sa trigger.config.ts. Gamitin sila para sa tracing, error reporting, at shared setup.
// trigger.config.ts export default defineConfig({ // ... init: async () => { Sentry.init({ dsn: process.env.SENTRY_DSN }); }, onFailure: async ({ error, ctx }) => { Sentry.captureException(error, { tags: { taskId: ctx.task.id, runId: ctx.run.id }, }); }, });
Tumatakbo ang init minsan kada worker container sa boot, hindi kada run, kaya ito ang tamang lugar upang i-set up ang clients at pools.
Realtime sa Frontend
Ang Trigger.dev ay nag-pu-publish ng mga pagbabago ng run state - status, metadata, output - sa pamamagitan ng streaming API. Ang React hooks ay nag-su-subscribe sa stream na iyon at awtomatikong nagre-render muli.
// trigger/process-video.ts import { task, metadata } from "@trigger.dev/sdk"; export const processVideo = task({ id: "process-video", run: async (payload: { videoId: string }) => { metadata.set("stage", "transcoding"); await transcode(payload.videoId); metadata.set("stage", "thumbnails"); await generateThumbnails(payload.videoId); metadata.set("stage", "uploading"); const url = await uploadToCDN(payload.videoId); return { url }; }, });
// components/VideoStatus.tsx "use client"; import { useRealtimeRun } from "@trigger.dev/react-hooks"; import type { processVideo } from "@/trigger/process-video"; export function VideoStatus({ runId, publicAccessToken, }: { runId: string; publicAccessToken: string; }) { const { run, error } = useRealtimeRun<typeof processVideo>(runId, { accessToken: publicAccessToken, }); if (error) return <p>Error: {error.message}</p>; if (!run) return <p>Loading...</p>; return ( <div> <p>Status: {run.status}</p> <p>Stage: {String(run.metadata?.stage ?? "queued")}</p> {run.output?.url && <video src={run.output.url} controls />} </div> ); }
Bumubuo ka ng public access token sa server-side, scoped sa isang partikular na run, at ipinapadala ito sa client. Hinahawakan ng hook ang auth, reconnection, at incremental updates.
Para sa trigger-and-subscribe sa isang hakbang:
import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks"; const { submit, run, isLoading } = useRealtimeTaskTrigger<typeof processVideo>( "process-video", { accessToken: publicAccessToken } ); <button onClick={() => submit({ videoId })} disabled={isLoading}> Process video </button>;
AI Agents at Streaming
Ang Trigger.dev ay naging isang popular na runtime para sa AI agents dahil ang parehong primitives - durable execution, retries, waits, real-time metadata, human-in-the-loop - ay eksaktong kailangan ng agents. Nag-stream ka ng tokens mula sa isang model provider sa metadata habang nangyayari ang run, ire-render ang frontend ang mga ito nang live, at nakaligtas ang run sa long-running tool calls nang hindi nagsusunog ng serverless timeout.
import { task, metadata } from "@trigger.dev/sdk"; import { streamText } from "ai"; import { anthropic } from "@ai-sdk/anthropic"; export const researchAgent = task({ id: "research-agent", maxDuration: 1800, run: async (payload: { question: string }) => { const result = streamText({ model: anthropic("claude-opus-4-7"), system: "You are a research assistant. Use the web.", prompt: payload.question, tools: { webSearch }, }); let fullText = ""; for await (const chunk of result.textStream) { fullText += chunk; metadata.set("partial", fullText); } return { answer: fullText, usage: await result.usage }; }, });
Gumagamit ang frontend ng useRealtimeRun at binabasa ang run.metadata.partial upang i-render ang streaming response, sa parehong paraan na ire-render mo ang isang chat completion - maliban na ang isang ito ay nakaligtas sa isang full page reload.
Pag-deploy
Ang mga deploys ay nagko-compile ng iyong tasks sa isang versioned bundle, bumubuo ng container, at atomically pinagbabago ang trapiko. Ang lumang in-flight runs ay patuloy na gumagamit ng nakaraang version.
npx trigger.dev@latest deploy --env prod
Sa CI ay karaniwang ikinakabit mo ito sa parehong workflow na nagpapadala ng iyong app:
# .github/workflows/deploy.yml - name: Deploy Trigger.dev env: TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }} run: npx trigger.dev@latest deploy --env prod
Para sa preview environments, ipasa ang --env preview --branch ${{ github.head_ref }} at ang Trigger.dev ay lumilikha ng isolated environment kada branch, na sumasalamin sa kung paano hawakan ng Vercel ang preview deployments.
Self-Hosting vs Cloud
Ang Trigger.dev ay open source sa ilalim ng Apache 2.0 license. Maaari kang mag-self-host sa anumang container platform (Docker Compose, Kubernetes, Fly.io) o gamitin ang managed cloud sa trigger.dev.
| Aspeto | Cloud | Self-hosted |
|---|---|---|
| Setup | Mag-sign up, patakbuhin ang init | Patakbuhin ang docker-compose o Helm chart |
| Scaling | Awtomatiko | Iyong responsibilidad |
| Pricing | Kada run + kada compute | Gastos lamang ng infra |
| Compliance | SOC 2 | Kahit anong ibinibigay ng iyong environment |
| Pinakamahusay para sa | Karamihan ng teams | Mahigpit na data residency, custom infra |
Ang SDK at CLI ay magkapareho sa pagitan ng modes - binabago mo ang isang profile flag at itinuturo sa iyong sariling instance.
Best Practices
1. Panatilihing maliit at serializable ang payloads
Ipasa ang IDs at references, hindi buong objects. Hilahin ang data sa loob ng task. Pinapanatili nitong maliit ang queue, mura ang payloads na i-log, at pinapayagan kang baguhin ang data source nang hindi muling nag-tri-trigger.
2. Idempotency keys sa bawat external call
Pagsamahin ang idempotencyKey sa task trigger sa idempotency keys sa iyong vendor APIs (Stripe, OpenAI, atbp.). Magiging ligtas ang retries sa end-to-end.
3. Gamitin ang triggerAndWait para sa orchestration, hindi Promise.all ng triggers
Ang isang parent na tumatawag ng triggerAndWait ay durable na nagsasama-sama ng child tasks. Ang isang parent na nag-tri-trigger at nag-re-resolve agad ay nawawalan ng observability ng chain.
4. I-tag ang runs
Magdagdag ng tags sa triggers (tags: ["user:123", "feature:onboarding"]) upang ma-filter mo ang dashboard at management API ayon sa business dimensions.
5. Panatilihing idempotent ang init
Tumatakbo ito sa bawat cold start. Iwasan ang migrations o one-shot side effects doon.
Konklusyon
Inaalis ng Trigger.dev ang mga kategorya ng trabaho na dating nangangailangan ng pagbuo ng job system mula sa simula. Sumusulat ka ng async TypeScript, tinatawag mo ito kahit saan, at binibigyan ka ng platform ng durable execution, scheduling, queues, retries, real-time updates, at human-in-the-loop patterns out of the box.
Ang parehong surface na nagpapatakbo ng nightly cron ay ang surface na nagpapatakbo ng multi-step AI agent na nag-stream sa frontend at nag-pa-pause para sa review. Ang convergence na iyon ang nagbibigay ng halaga sa framework para sa seryosong tingin sa 2026, kahit nagpapatakbo ka ng SaaS na nangangailangan ng maaasahang background work o nagpapadala ng AI features na nakaligtas sa serverless timeout.
Checklist sa Pagsisimula:
- Mag-sign up sa trigger.dev o patakbuhin ang self-hosted Docker stack
npx trigger.dev@latest initsa iyong project- Tukuyin ang iyong unang task gamit ang
task({ id, run })- I-trigger ito mula sa iyong API at panoorin ang run sa dashboard
- Magdagdag ng
idempotencyKeyatconcurrencyKeypara sa kaligtasan ng production- Ikabit ang
useRealtimeRunsa isang status component- I-deploy gamit ang
trigger.dev deploy --env prodmula sa CI