ffmpegdenovideo-processingapibucket-doc

How to Use FFmpeg with Deno (No Installation Required)

·Javid Jamae·7 min read
How to Use FFmpeg with Deno (No Installation Required)

You're building something in Deno and you need video processing. Thumbnail generation, format conversion, transcoding user uploads. You search for "ffmpeg deno" and quickly realize the ecosystem isn't like Node.js. There's no fluent-ffmpeg equivalent. No battle-tested wrapper with thousands of weekly downloads.

Deno's security model makes this worse. By default, Deno sandboxes your code. No file system access, no subprocess execution, no network calls unless you explicitly grant permissions. That's great for security. It's a headache when you need to shell out to a native binary like FFmpeg.

There are three realistic paths. Each works, each has real tradeoffs.

Using Deno.Command with a Local FFmpeg Binary

If FFmpeg is installed on the machine, you can call it from Deno using Deno.Command. This replaced the deprecated Deno.run API and is the current way to spawn subprocesses.

const command = new Deno.Command("ffmpeg", {
  args: [
    "-i", "input.mp4",
    "-c:v", "libx264",
    "-crf", "23",
    "-preset", "medium",
    "-c:a", "aac",
    "-b:a", "192k",
    "output.mp4",
  ],
  stderr: "piped",
});

const process = command.spawn();
const { code } = await process.status;

if (code !== 0) {
  const stderr = new TextDecoder().decode((await process.stderr).bytes());
  console.error("FFmpeg failed:", stderr);
} else {
  console.log("Transcoding complete");
}

You must run this with --allow-run=ffmpeg and --allow-read / --allow-write flags. Without them, Deno blocks the subprocess call at runtime.

This approach works for local scripts and CI pipelines where you control the environment. But it breaks down fast in production. You need FFmpeg installed on every machine that runs your code. On Deno Deploy, you can't install system binaries at all. And the permission flags defeat much of the point of Deno's security model if you're granting broad access anyway.

Using FFmpeg WASM in Deno

ffmpeg.wasm compiles FFmpeg to WebAssembly, so it runs without a native binary. You can load it in Deno through npm compatibility:

import { FFmpeg } from "npm:@ffmpeg/ffmpeg";
import { fetchFile } from "npm:@ffmpeg/util";

const ffmpeg = new FFmpeg();
await ffmpeg.load();

await ffmpeg.writeFile("input.mp4", await fetchFile("./input.mp4"));
await ffmpeg.exec(["-i", "input.mp4", "-c:v", "libx264", "output.mp4"]);
const data = await ffmpeg.readFile("output.mp4");

await Deno.writeFile("output.mp4", data);

No FFmpeg binary needed. No --allow-run flag. But the tradeoffs are significant. Processing is 10-20x slower than native FFmpeg because WebAssembly can't match native CPU performance for heavy compute work. Memory is constrained. Not all codecs are available in the WASM build. And large files (anything over a few hundred MB) will likely crash.

ffmpeg.wasm is fine for lightweight operations like extracting a single frame or trimming a short clip. Don't try to transcode a full-length video with it.

Using the FFmpeg Micro Cloud API

If you don't want to manage FFmpeg binaries, fight with permission flags, or accept WASM performance penalties, a cloud API is the cleanest path. FFmpeg Micro processes video through standard HTTP requests, which Deno handles natively with fetch(). No npm packages. No binary dependencies. No special permissions beyond --allow-net.

const response = await fetch("https://api.ffmpeg-micro.com/v1/transcodes", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${Deno.env.get("FFMPEG_MICRO_API_KEY")}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    inputs: [{ url: "https://example.com/video.mp4" }],
    outputFormat: "mp4",
    preset: { quality: "high", resolution: "1080p" },
  }),
});

const job = await response.json();
console.log(`Job ${job.id} status: ${job.status}`);

One fetch call. FFmpeg Micro handles the transcoding infrastructure, codec management, and scaling. Your Deno app just sends an HTTP request.

For advanced use cases where you need specific FFmpeg flags, use the options array instead of presets:

const response = await fetch("https://api.ffmpeg-micro.com/v1/transcodes", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${Deno.env.get("FFMPEG_MICRO_API_KEY")}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    inputs: [{ url: "https://example.com/video.mp4" }],
    outputFormat: "webm",
    options: [
      { option: "-c:v", argument: "libvpx-vp9" },
      { option: "-crf", argument: "30" },
      { option: "-b:v", argument: "0" },
      { option: "-c:a", argument: "libopus" },
    ],
  }),
});

