| name | add-effect |
| description | Add a new effect to @remotion/effects, including implementation, package exports, docs, demos, preview images, Remotion skill updates, tests, formatting, and builds. |
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: ): ({
: p. ?? ,
});
validateMyEffectParams = (: ): {
(params, );
(params., );
(params. ?? , );
};
= {
: ;
: ;
: ;
: ;
: ;
: | ;
: | ;
};
= ;
= ;
myEffect = createEffect<, >({
: ,
: ,
: ,
: ,
: {
r = (params);
;
},
: {
gl = target.(, {
: ,
: ,
: ,
});
(!gl) {
();
}
gl.(gl., );
(gl, , );
},
: {
r = (params);
state..(, , width, height);
state..(state.., );
state..(state..);
state..(state.., state.);
state..(state.., flipSourceY);
state..(
state..,
,
state..,
state..,
state..,
source ,
);
state..(state.);
(state.) state..(state., );
(state.) state..(state., r.);
state..(state.);
state..(state.., , );
},
: {
gl.(texture);
gl.(vbo);
gl.(program);
gl.(vao);
},
: myEffectSchema,
: 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. Add tests
Update packages/effects/src/test/effect-params.test.ts:
- Import the new effect.
- Add it to the documentation link test.
- Test default params when all fields are optional.
- Test required params if any are required.
- Test invalid values and exact error substrings.
- Test that meaningful params produce distinct
effectKey values.
Run:
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 the docs-demo skill for demo details.
7. Add and render the table-of-contents preview composition
The TOC card must come from a real Remotion composition in packages/docs, not a hand-written asset.
Always render preview assets as PNG files.
Add a Still to packages/docs/src/remotion/Root.tsx under the effect-previews folder:
<Still
id="effects-my-effect-preview"
component={EffectsMyEffectPreview}
width={1280}
height={720}
defaultProps={{
amount: 1,
}}
/>
Use the same width and height as the preview component's CanvasImage.
If the preview component uses the shared docs preview image, keep
fit="cover" on CanvasImage. Rendering a 16:9 preview component into a
different aspect ratio can leave black bars in the generated TOC image.
Then render from packages/docs:
bunx remotion still src/remotion/entry.ts effects-my-effect-preview static/img/effects-my-effect-preview.png --overwrite --image-format=png
Commit both:
- The composition entry in
packages/docs/src/remotion/Root.tsx
- The rendered image in
packages/docs/static/img/
8. Generate docs card
Run:
cd packages/docs
bun render-cards.ts
Commit the generated packages/docs/static/generated/articles-docs-effects-<effect-name>.png and the new image: frontmatter line.
If render-cards.ts opportunistically generates unrelated missing cards, remove those unrelated files unless they belong to the current change.
9. Update the Remotion skill
Keep the agent-facing Remotion skill in sync with the new effect.
Update packages/skills/skills/remotion-markup/effects.md only if the new effect changes general usage mechanics, import conventions, installation guidance, or custom-effect recommendations. Do not duplicate the full effect list there; use the docs table of contents as the canonical list.
10. Format, build, and verify
Run:
cd packages/effects
bunx oxfmt src --write
cd ../..
bun run build
bun run formatting
If the change touches docs source, bun run formatting covers packages/docs/src. For MDX-only edits, do not run formatters on docs pages.
Before committing, check:
git diff --check
git status --short
Common pitfalls
- Do not forget
package.json exports and typesVersions; subpath imports like @remotion/effects/my-effect depend on them.
- Do not forget
bundle.ts; otherwise the ESM subpath will not be built.
- Do not leave temporary render entry points in
packages/docs/src/remotion.
- Do not use a hand-written SVG for the effect TOC preview.
- Preserve alpha unless the effect intentionally changes it.
- For pixel math, be aware canvases store premultiplied alpha.
- WebGL color math often needs to unpremultiply the sampled RGB before luminance
or threshold calculations, then premultiply the output RGB again.