remotion-dev/remotion1 file

Add Effect

Add a new effect to @remotion/effects, including implementation, package exports, docs, demos, preview images, Remotion skill updates, formatting, and builds.

Specification
Skill ID
remotion-dev/remotion/add-effect
Publisher
remotion-dev
Repository
remotion
Installs
0
Files
1
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

remotion-dev/remotion/add-effectInstalls these files
  • SKILL.md

What this skill tells the agent

Add a new @remotion/effects effect

Use this skill when adding a new effect to @remotion/effects.

1. Pick the effect shape

  • Prefer the WebGL2 backend for new effects. Use 2D only when WebGL cannot express the effect.
  • Use a single file at packages/effects/src/<effect-name>.ts for simple effects.
  • Use a folder at packages/effects/src/<effect-name>/ plus a top-level re-export file when the effect needs multiple shaders, runtime helpers, or multiple files.
  • Follow naming already used by the package:
  • File/subpath: kebab-case (chromatic-aberration)
  • Function: camelCase (chromaticAberration)
  • Type: PascalCase params (ChromaticAberrationParams)
  • Effect type string: remotion/<kebab-case-name>

2. Implement the effect

In the effect file:

  • Import SequenceSchema and Internals from remotion.
  • Use const {createEffect, createWebGL2ContextError} = Internals;.
  • Define defaults as const values.
  • Define a schema with satisfies SequenceSchema; these fields appear in Studio visual editing.
  • Export the params type.
  • Resolve defaults in a resolve() helper.
  • Validate params using helpers from:
  • packages/effects/src/validate-effect-param.ts
  • packages/effects/src/color-utils.ts
  • Throw createWebGL2ContextError('<effect name> effect') if WebGL2 cannot be acquired.
  • Set documentationLink to https://www.remotion.dev/docs/effects/<slug>.
  • Include every resolved parameter in calculateKey().

For WebGL2 effects, use this general structure:

import type {SequenceSchema} from 'remotion';
import {Internals} from 'remotion';
import {assertOptionalFiniteNumber, validateUnitInterval} from './color-utils.js';
import {assertEffectParamsObject} from './validate-effect-param.js';

const {createEffect, createWebGL2ContextError} = Internals;

const DEFAULT_AMOUNT = 1 as const;

const myEffectSchema = {
	amount: {
		type: 'number',
		min: 0,
		max: 1,
		step: 0.01,
		default: DEFAULT_AMOUNT,
		description: 'Amount',
	},
} as const satisfies SequenceSchema;

export type MyEffectParams = {
	readonly amount?: number;
};

type MyEffectResolved = {
	amount: number;
};

const resolve = (p: MyEffectParams): MyEffectResolved => ({
	amount: p.amount ?? DEFAULT_AMOUNT,
});

const validateMyEffectParams = (params: MyEffectParams): void => {
	assertEffectParamsObject(params, 'My effect');
	assertOptionalFiniteNumber(params.amount, 'amount');
	validateUnitInterval(params.amount ?? DEFAULT_AMOUNT, 'amount');
};

type MyEffectState = {
	readonly gl: WebGL2RenderingContext;
	readonly program: WebGLProgram;
	readonly vao: WebGLVertexArrayObject;
	readonly vbo: WebGLBuffer;
	readonly texture: WebGLTexture;
	readonly uSource: WebGLUniformLocation | null;
	readonly uAmount: WebGLUniformLocation | null;
};

const VERTEX_SHADER = /* glsl */ `#version 300 es
in vec2 aPos;
in vec2 aUv;
out vec2 vUv;

void main() {
	vUv = aUv;
	gl_Position = vec4(aPos, 0.0, 1.0);
}
`;

const FRAGMENT_SHADER = /* glsl */ `#version 300 es
precision highp float;

in vec2 vUv;
out vec4 fragColor;

uniform sampler2D uSource;
uniform float uAmount;

void main() {
	vec4 color = texture(uSource, vUv);
	fragColor = vec4(color.rgb * uAmount, color.a);
}
`;

// Follow existing helpers in halftone.ts or a runtime file for shader
// compilation, program linking, fullscreen-quad setup, and texture setup.