Polling for Completion

Transcodes are async. Poll the status endpoint until the job finishes:

async function waitForTranscode(jobId: string): Promise<Record<string, unknown>> {
  const apiKey = Deno.env.get("FFMPEG_MICRO_API_KEY");

  while (true) {
    const res = await fetch(
      `https://api.ffmpeg-micro.com/v1/transcodes/${jobId}`,
      { headers: { "Authorization": `Bearer ${apiKey}` } },
    );
    const job = await res.json();

    if (job.status === "completed" || job.status === "failed") {
      return job;
    }

    await new Promise((r) => setTimeout(r, 2000));
  }
}

Once completed, grab the download URL:

const downloadRes = await fetch(
  `https://api.ffmpeg-micro.com/v1/transcodes/${job.id}/download`,
  { headers: { "Authorization": `Bearer ${apiKey}` } },
);
const { url } = await downloadRes.json();
console.log("Download:", url);

This works identically on your local machine, in a Docker container, on Deno Deploy, or anywhere else Deno runs. No environment-specific setup.

Which Approach Should You Use

Deno.Command if you're writing a local script or CLI tool where you control the machine and FFmpeg is already installed.

ffmpeg.wasm if you need client-side processing for small files and can tolerate much slower performance.

A cloud API like FFmpeg Micro if you're building a production app, deploying to Deno Deploy, or want to avoid managing FFmpeg binaries entirely. The API approach is the only one that works on Deno Deploy, since Deploy doesn't support subprocess execution or native binary installation.

Common Pitfalls

Using Deno.run instead of Deno.Command. Deno.run is deprecated and removed in recent versions of Deno. Always use Deno.Command for subprocess calls.

Forgetting permission flags. Deno's sandbox blocks everything by default. Running FFmpeg as a subprocess needs --allow-run, --allow-read, and --allow-write at minimum. Missing any one of them gives you a PermissionDenied error at runtime, not at startup.

Assuming Deno Deploy supports subprocesses. It doesn't. Deno Deploy runs your code in a V8 isolate with no access to system binaries. If you're deploying there, your only options are ffmpeg.wasm (slow) or a cloud API (fast). The same applies to most edge runtimes.

Loading ffmpeg.wasm without enough memory. The WASM module itself is large. On resource-constrained environments, just loading it can fail. If you're going the WASM route, test on your actual deployment target, not just your local machine.

FAQ

Can I use FFmpeg on Deno Deploy?

Not directly. Deno Deploy doesn't allow subprocess execution or native binary installation. You can use ffmpeg.wasm for lightweight operations, but for production video processing, a cloud API like FFmpeg Micro is the practical choice. It runs through standard fetch calls, which Deno Deploy fully supports.

Is there a fluent-ffmpeg equivalent for Deno?

No. As of 2026, there's no Deno-native FFmpeg wrapper with the same maturity as fluent-ffmpeg in Node.js. You can import fluent-ffmpeg via Deno's npm compatibility (npm:fluent-ffmpeg), but it still requires a local FFmpeg binary and was designed for Node.js. For a Deno-native approach, use Deno.Command for local work or the FFmpeg Micro API for cloud processing.

How does Deno video processing compare to Node.js?

The FFmpeg CLI itself is identical. The difference is in how you call it. Node.js uses child_process.spawn, Deno uses Deno.Command. Both need the binary installed. The cloud API approach is the same across both runtimes since it's just HTTP fetch. If you're coming from Node.js, check the Node.js FFmpeg guide or the TypeScript FFmpeg guide for a side-by-side comparison.

What video formats does the FFmpeg Micro API support?

FFmpeg Micro accepts any format FFmpeg can read as input. For output, you can specify mp4, webm, or mov in the outputFormat field. The preset mode supports quality levels (high, medium, low) and resolution options (480p, 720p, 1080p, 4k). Check the full API docs for the complete list.

Do I need to pay to use FFmpeg Micro with Deno?

FFmpeg Micro has a free tier with enough processing time to build and test your integration. You can sign up for a free API key and start processing video in minutes. For a hands-on walkthrough of FFmpeg concepts, the Learn FFmpeg training covers everything from basics to automation.

About Javid Jamae

Founder & CEO at FFmpeg Micro

Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.

Software EngineeringVideo ProcessingFFmpegCloud ArchitectureAPI DesignAutomation

Ready to process videos at scale?

Start using FFmpeg Micro's simple API today. No infrastructure required.

Get Started Free