De fleste produktionsapplikationer har brug for arbejde, der ikke passer ind i request/response-cyklussen: afsendelse af e-mails, behandling af uploads, kørsel af AI-pipelines, synkronisering af tredjepartsdata, generering af rapporter. Det traditionelle svar er en koe (Redis, SQS, RabbitMQ), en flade af workers, en scheduler og en skroebelig bunke limkode, der gar i stykker ved hver deployment.
Trigger.dev komprimerer den stack til et enkelt TypeScript SDK. Du skriver funktioner, kalder dem fra hvor som helst, og platformen handterer queueing, retries, observability, scheduling og holdbar udforsel. Tasks korer sa lange, der er brug for - ingen 10-sekunders serverless timeout, intet tabt arbejde ved redeploys.
Hvorfor Trigger.dev
Skiftet i 2026 er holdbar udforsel. Workflows skal overleve genstarter, nedbrud, deploys og rate limits. De skal ogsa streame fremgang til UI'en i realtid og pause for menneskelig input. Trigger.dev blev genopbygget omkring disse krav med version 3 og fortsætter med at udvide sin AI-infrastrukturoverflade.
Modellen er enkel: du definerer tasks som exports, SDK'et henter dem, platformen planlaegger og kører dem i isolerede containere, og run-tilstanden bevares, sa du kan genoptage, prove igen og observere.
Kom i gang
Initialiser et projekt
npx trigger.dev@latest login npx trigger.dev@latest init
Dette opretter en trigger.config.ts fil og en trigger/ mappe med eksempel-tasks. Konfigurationsfilen er sandhedskilden for dit projekt: hvilke mapper indeholder tasks, byggeindstillinger, lifecycle hooks og runtime-muligheder.
// 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"], });
Kor tasks lokalt
npx trigger.dev@latest dev
Dev-serveren forbinder til skyen, registrerer dine tasks og streamer runs gennem din lokale kode. Du saetter breakpoints i din editor og rammer dem pa rigtige triggers - samme loop, du ville bruge i ethvert almindeligt Node.js-projekt.
Definere en Task
En task er et objekt, der eksporteres med et unikt id og en run funktion. SDK'et inspicerer eksporterne paa tvers af dirs og registrerer dem automatisk.
// 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 }; }, });
Tre ting at bemaerke:
- Ingen timeout i run-kroppen. Platformen handterer udforselstiden via
maxDurationi konfigurationen, ikke i runtime. - Throws er retries. SDK'et fanger undtagelser og korer igen med eksponentiel backoff i henhold til
retry-politikken. - Returvardien bevares. Andre tasks og din frontend kan laese
run.outputhvor som helst.
Trigge Tasks
Du kalder en task fra din backend, dine API-ruter eller en anden 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 - brug til at spore eller vise fremskridt
Mulighederne lasker meget adfaerd op i et enkelt opkald:
idempotencyKey- hvis et run med samme noegle allerede eksisterer, returnerer SDK'et det eksisterende handle i stedet for at duplikere arbejde.concurrencyKey- serialiserer runs, der deler noeglen, sa du ikke overskrider en per-tenant rate limit.queue.concurrencyLimit- global cap for koen pa tvers af alle noegler.delay- planlaegger run for en fremtidig tid.ttl- hvis run ikke er startet inden da, udlober automatisk.
Batch trigger
For fan-out-arbejdsbelastninger accepterer batchTrigger op til 500 elementer per opkald og opretter en run per element.
await sendWelcomeEmail.batchTrigger( newUsers.map((u) => ({ payload: { email: u.email, name: u.name }, options: { idempotencyKey: `welcome-${u.id}` }, })) );
Planlagte Tasks
Cron-jobs bliver foersteklasses erklaeringer. Selve schedule er et separat objekt, du kan vedhaefte til en task flere gange.
// 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); }, });
For per-tenant schedules - sig en cron per kunde - opretter du dem dynamisk via 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}`, });
deduplicationKey gor opkaldet idempotent: at kore samme kode igen ved deploy-tid stabler ikke duplikerede schedules.
Koer, Samtidighed og Idempotens
Tre primitiver daekker de fleste behov for rate-limiting og rakkefølge.
Et almindeligt monster: en koe per tenant med en lille per-key samtidighed for at respektere en vendors rate limit, plus en idempotens noegle for at gore retries sikre.
await syncShopifyOrders.trigger( { shopId }, { queue: { name: `shopify-${shopId}`, concurrencyLimit: 2 }, concurrencyKey: shopId, idempotencyKey: `sync-${shopId}-${Date.now() / 60_000 | 0}`, } );
Ventetid og langvarigt arbejde
Tasks kan pause uden at holde en forbindelse eller braende compute. Platformen bevarer tilstanden og genoptager funktionen, naar ventetiden er afsluttet.
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 }); }, });
triggerAndWait er killer-funktionen: den triggrer en barntask og suspenderer foralderen, indtil barnet er faerdigt. Du komponerer tasks som async-funktioner, men orkestrationen korer holdbart over dage eller uger.
Human-in-the-loop med wait.forToken
For godkendelsesstromninger og AI-gates pauser wait.forToken, indtil din applikation kalder tilbage med et resultat.
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); }, });
Redaktoren abner et UI, gennemgar udkastet, klikker pa Godkend, og din backend afslutter token. Task fortsaetter, hvor den slap - selv hvis timer eller dage er gaet.
Lifecycle Hooks
Du kan vedhaefte init, onStart, onSuccess og onFailure til en task eller globalt i trigger.config.ts. Brug dem til tracing, error reporting og delt 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 }, }); }, });
init kører en gang per worker container ved boot, ikke per run, sa det er det rigtige sted at opsaette klienter og pools.
Realtid i Frontenden
Trigger.dev publicerer run state-aendringer - status, metadata, output - over et streaming API. React-hooks abonnerer pa den stream og re-renderer automatisk.
// 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> ); }
Du genererer det offentlige access token pa serversiden, scoped til en specifik run, og sender det til klienten. Hooket handterer auth, gentilslutning og inkrementelle opdateringer.
For trigger-and-subscribe i et trin:
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-agenter og Streaming
Trigger.dev er blevet en populaer runtime for AI-agenter, fordi de samme primitiver - holdbar udforsel, retries, ventetid, realtidsmetadata, human-in-the-loop - er praecis det, agenter har brug for. Du streamer tokens fra en model-udbyder til metadata, mens run finder sted, frontenden renderer dem live, og run overlever langvarige tool calls uden at braende et 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 }; }, });
Frontenden bruger useRealtimeRun og laeser run.metadata.partial for at rendere streaming-svaret, pa samme made som du ville rendere en chat completion - undtagen at denne overlever en fuld side-genindlaesning.
Deploye
Deployments kompilerer dine tasks til en versioneret bundle, bygger en container og bytter trafik atomart. Gamle in-flight runs fortsætter med at bruge den tidligere version.
npx trigger.dev@latest deploy --env prod
I CI forbinder du typisk dette til samme workflow, der sender din 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
For preview-miljoer, send --env preview --branch ${{ github.head_ref }} og Trigger.dev opretter et isoleret miljo per branch, hvilket afspejler den made, hvorpa Vercel handterer preview-deployments.
Self-Hosting vs Cloud
Trigger.dev er open source under Apache 2.0-licensen. Du kan self-hoste pa enhver containerplatform (Docker Compose, Kubernetes, Fly.io) eller bruge den administrerede sky pa trigger.dev.
| Aspekt | Cloud | Self-hosted |
|---|---|---|
| Setup | Tilmeld, kor init | Kor docker-compose eller Helm chart |
| Skalering | Automatisk | Dit ansvar |
| Pricing | Per run + per compute | Kun infra-omkostning |
| Compliance | SOC 2 | Hvad dit miljo leverer |
| Bedst til | De fleste teams | Streng dataresidens, custom infra |
SDK'et og CLI'en er identiske mellem tilstande - du aendrer et profile flag og peger pa din egen instans.
Best Practices
1. Hold payloads sma og serialiserbare
Send IDer og referencer, ikke fulde objekter. Hent data inde i task. Dette holder koen lille, payloads billige at logge og lader dig aendre datakilden uden at trigge igen.
2. Idempotens-noegler pa hvert eksternt opkald
Kombiner idempotencyKey pa task-trigger med idempotens-noegler pa dine vendor-API'er (Stripe, OpenAI, etc.). Retries bliver end-to-end sikre.
3. Brug triggerAndWait til orkestrering, ikke Promise.all af triggers
En forelder, der kalder triggerAndWait, komponerer holdbart child tasks. En forelder, der trigger og loeser med det samme, mister observerbarhed af kaeden.
4. Tag runs
Tilfojer tags til triggers (tags: ["user:123", "feature:onboarding"]), sa du kan filtrere dashboard og management API efter forretningsdimensioner.
5. Hold init idempotent
Den korer ved hver cold start. Undga migreringer eller engangs-bivirkninger der.
Konklusion
Trigger.dev fjerner de kategorier af arbejde, der tidligere kraevede at bygge et job-system fra bunden. Du skriver async TypeScript, du kalder det fra hvor som helst, og platformen giver dig holdbar udforsel, scheduling, koer, retries, realtidsopdateringer og human-in-the-loop monstre ud af kassen.
Den samme overflade, der driver en natlig cron, er den overflade, der driver en multi-step AI-agent, der streamer til frontenden og pauser for review. Den konvergens er det, der gor framework vaerd at se serioest pa i 2026, uanset om du driver en SaaS, der har brug for paalideligt baggrundsarbejde, eller sender AI-funktioner, der overlever en serverless timeout.
Komme i gang Checklist:
- Tilmeld dig pa trigger.dev eller kor self-hosted Docker stack
npx trigger.dev@latest initi dit projekt- Definer din forste task med
task({ id, run })- Trigge den fra din API og se run i dashboardet
- Tilfojer
idempotencyKeyogconcurrencyKeyfor produktionssikkerhed- Forbind
useRealtimeRuntil en statuskomponent- Deploy med
trigger.dev deploy --env prodfra CI