export const myEffect = createEffect<MyEffectParams, MyEffectState>({
	type: 'remotion/my-effect',
	label: 'My Effect',
	documentationLink: 'https://www.remotion.dev/docs/effects/my-effect',
	backend: 'webgl2',
	calculateKey: (params) => {
		const r = resolve(params);
		return `my-effect-${r.amount}`;
	},
	setup: (target) => {
		const gl = target.getContext('webgl2', {
			premultipliedAlpha: true,
			alpha: true,
			preserveDrawingBuffer: true,
		});
		if (!gl) {
			throw createWebGL2ContextError('my effect effect');
		}

		gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);

		return createMyEffectState(gl, VERTEX_SHADER, FRAGMENT_SHADER);
	},
	apply: ({source, width, height, params, state, flipSourceY}) => {
		const r = resolve(params);

		state.gl.viewport(0, 0, width, height);
		state.gl.bindFramebuffer(state.gl.FRAMEBUFFER, null);
		state.gl.activeTexture(state.gl.TEXTURE0);
		state.gl.bindTexture(state.gl.TEXTURE_2D, state.texture);
		state.gl.pixelStorei(state.gl.UNPACK_FLIP_Y_WEBGL, flipSourceY);
		state.gl.texImage2D(
			state.gl.TEXTURE_2D,
			0,
			state.gl.RGBA,
			state.gl.RGBA,
			state.gl.UNSIGNED_BYTE,
			source as TexImageSource,
		);

		state.gl.useProgram(state.program);
		if (state.uSource) state.gl.uniform1i(state.uSource, 0);
		if (state.uAmount) state.gl.uniform1f(state.uAmount, r.amount);
		state.gl.bindVertexArray(state.vao);
		state.gl.drawArrays(state.gl.TRIANGLE_STRIP, 0, 4);
	},
	cleanup: ({gl, program, vao, vbo, texture}) => {
		gl.deleteTexture(texture);
		gl.deleteBuffer(vbo);
		gl.deleteProgram(program);
		gl.deleteVertexArray(vao);
	},
	schema: myEffectSchema,
	validateParams: validateMyEffectParams,
});

Look at existing WebGL2 effects such as halftone.ts, blur/blur-runtime.ts, chromatic-aberration/chromatic-aberration-runtime.ts, and wave/wave-runtime.ts before adding new helpers. In the template above, createMyEffectState() stands for the shader compilation, program linking, fullscreen-quad, texture, and uniform-location setup used by those files.

3. Register package entry points

Update:

  • packages/effects/bundle.ts — add the new src/<effect-name>.ts entrypoint.
  • packages/effects/package.json:
  • Add exports["./<effect-name>"].
  • Add the typesVersions entry.

If using a folder implementation, add a top-level file that re-exports from the folder:

export {myEffect, type MyEffectParams} from './my-effect/index.js';

4. Build and verify

Run the existing tests and build:

cd packages/effects
bun test src/test
bunx turbo make --filter="@remotion/effects"

5. Add docs

Create packages/docs/docs/effects/<effect-name>.mdx.

Follow existing effect pages:

  • Frontmatter: slug, title, sidebar_label, crumb: '@remotion/effects'.
  • Add image: only after running bun render-cards.ts.
  • H1: # effectName()<AvailableFrom v="..." />.
  • Include _Part of the [@remotion/effects](/docs/effects/api) package._.
  • Add a short description.
  • Add <EffectsDemo type="effects-<effect-name>" />.
  • Add a twoslash example with title="MyComp.tsx".
  • Document each option as its own ### heading, using ? for optional parameters.
  • Add a disabled? section.
  • Add a See also section.

Update:

  • packages/docs/sidebars.ts — add 'effects/<effect-name>' in alphabetical order.
  • packages/docs/docs/effects/table-of-contents.tsx — add a card in the right category.
  • packages/docs/src/data/articles.ts by running the card generator, not by hand.

Use the writing-docs skill for documentation wording.

6. Add the interactive docs demo

Create packages/docs/components/effects/effects-<effect-name>-preview.tsx.

Use the same preview source as other effects:

import {myEffect} from '@remotion/effects/my-effect';
import React from 'react';
import {CanvasImage} from 'remotion';
import {EFFECTS_PREVIEW_IMAGE_SRC} from './effects-preview-image';

export const EffectsMyEffectPreview: React.FC<{
	readonly amount: number;
}> = ({amount}) => {
	return (
		<CanvasImage
			src={EFFECTS_PREVIEW_IMAGE_SRC}
			width={1280}
			height={720}
			fit="cover"
			effects={[myEffect({amount})]}
		/>
	);
};

Use fit="cover" for docs effect previews so the shared preview image fills the 16:9 canvas and does not leave transparent bars.

Register the demo in packages/docs/components/effects-demos/registry.ts:

  • Import the preview component and the real effect schema (or read it from effect().definition.schema).
  • Add an entry with id: 'effects-<effect-name>'.
  • Add initialValues only for required fields whose schema default is undefined.

Use this skill in a project

Open any RiverX project, search the Skills panel for remotion-dev/remotion/add-effect, and install it in one click.

Use this skill