← All articles

Make WebP stickers locally, with ordered effects and automatic rendering

The new @open-wa/stickers package uses an existing browser when it can complete the job and a portable standalone worker otherwise, without Sharp.

The new @open-wa/stickers package creates WebP output on your machine. It doesn't need a remote sticker-rendering API, a Sharp native installation, or a browser download during installation. The first release contains 62 executable recipes shared between its browser and standalone backends.

Stack effects in the order you want

import { writeFile } from 'node:fs/promises';
import { createSticker } from '@open-wa/stickers';

const sticker = await createSticker('./photo.jpg', {
  effects: [
    'comic',
    { name: 'tint', options: { color: '#e056ae' } },
    'sparkle',
  ],
});
await writeFile('./sticker.webp', sticker.bytes);

Jobs accept up to eight ordered effects. That order matters: freeze keeps the first frame at its position in the stack, while an animated effect after it can introduce new motion. Use listEffects({ availableOnly: true }) to inspect executable recipes. The larger historical catalogue is an inventory, and unported entries fail explicitly; it isn't a claim of Jeyy parity.

Let the package choose the backend

createSticker(input, job) defaults to automatic routing. Before decoding, the planner checks the input codec and every effect against available browser capabilities. If the browser can complete the entire job, it uses that browser. Otherwise the entire job runs standalone. Unsupported complete jobs fail with CAPABILITY_UNAVAILABLE, without forwarding intermediate frames between backends or calling a rendering server.

Outside a browser, standalone is the default. In an application with an existing page or driver, pass its browser backend to createStickerRuntime; the SDK supplies its existing context for sticker requests. Explicit backend overrides are strict.

The browser uses its Canvas and codecs. Standalone lazily loads CanvasKit and libwebp. Shared Rust SIMD WASM implements selected numerical effects; other recipes currently use JavaScript and their context's compositor. This isn't a full Rust rewrite of every effect.

Use the same job from the CLI

npx --package @open-wa/stickers@5.5.1 openwa-sticker photo.jpg \
  --effects comic,sparkle --output sticker.webp

Use --job sticker-job.json for the library's full job fields, effects to list recipes, and effects --all for the inventory. --offline requires previously cached resources. Downloads are pinned by version, length and SHA-256 and verified before execution.

PNG, JPEG, GIF and WebP are implemented in both contexts, including animated input. AVIF is browser-dependent; standalone AVIF and MP4 aren't implemented. trim uses source milliseconds, fps samples without speeding the animation, slow changes frame durations, and quality is a maximum that may be reduced to meet the byte budget. Text recipes load portable fonts only when needed.

Installation and runtime costs

The public source package and kernels are about 247 KB unpacked. Standalone downloads approximately 7.44 MB for CanvasKit and 384 KB for libwebp; individual fonts add 46–212 KB when selected. Those are resource byte sizes, not measured peak RAM. A single 512×512 RGBA surface uses 1 MiB, and effects can allocate several surfaces plus their WASM heaps and runtime overhead.

Each backend serializes jobs, admits at most four queued requests and 64 MiB of queued input, and drops owned resources after 60 seconds idle. The CLI disposes its worker when it exits. Peak RAM hasn't been measured for this implementation.

Output is bounded to 512×512, 120 frames and ten seconds, with 100,000-byte static and 512,000-byte animated budgets. Rendering reserves metadata space and can reject output that doesn't fit.

Rendering and sending have separate responsibilities

Rendering produces pixels and optional author/pack labels. SDK sending requires the private sticker finalizer, which adds the donor app links before WhatsApp's hashing, encryption and upload. That private rollout is separate from publishing the renderer package. sendRawMessage rejects sticker payloads, and native forwarding awaits its local finalization implementation.

WASM and patches can be inspected or modified by a machine's owner. Donor protection governs the supported send path; it cannot make arbitrary modified clients tamper-proof. Background removal, remaining historical effects, scene simulations and statistics reporting remain separate work.

Read the package guide for the full API, cache controls, strict overrides and resource limits.