remotion-dev/remotion · error · TypeError

outputs.${layer}.outputWritable must be a WritableStream.

Error message

outputs.${layer}.outputWritable must be a WritableStream.

What it means

separateVideoLayers() validates each layer's output options. When you pass outputs.<layer>.outputWritable, it must be an actual Web WritableStream instance; anything else (a Node.js stream, a string path, a plain object) is rejected with this TypeError because the library pipes MediaRecorder/WebCodecs output into it via the standard streams API.

Source

Thrown at packages/video-matting/src/separate-video-layers.ts:142

			`outputs.${layer}.outputTarget must be arraybuffer or web-fs.`,
		);
	}

	if (
		output.outputTarget !== undefined &&
		output.outputWritable !== undefined
	) {
		throw new TypeError(
			`outputs.${layer} cannot specify both outputTarget and outputWritable.`,
		);
	}

	if (output.outputWritable !== undefined) {
		if (
			typeof WritableStream === 'undefined' ||
			!(output.outputWritable instanceof WritableStream)
		) {
			throw new TypeError(
				`outputs.${layer}.outputWritable must be a WritableStream.`,
			);
		}

		if (output.outputWritable.locked) {
			throw new TypeError(
				`outputs.${layer}.outputWritable must not already be locked.`,
			);
		}
	}
};

const validateOptions = (options: SeparateVideoLayersOptions) => {
	if (!options || typeof options !== 'object') {
		throw new TypeError('separateVideoLayers() expects an options object.');
	}

	const isBlob = typeof Blob !== 'undefined' && options.src instanceof Blob;

View on GitHub (pinned to b2f4e34732)

Solutions

  1. Pass a Web WritableStream, e.g. new WritableStream({...}) or one obtained from a TransformStream
  2. If you have a Node stream, wrap/convert it to a Web WritableStream (e.g. node:stream/web Writable.toWeb(nodeStream))
  3. Remove the outputWritable property entirely so the library uses its default output handling
  4. Ensure the runtime supports web streams (Node >= 18, modern browsers)

Example fix

// before
await separateVideoLayers({ src: 'video.mp4', outputs: { base: { outputWritable: fs.createWriteStream('base.mp4') } } });
// after
import { Writable } from 'node:stream/web';
await separateVideoLayers({ src: 'video.mp4', outputs: { base: { outputWritable: Writable.toWeb(fs.createWriteStream('base.mp4')) } } });
Defensive patterns

Strategy: type-guard

Validate before calling

function assertWritable(s) { if (s !== undefined && !(typeof WritableStream !== 'undefined' && s instanceof WritableStream)) throw new TypeError('outputWritable must be a Web WritableStream'); }

Type guard

const isWritableStream = (v) => typeof WritableStream !== 'undefined' && v instanceof WritableStream;

Try / catch

try { await separateVideoLayers(opts); } catch (e) { if (e instanceof TypeError && e.message.includes('outputWritable must be a WritableStream')) { /* fix stream construction */ } else throw e; }

Prevention

When it happens

Trigger: Passing outputs.base.outputWritable or outputs.foreground.outputWritable set to a Node stream (fs.createWriteStream), a file path string, null (non-undefined), a WritableStream-like duck-typed object, or running in an environment without WritableStream defined (old Node/runtime without web streams).

Common situations: Confusing Node.js fs write streams with Web WritableStreams in SSR/Node scripts; passing a file path instead of a stream; forgetting to import WritableStream from a polyfill in older runtimes; reusing a writable that was already handed to another consumer.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of remotion-dev/remotion@b2f4e34732 (2026-09-09). Data as JSON: /api/errors/073884a4b0493747. Report an issue: GitHub.