How to Add Custom Video Effects to Clypra: A Complete Guide to the Effect Renderer System

To add custom video effects in Clypra, you define a TypeScript interface for your parameters, extend the EffectRenderer base class to implement the drawing logic, and register the effect in the UI registry.

Clypra’s video effects subsystem is built around an extensible type system and a runtime EffectRenderer class that draws effects onto the canvas. Whether you want to implement glitch effects, color grading, or custom shaders, the architecture follows a consistent three-step pattern that integrates seamlessly with the playback pipeline.

Understanding Clypra's Effect Architecture

The effect system consists of three core layers that work together at runtime. First, the data contract layer (src/features/video-effects/types.ts) defines what parameters your effect accepts. Second, the rendering layer (src/features/video-effects/renderers/EffectRenderer.ts) handles the actual pixel manipulation using Canvas2D or WebGL contexts. Third, the integration layer (src/features/video-effects/index.ts and UI components) exposes the effect to users and wires it into the timeline.

When the playback engine renders a frame, it calls applyRendererEffect from src/features/video-effects/utils/applyRendererEffect.ts, which instantiates the appropriate renderer subclass and passes the canvas context and parameters.

Step 1 – Define the Effect Data Contract

Every custom effect starts with a TypeScript interface that describes its configurable parameters. This interface becomes part of the union type that the rest of the application uses for type safety.

Adding Your Interface to types.ts

Open src/features/video-effects/types.ts and add your effect definition to the existing union. Ensure you export the interface so renderers and UI components can import it.

// src/features/video-effects/types.ts
export interface GlitchEffect {
  type: 'glitch';
  intensity: number;   // 0-1 range
  offset: number;      // pixels to shift
}

export type VideoEffect = GlitchEffect | /* existing effects */;

The type property serves as a discriminant for the union, allowing the TypeScript compiler to narrow types correctly throughout the application.

Step 2 – Implement the Rendering Logic

The rendering implementation extends the abstract EffectRenderer base class and overrides the apply method to execute your custom drawing code.

Extending EffectRenderer

Create a new file in src/features/video-effects/renderers/ that inherits from EffectRenderer. The base class provides lifecycle management, while your subclass handles the specific visual transformation.

Here is a complete implementation of a Glitch effect renderer:

// src/features/video-effects/renderers/GlitchRenderer.ts
import { EffectRenderer } from './EffectRenderer';
import type { GlitchEffect } from '../types';

export class GlitchRenderer extends EffectRenderer {
  apply(
    ctx: CanvasRenderingContext2D,
    params: GlitchEffect,
    time: number
  ): void {
    const { intensity, offset } = params;
    const w = ctx.canvas.width;
    const h = ctx.canvas.height;

    // Simple horizontal offset glitch based on time
    const shift = Math.sin(time * 30) * offset * intensity;
    ctx.drawImage(ctx.canvas, shift, 0, w, h, 0, 0, w, h);
  }
}

The apply Method Signature

The apply method receives three arguments: the canvas rendering context (CanvasRenderingContext2D or WebGL context), the effect parameters (typed via your interface from Step 1), and the current time in seconds. You must respect this signature exactly, as the playback pipeline depends on it when dispatching effects.

Registering in applyRendererEffect.ts

After implementing the renderer, map the effect type to your class in src/features/video-effects/utils/applyRendererEffect.ts:

// src/features/video-effects/utils/applyRendererEffect.ts
import { GlitchRenderer } from '../renderers/GlitchRenderer';

export const rendererMap = {
  glitch: GlitchRenderer,
  // ... other effect mappings
};

This registry allows the runtime to instantiate the correct renderer when it encounters your effect type in the timeline.

Step 3 – Expose the Effect in the UI

Users interact with effects through the interface components located in src/features/video-effects/components/. You must register your effect in the central index and update the picker component.

Registering in the Effect Registry

Open src/features/video-effects/index.ts and add your effect to the registry, providing the renderer class and default parameters:

// src/features/video-effects/index.ts
import { GlitchRenderer } from './renderers/GlitchRenderer';

export const effectRegistry = {
  glitch: {
    name: 'Glitch',
    renderer: GlitchRenderer,
    defaultParams: { 
      type: 'glitch', 
      intensity: 0.5, 
      offset: 10 
    },
  },
  // ... existing entries
};

This registry entry ties the technical implementation to the user-facing name and initial values.

Adding UI Components

Update src/features/video-effects/components/EffectPicker.tsx to include your effect in the selection list. The component typically iterates over effectRegistry to populate the dropdown or panel:

// src/features/video-effects/components/EffectPicker.tsx
{Object.entries(effectRegistry).map(([key, def]) => (
  <option key={key} value={key}>
    {def.name}
  </option>
))}

For advanced parameter controls, create a custom panel in src/features/video-effects/components/EffectsPanel.tsx that binds input fields to the effect's parameters. The component receives the active effect's parameter object and updates it via the application's state management.

How the Runtime Pipeline Works

When a clip with your custom effect plays, Clypra executes the following sequence:

  1. Timeline Lookup: The playback engine identifies that the current clip has a video effect attached, retrieving the effect ID and parameter blob from the timeline state.
  2. Renderer Dispatch: The engine calls applyEffect from src/features/video-effects/utils/applyEffect.ts, which consults the rendererMap to instantiate the appropriate EffectRenderer subclass.
  3. Frame Rendering: Your renderer's apply method receives the canvas context, current parameters, and playback time. After executing your drawing code, the modified frame proceeds to the next effect or displays to the user.

Because EffectRenderer is a standard TypeScript class, you can utilize any Canvas2D API methods, WebGL shaders, or third-party graphics libraries, provided they are bundled with the application.

Summary

Frequently Asked Questions

What parameters does the apply method receive in Clypra's EffectRenderer?

The apply method receives three parameters: a CanvasRenderingContext2D (or WebGL context) for drawing, a params object typed to your specific effect interface, and a number representing the current playback time in seconds. This signature is strictly enforced by the base class in src/features/video-effects/renderers/EffectRenderer.ts.

Can I use WebGL instead of Canvas2D for custom video effects?

Yes. While the examples show CanvasRenderingContext2D, the EffectRenderer base class can accept a WebGL rendering context. Ensure your subclass is typed to receive WebGL2RenderingContext or WebGLRenderingContext, and implement your shaders within the apply method using standard WebGL APIs.

Where do I register a new video effect so it appears in the Clypra editor?

You must register the effect in two locations: first, add the renderer mapping to src/features/video-effects/utils/applyRendererEffect.ts so the runtime can instantiate it; second, add the metadata entry to src/features/video-effects/index.ts so the UI components in EffectPicker.tsx can display it to users.

How does Clypra handle multiple effects on the same video clip?

The playback pipeline processes effects sequentially. For each frame, applyEffect iterates through the active effects list, calling the corresponding renderer's apply method for each one. The canvas context persists between calls, allowing each effect to build upon the previous one's output, creating a composited result.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →