Render static images and frames server-side with JSX and CSS, no browsers involved. Compose videos in declarative JSON. Tie it all together over HTTP.
MIT-licensed and runs on nothing but Node and FFmpeg. Nice and simple, made for scale, and no vendor lock-in.
You build a video by combining simple pieces: images for static visuals, annies for animations, and an effie that says how they all fit together. FFS, our FFmpeg-based rendering service, is what turns effies into MP4s. That's all there is to it, really — but it's surprisingly powerful.
Effing Canvas is a server-side canvas that turns JSX and CSS into pixels — no DOM involved. It lets you write the same flexbox model and CSS properties you use on the web. Yoga handles layout, Skia handles rendering. This makes rendering images and annie frames easy.
const width = 1080, height = 1920 const canvas = createCanvas(width, height) const ctx = canvas.getContext("2d") await renderReactElement(ctx, <div style={{ width, height, display: "flex", alignItems: "center", justifyContent: "center", backgroundColor: "#151716", fontSize: 80, color: "#4CAE4C", }}>My Video</div>, ) const png = await canvas.encode("png")
Powered by Yoga. The same model you use in CSS and React Native.
Fonts, gradients, borders, shadows, transforms, opacity — styled the way you expect.
Automatic emoji rendering from CDN sources — Twemoji, Noto, Fluent, and more.
Not just JSX. You can also render After Effects animations frame by frame, via Skottie, on the same canvas.
Write your images, annies, and effies as @effing/fn modules — they pair a function with a props schema and preview defaults. Point to them in effing.config.ts and run effing dev to boot a preview app, or effing build to bundle a production server. That's it!
import { z } from "zod" import type { RunnerArgs, ImageRunnerReturn } from "@effing/fn" import { createCanvas, renderReactElement } from "@effing/canvas" export const propsSchema = z.object({ title: z.string(), accent: z.string().default("#4CAE4C"), }) type HelloProps = z.infer<typeof propsSchema> export const previewProps: HelloProps = { title: "Hello, world" } export async function runner({ props: { title, accent }, bounds: { width, height }, }: RunnerArgs<HelloProps>): ImageRunnerReturn { const canvas = createCanvas(width, height) await renderReactElement(canvas.getContext("2d"), <div style={{ width, height, display: "flex", alignItems: "center", justifyContent: "center", backgroundColor: "#151716", fontSize: 96, color: accent, }}>{title}</div>, ) return canvas.encode("png") }
An overview page lists every image, annie, and effie. Per-module pages have a resolution picker and auto-reload on file save.
Build a self-contained dist/server.js that exposes every fn as a signed URL. Run it with just node.
HTML preview pages let humans click through every fn. Raw .bytes, .tar, and .json endpoints alongside let agents fetch artifacts directly.
Run effing manual for a comprehensive dev guide. Put that in your AGENTS.md so your agents know exactly how it's done.
Twelve focused TypeScript packages. Use the whole pipeline, or pull in just the pieces you need.
Scaffold a project, preview in the browser, render to MP4.
npm create @effing my-effing-app