JavaScript is a practical way to automate video background removal. Instead of uploading each clip by hand, you can submit a video from Node.js, let Unscreen process it, and download the finished transparent or background-removed result from your own script, backend route, queue worker, or content pipeline.
This guide shows how to use @unscreen/video-background-remover from install to production-style usage. You will see two main approaches:
- wait for a job to finish inside the same Node.js process
- submit the job without waiting and receive the result through a webhook callback
1. Install the SDK
The package is published as an npm Node.js library and requires Node.js 20 or newer.
npm install @unscreen/video-background-remover
Set your API key as an environment variable:
export UNSCREEN_API_KEY="your_api_key_here"
In production, set the same variable in your hosting provider, CI runner, container secret, or server environment. Do not ship the API key to browser-side JavaScript.
2. Create a client
Create one Unscreen client and reuse it where you submit jobs.
import { Unscreen } from "@unscreen/video-background-remover";
const unscreen = new Unscreen({
apiKey: process.env.UNSCREEN_API_KEY,
});
The SDK reads the API key from UNSCREEN_API_KEY when you pass it explicitly like this. Keeping it explicit also makes tests and deployment configuration easier to understand.
3. Fastest path: remove and download with wait
For a simple Node.js script, use removeBackground. When no webhookUrl is provided, this convenience method waits for the job to complete by default.
import { Unscreen } from "@unscreen/video-background-remover";
const unscreen = new Unscreen({
apiKey: process.env.UNSCREEN_API_KEY,
});
await unscreen.removeBackground({
input: "./input.mp4",
output: "./output.mp4",
mode: "auto",
});
console.log("Saved ./output.mp4");
This flow creates the job, uploads the local video, starts processing, waits until the status is terminal, and writes the finished output video to ./output.mp4.
The mode option accepts auto or human_only. Use auto when Unscreen should detect the foreground subject automatically, and use human_only when the clip should focus on people as the foreground.
4. Manual polling with "jobs.wait"
If you want more control, split the workflow into explicit steps:
import { Unscreen } from "@unscreen/video-background-remover";
const unscreen = new Unscreen({
apiKey: process.env.UNSCREEN_API_KEY,
});
const started = await unscreen.jobs.submit({
input: "./input.mp4",
mode: "auto",
});
console.log(`Started job ${started.jobId}`);
const completed = await unscreen.jobs.wait(started.jobId, {
intervalMs: 2000,
timeoutMs: 10 * 60 * 1000,
});
await unscreen.jobs.download(completed, {
asset: "outputVideo",
path: "./output.mp4",
});
console.log(`Downloaded result for ${completed.jobId}`);
jobs.wait polls the job status until it becomes completed or failed. If the job fails, the SDK throws an UnscreenError. If it takes longer than timeoutMs, it throws an UnscreenTimeoutError.
5. Use a remote video URL instead of a local file
The input field can be a local path, a remote HTTP(S) URL, a Blob, an ArrayBuffer, or a Uint8Array.
const completed = await unscreen.removeBackground({
input: "https://example.com/videos/product-demo.mp4",
output: "./product-demo-no-background.mp4",
mode: "auto",
});
console.log(completed.status);
When you use a remote URL, the SDK fetches the file and uploads it to the Unscreen job. If the URL does not expose a recognizable video content type, you can pass contentType manually:
await unscreen.jobs.submit({
input: "https://example.com/download?id=123",
contentType: "video/mp4",
});
6. Download different result assets
The default download asset is outputVideo. You can also request other assets when they are available on the completed job:
await unscreen.jobs.download(completed, {
asset: "alphaVideo",
path: "./alpha.webm",
});
await unscreen.jobs.download(completed, {
asset: "mask",
path: "./mask.mp4",
});
Supported asset names are:
outputVideoalphaVideomaskpreviewImagemetadata
Use the asset that matches your pipeline. For example, a creator tool may need outputVideo, while a compositing workflow may prefer alphaVideo or mask.
7. Submit without wait by using a webhook callback
Polling is simple, but it keeps your Node.js process busy until the video is done. For web apps, marketplaces, user uploads, and background queues, a webhook is usually better.
When you pass webhookUrl, removeBackground submits and starts the job without waiting unless you explicitly set wait: true.
import { Unscreen } from "@unscreen/video-background-remover";
const unscreen = new Unscreen({
apiKey: process.env.UNSCREEN_API_KEY,
});
const started = await unscreen.removeBackground({
input: "./input.mp4",
mode: "auto",
webhookUrl: "https://example.com/api/webhooks/unscreen",
});
console.log(`Started ${started.jobId}; waiting for webhook callback`);
This returns after the upload and start request. Your server receives the completion event later.
8. Handle the webhook event in Node.js
Here is a minimal Express-style endpoint:
import express from "express";
import { writeFile } from "node:fs/promises";
const app = express();
app.use(express.json());
app.post("/api/webhooks/unscreen", async (req, res) => {
const event = req.body;
if (event.eventType === "video.completed") {
console.log(`Job completed: ${event.jobId}`);
if (event.resultUrl) {
const response = await fetch(event.resultUrl);
const arrayBuffer = await response.arrayBuffer();
await writeFile(
`./downloads/${event.jobId}.mp4`,
Buffer.from(arrayBuffer)
);
}
}
if (event.eventType === "video.failed") {
console.error(`Job failed: ${event.jobId}`);
}
res.sendStatus(200);
});
app.listen(3000, () => {
console.log("Webhook server listening on http://localhost:3000");
});
For a production app, store the jobId when you submit the job, then update that database row when the webhook arrives. The webhook should respond quickly with a 2xx status after you have accepted the event.
9. Type the webhook event
If you write TypeScript, import the WebhookEvent type:
import type { WebhookEvent } from "@unscreen/video-background-remover";
export async function handleUnscreenWebhook(event: WebhookEvent) {
if (event.eventType === "video.completed") {
console.log(event.jobId, event.resultUrl);
return;
}
if (event.eventType === "video.failed") {
console.error(`Unscreen job failed: ${event.jobId}`);
}
}
Webhook event names are video.completed and video.failed.
10. Force wait even when using a webhook
Most webhook flows should not wait. But if you need both a webhook and a synchronous result, pass wait: true:
const completed = await unscreen.removeBackground({
input: "./input.mp4",
output: "./output.mp4",
webhookUrl: "https://example.com/api/webhooks/unscreen",
wait: true,
waitOptions: {
intervalMs: 3000,
timeoutMs: 15 * 60 * 1000,
},
});
console.log(completed.status);
This is useful for internal tooling, but most user-facing apps should prefer the non-waiting webhook pattern so requests do not stay open for a long video.
11. Check credits before a batch
For batch jobs, check the current credit balance before submitting work:
const balance = await unscreen.credits.getBalance();
console.log(`Credits remaining: ${balance.credits}`);
You can use this before queueing a folder of videos or before accepting a paid user upload.
12. Basic error handling
Wrap job submission, waiting, and downloads in try/catch:
import {
Unscreen,
UnscreenError,
UnscreenTimeoutError,
} from "@unscreen/video-background-remover";
const unscreen = new Unscreen({
apiKey: process.env.UNSCREEN_API_KEY,
});
try {
await unscreen.removeBackground({
input: "./input.mp4",
output: "./output.mp4",
waitOptions: {
timeoutMs: 10 * 60 * 1000,
},
});
} catch (error) {
if (error instanceof UnscreenTimeoutError) {
console.error("The job is still running. Check status later.");
} else if (error instanceof UnscreenError) {
console.error(error.message);
} else {
throw error;
}
}
For queues and background workers, store the jobId as soon as you receive it. That lets you inspect status later even if a worker restarts.
Which flow should you choose?
Choose wait when:
- you are building a CLI script
- the video is short enough for the process to stay open
- you want the output file immediately
- the job is part of a controlled batch worker
Choose webhook callbacks when:
- users upload videos through your app
- jobs may take longer than an HTTP request should stay open
- you need reliable async processing
- you want to update a database, dashboard, email, or notification after completion
The SDK supports both patterns, so you can start with the simple wait workflow and move to webhooks when your app needs asynchronous processing.
Complete wait example
import { Unscreen } from "@unscreen/video-background-remover";
const unscreen = new Unscreen({
apiKey: process.env.UNSCREEN_API_KEY,
});
const completed = await unscreen.removeBackground({
input: "./input.mp4",
output: "./output.mp4",
mode: "auto",
waitOptions: {
intervalMs: 2000,
timeoutMs: 10 * 60 * 1000,
},
});
console.log(`Finished ${completed.jobId}`);
Complete webhook example
import { Unscreen } from "@unscreen/video-background-remover";
const unscreen = new Unscreen({
apiKey: process.env.UNSCREEN_API_KEY,
});
const started = await unscreen.removeBackground({
input: "./input.mp4",
mode: "auto",
webhookUrl: "https://example.com/api/webhooks/unscreen",
});
console.log(`Submitted ${started.jobId}`);
That is the core Node.js workflow: install the SDK, create a client, submit a video, and decide whether your app should wait for the result or receive it later through a webhook.