December 3, 2024

Build extensions

Install system packages, like FFmpeg and Puppeteer, and modify the build process.

Build extensions allow you to install system packages, run container commands, modify package dependencies, use ESBuild plugins, and more.

What can I build with them?

This is a big deal because many system packages don't work in serverless environments, like Vercel.

Using the FFmpeg extension and how it works

To use our official FFmpeg extension you just modify your trigger.config.ts file to include the extension.

trigger.config.ts

import { ffmpeg } from "@trigger.dev/build/extensions/core";
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [ffmpeg()],
},
});

Then in your tasks you can use FFmpeg. In this example, we compress a video using the fluent-ffmpeg library.

trigger/compressVideo.ts

import { task, logger } from "@trigger.dev/sdk/v3";
import ffmpeg from "fluent-ffmpeg";
import { Readable } from "node:stream";
import os from "os";
import path from "path";
export const compressVideo = task({
id: "compress-video",
run: async (payload: { videoUrl: string }) => {
const { videoUrl } = payload;
const response = await fetch(videoUrl);
const outputPath = path.join(os.tmpdir(), `output_${Date.now()}.mp4`);
// Compress the video
await new Promise((resolve, reject) => {
if (!response.body) {
return reject(new Error("Failed to fetch video"));
}
ffmpeg(Readable.fromWeb(response.body))
.outputOptions(["-c:v libx264", "-crf 28"])
.output(outputPath)
.on("end", resolve)
.on("error", reject)
.run();
});
logger.log("Compressed video");
const url = await uploadVideoSomewhere(outputPath);
return {
url
};
},
});

Here's a short video from our dashboard of this task running in prod:

How does this work?

The FFmpeg extension uses the onBuildComplete hook, which gets called after ESBuild has packaged the code.

You can add layers which can:

  • Modify the Docker image.
    • Install system packages usingpkgs.
    • Add instructions using instructions.
  • Run commands at the end of the build process using commands.
  • Set environment variables.

Here's the full source code for the FFmpeg extension:

ffmpeg.ts

/**
* Add ffmpeg to the build, and automatically set the FFMPEG_PATH and FFPROBE_PATH environment variables.
* @param options.version The version of ffmpeg to install. If not provided, the latest version will be installed.
*
* @returns The build extension.
*/
export function ffmpeg(options: { version?: string; } = {}): BuildExtension {
return {
name: "ffmpeg",
onBuildComplete(context) {
if (context.target === "dev") {
return;
}
context.logger.debug("Adding ffmpeg", {
options,
});
context.addLayer({
id: "ffmpeg",
image: {
pkgs: options.version ? [`ffmpeg=${options.version}`] : ["ffmpeg"],
},
deploy: {
env: {
FFMPEG_PATH: "/usr/bin/ffmpeg",
FFPROBE_PATH: "/usr/bin/ffprobe",
},
override: true,
},
});
},
};
}

Here's a full example of using FFmpeg to process a video.

Puppeteer

Puppeteer is a headless browser that allows you to interact with web pages. You can use it to take screenshots, generate PDFs, and more.

Use the extension like this:

trigger.config.ts

import { defineConfig } from "@trigger.dev/sdk/v3";
import { puppeteer } from "@trigger.dev/build/extensions/puppeteer";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [puppeteer()],
},
});

This very simple task logs out the title of a web page:

trigger/puppeteer.ts

import { logger, task } from "@trigger.dev/sdk/v3";
import puppeteer from "puppeteer";
export const puppeteerTask = task({
id: "puppeteer-log-title",
run: async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto("https://trigger.dev");
const content = await page.title();
logger.info("Content", { content });
await browser.close();
},
});

IMPORTANT

Please note it's against our Cloud Terms of Service to use Puppeteer to scrape websites where you don't have permission, unless you use a proxy. We recommend using a service like BrowserBase which we have a guide for here.

The source code for the Puppeteer extension is a bit more complicated because it runs a few instructions in the image. But it's still pretty simple.

LibreOffice: how to use aptGet() to install system packages

It's so useful to install system packages that we created the aptGet extension to make it easier:

trigger.config.ts

import { defineConfig } from "@trigger.dev/sdk/v3";
import { aptGet } from "@trigger.dev/build/extensions/core";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [aptGet({ packages: ["libreoffice"] })],
},
});

Here's how to use LibreOffice to convert documents to PDFs.

ESBuild plugins

We use ESBuild to bundle your task code. It is fast, removes unused code, and has a mature plugin system.

You can add ESBuild plugins using our esbuildPlugin extension, in this case we're using the Sentry ESBuild plugin:

trigger.config.ts

import { defineConfig } from "@trigger.dev/sdk/v3";
import { esbuildPlugin } from "@trigger.dev/build/extensions";
import { sentryEsbuildPlugin } from "@sentry/esbuild-plugin";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [
esbuildPlugin(
sentryEsbuildPlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
}),
// optional - only runs during the deploy command, and adds the plugin to the end of the list of plugins
{ placement: "last", target: "deploy" }
),
],
},
});

More official build extensions

  • Prisma: handles generating the Prisma client.
  • Sync env vars: syncs environment variables from another service to Trigger.dev.
  • Vercel sync env vars: syncs environment variables from Vercel to Trigger.dev.
  • Audio Waveform: generates waveform data and images from audio files.
  • Emit decorators: emits metadata for TypeScript decorators, needed for NestJS and MikroORM.

Writing your own extensions

Hopefully I've shown how powerful the build extension system is.

If you want to write your own extension I recommend you read the docs, and view the source code for our official extensions.

If you've built something cool please let us know, we'd love to feature it in our docs or turn it into an official extension.

Ready to start building?

Build and deploy your first task in 3 minutes.

Get started now
,