New:Microsoft Teams Notifications Are Now Available in Socket.Learn more →
Get Started

@cesdk/node-native

Package Overview
Dependencies
Maintainers
8
Versions
127
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@cesdk/node-native

CE.SDK Creative Engine for Node.js — native bindings

latest
Source
npmnpm
Version
1.82.1
Version published
Maintainers
8
Created
Source

Hero image showing the configuration abilities of CE.SDK

@cesdk/node-native

Native Node.js bindings for the IMG.LY Creative Engine, the core of CE.SDK. A drop-in alternative to @cesdk/node (WASM) that loads the engine as a platform-native N-API addon. The native build adds video and audio export, GPU-accelerated rendering, and removes the WASM 4 GB linear-memory ceiling.

For full Creative Engine documentation see https://img.ly/docs/cesdk/web/guides/headless/setup_node.

When to choose @cesdk/node-native

Pick @cesdk/node-native if you need any of the following; otherwise stay on @cesdk/node (WASM), which has wider platform coverage:

  • Video and audio export (MP4 / H.264 via VideoToolbox on macOS, GStreamer on Linux).
  • Working memory above ~4 GB. WASM linear memory is capped; native uses the full process address space.
  • Hardware-accelerated rendering for image export on Metal (macOS) or EGL (Linux). CPU fallback is automatic when no GPU is available.

For raw call overhead the native addon also avoids the WASM↔JS boundary, but the practical impact is workload-dependent — measure on your own scenes before relying on a specific multiplier.

Supported platforms

OSArchNodeMinimum OS
macOSarm64≥ 22macOS 12 (Monterey)
macOSx64≥ 22macOS 12 (Monterey)
Linuxx64≥ 22glibc ≥ 2.39 (Ubuntu 24.04, Debian 13, RHEL 10)
Windowsx64≥ 22Windows 10 / Server 2016 — see limitations

Windows requirements and limitations

  • Install the GStreamer MSVC x86_64 runtime (1.24 or newer) from https://gstreamer.freedesktop.org/download/ before use. The addon loads GStreamer libraries at start, so the runtime is required even for image-only work — this is the Windows analogue of the Linux apt install gstreamer1.0-* requirement below.
  • Rendering runs on the CPU only: device: 'gpu' throws and 'auto' falls back to CPU raster. Expect lower throughput than macOS (Metal) for raster-heavy exports; throughput scales with CPU cores.
  • Video import/export uses the installed GStreamer runtime (H.264 in/out, AAC, MP3). H.265 import needs a GPU with HEVC decode (DXVA); on GPU-less machines H.265 import fails with a clear error.

Not supported in this release:

  • Windows arm64
  • Linux arm64
  • musl-based distributions (Alpine, etc.)
  • AWS Lambda Node.js runtimes (AL2 / AL2023 ship glibc 2.26 / 2.34 — use @cesdk/node for serverless until a manylinux build lands)

npm install @cesdk/node-native succeeds on unsupported hosts (the main package itself has no platform filter), but the binary-carrying sibling is filtered out by npm's os / cpu / libc constraints and the first require('@cesdk/node-native') throws a clear error pointing at @cesdk/node (WASM). This is intentional so that conditional installs in Dockerfiles and CI don't fail at install time.

JavaScript runtimes: Node.js, Bun, Deno

The addon is a standard Node-API (N-API) native module, so it also runs on JavaScript runtimes that implement Node-API — no runtime-specific code paths or configuration in the package. The full CE.SDK test suite (engine lifecycle, rendering, image/video/audio export, events, asset sources) is run against all three runtimes in CI:

RuntimeVerified versionNotes
Node.js≥ 22Primary target
Bun1.3.14Both CJS require and ESM import
Deno2.9.3Needs permission flags — see below

The listed versions are the ones CI pins and runs the suite against on every change. Newer releases are expected to work; older ones are not tested and earlier Bun/Deno releases are known to have had incomplete Node-API support.

Bun (the package ships prebuilt binaries and has no install scripts, so no trustedDependencies entry is needed):

bun add @cesdk/node-native
bun my-script.js

Deno needs a local node_modules/ directory — native addons cannot be loaded from Deno's global npm cache. deno add sets this up; in an existing project add "nodeModulesDir": "auto" to deno.json. An existing Node.js project that already has package.json + node_modules/ runs under Deno as-is.

deno add npm:@cesdk/node-native
deno run --allow-ffi --allow-read --allow-env my-script.js

--allow-ffi, --allow-read, and --allow-env are the required floor (loading the addon and resolving the bundled assets). Add --allow-net when scenes reference remote assets or you pass an API-key license, and --allow-write when your script writes exported files to disk. As with any native addon, code inside the .node binary itself is not sandboxed by Deno's permission system.

Two caveats that apply to every runtime, including Node.js: the engine must run on the main thread (CreativeEngine.init() inside a worker_threads worker is not supported), and one engine instance must only ever be used from the thread that created it.

Installation

npm install @cesdk/node-native
# or, to track nightly builds:
npm install @cesdk/node-native@dev

Usage

ES Modules:

import CreativeEngine from '@cesdk/node-native';

const engine = await CreativeEngine.init({ license: 'YOUR_LICENSE_KEY' });
const scene = engine.scene.create();
const page = engine.block.create('page');
engine.block.appendChild(scene, page);
engine.block.setWidth(page, 1920);
engine.block.setHeight(page, 1080);

const png = await engine.block.export(page, 'image/png', {});
// `png` is a Blob (same as @cesdk/node). Use `png.arrayBuffer()` to read.
engine.dispose();

CommonJS:

const CreativeEngine = require('@cesdk/node-native');

CreativeEngine.init({ license: 'YOUR_LICENSE_KEY' }).then(async (engine) => {
  // ... same API as above.
  engine.dispose();
});

Always call engine.dispose() when done. The engine runs an internal update timer (driving rendering and export), so until you dispose it the Node.js event loop stays alive and the process will not exit on its own — a short-lived script or a Lambda handler that skips dispose() will appear to hang until timeout.

License & evaluation mode

The CreativeEditor SDK is a commercial product. Purchase a license at https://img.ly/pricing.

You can run the engine in evaluation mode by passing an empty string as the license key. In evaluation mode the engine prints an IMG.LY banner on stderr and watermarks output. There are three equivalent ways to enter it:

// 1. Empty string at init.
await CreativeEngine.init({ license: '' });

// 2. Omit `license` entirely at init, then unlock later.
const engine = await CreativeEngine.init();
engine.editor.unlockWithLicense('');             // evaluation
// engine.editor.unlockWithLicense('YOUR_KEY');  // upgrade in place

// 3. Inspect the active key (empty string => evaluation mode).
engine.editor.getActiveLicense();

Linux runtime dependencies

The Linux platform package statically links libc++ / libc++abi / libstdc++ into the addon, so a stock Ubuntu 24.04 / Debian 13 install needs only the engine's runtime dependencies. On ubuntu:24.04 and debian:13 the relevant packages are present by default; minimal base images (slim, distroless) need them installed explicitly.

Image / scene / archive workflows depend on the standard graphics stack:

apt-get install -y \
  libcurl4 \
  libssl3 \
  libfontconfig1 \
  libfreetype6 \
  libuuid1 \
  libxcb1 \
  libgl1 \
  libegl1

Video and audio export additionally needs GStreamer 1.x plus the common plugin sets:

apt-get install -y \
  libgstreamer1.0-0 \
  gstreamer1.0-plugins-base \
  gstreamer1.0-plugins-good \
  gstreamer1.0-plugins-bad \
  gstreamer1.0-plugins-ugly \
  gstreamer1.0-libav \
  gstreamer1.0-gl

A future release will offer a self-contained GStreamer bundle shipped inside the Linux platform package; until then, system GStreamer is required.

Licensed codec pack on Linux

Download the Linux x86_64 codec pack from your licensing dashboard. It is distributed separately from this npm package and requires a CE.SDK license that includes licensed codecs. GStreamer remains required; codec license cleanup also needs Python 3 at /usr/bin/python3.

Pass your dashboard API key as the license option to CreativeEngine.init() to reserve a codec license when processing video or audio.

Extract the pack for the user that runs Node.js. Replace YYYY-MM-DD with the version in the downloaded filename:

mkdir -p "$HOME/.local/share/cesdk-codecs"
tar -xzf "$HOME/Downloads/licensed-codec-pack-YYYY-MM-DD.tar.gz" \
  -C "$HOME/.local/share/cesdk-codecs"

The default directory must directly contain plugins/, presets/, and misc/. CE.SDK automatically detects the pack, selects the licensed codecs, and loads its plugins/ directory.

For a custom installation:

mkdir -p "$HOME/my-cesdk-codecs"
tar -xzf "$HOME/Downloads/licensed-codec-pack-YYYY-MM-DD.tar.gz" \
  -C "$HOME/my-cesdk-codecs"
export CESDK_LICENSED_CODEC_PACK="$HOME/my-cesdk-codecs"
node index.mjs

You can also set the path directly in Node.js using process.env. Replace your static CE.SDK import with a dynamic import so the assignment runs before CE.SDK loads:

process.env.CESDK_LICENSED_CODEC_PACK = `${process.env.HOME}/my-cesdk-codecs`;
const { default: CreativeEngine } = await import('@cesdk/node-native');

For CommonJS, set the variable before require('@cesdk/node-native').

The override points to the pack root containing plugins/, presets/, and misc/. A non-empty override replaces the default path; an unset or empty override uses $HOME/.local/share/cesdk-codecs. Set it before loading CE.SDK and restart the process after installing a pack or changing the path.

Supported Docker base images

The Linux platform package ships with a glibc ≥ 2.39 floor (the libc: ["glibc"] npm filter blocks Alpine/musl at install time; older-glibc systems install successfully but fail at first require('@cesdk/node-native') with version 'GLIBC_2.XX' not found).

Base imageWorks?glibcNotes
ubuntu:24.04 (and :24.04-slim)yes2.39Reference image
debian:13 (trixie, and :13-slim)yes2.41
node:22-trixieyes2.41
gcr.io/distroless/nodejs22-debian13yes2.41Recommended distroless
node:22-alpinenomuslFiltered at install
amazonlinux:2 / :2023no2.26 / 2.34Below floor; also AWS Lambda base
public.ecr.aws/lambda/nodejs:22no2.34Below floor; see Lambda note below

Use @cesdk/node (WASM) on every "no" row above.

AWS Lambda

The Linux platform package targets glibc 2.39+; AWS Lambda's Amazon Linux 2 / 2023 base images ship glibc 2.26 / 2.34, both below the floor. A Lambda function that does require('@cesdk/node-native') will fail at load with GLIBC_2.38 not found. Use @cesdk/node (WASM) on Lambda. The apps/cesdk_web_examples/cookbooks-aws-lambda/ cookbook documents the WASM-on-Lambda path.

Bundlers (webpack / esbuild / Next.js / Vite)

@cesdk/node-native ships a native .node binary that bundlers cannot inline. Mark the package external and have the bundler emit the .node + the assets/ resource bundle into the output as-is.

webpack / Next.js (server-side / API routes)

// next.config.js — server side only, never the browser bundle
module.exports = {
  webpack(config, { isServer }) {
    if (isServer) {
      config.externals = [
        ...(config.externals ?? []),
        '@cesdk/node-native',
        '@cesdk/node-native-darwin-arm64',
        '@cesdk/node-native-darwin-x64',
        '@cesdk/node-native-linux-x64',
        '@cesdk/node-native-win32-x64',
      ];
    }
    return config;
  },
  // Next.js 13+ App Router with server components:
  experimental: {
    serverComponentsExternalPackages: ['@cesdk/node-native'],
  },
};

esbuild

await esbuild.build({
  // …
  platform: 'node',
  external: [
    '@cesdk/node-native',
    '@cesdk/node-native-darwin-arm64',
    '@cesdk/node-native-darwin-x64',
    '@cesdk/node-native-linux-x64',
    '@cesdk/node-native-win32-x64',
  ],
});

Vite (server / SSR)

export default defineConfig({
  ssr: {
    external: ['@cesdk/node-native'],
    noExternal: [],
  },
});

In every bundler the engine resource bundle (@cesdk/node-native/assets/) must reach the runtime layout intact. Either keep node_modules/ next to the bundle output, or copy node_modules/@cesdk/node-native/assets/ into the deploy artifact and point CreativeEngine.init({ baseURL }) at that location explicitly.

Yarn Plug'n'Play

Yarn PnP virtualises the node_modules graph through .pnp.cjs; the default mode keeps optional dependencies inside the zip cache, which breaks dlopen against the platform sibling package's .node because the dynamic linker cannot read into a zip filesystem. The fix is to opt the platform package out of PnP zipping by declaring it unplugged:

# .yarnrc.yml
nodeLinker: pnp
pnpEnableInlining: false
// package.json
{
  "dependencies": { "@cesdk/node-native": "^1.82.1" },
  "dependenciesMeta": {
    "@cesdk/node-native-darwin-arm64": { "unplugged": true },
    "@cesdk/node-native-darwin-x64":   { "unplugged": true },
    "@cesdk/node-native-linux-x64":    { "unplugged": true },
    "@cesdk/node-native-win32-x64":    { "unplugged": true }
  }
}

The unplugged setting causes Yarn to extract the matching platform sibling into .yarn/unplugged/, where the .node resolves to a real filesystem path that dlopen can consume. The nodeLinker: node-modules mode (Yarn classic compatibility) doesn't need this — it materialises a real node_modules/ tree on disk and works out of the box.

Migration from @cesdk/node

The Block, Scene, Editor, Asset, Event, and Variable APIs are the same. For the vast majority of code paths, @cesdk/node-native is a drop-in replacement: change the import and run.

- import CreativeEngine from '@cesdk/node';
+ import CreativeEngine from '@cesdk/node-native';

Binary-returning methods (block.export, block.exportVideo, block.exportAudio, block.saveToArchive, scene.saveToArchive) return Blob on both packages. block.export() accepts both the WASM-shaped (block, { mimeType, ... }) and the positional (block, mimeType, options) signatures, so existing call sites don't need rewriting.

Default asset-source helpers (engine.asset.addDefaultAssetSources, engine.asset.addDemoAssetSources) are available with the same options (baseURL, excludeAssetSourceIds, sceneMode).

Intentional divergence

  • engine.update() is public on @cesdk/node-native (the wrapper pumps frames internally during exports; you only need it if you're driving frame-perfect work manually). On WASM it's private — the browser's animation frame drives it.

Swapping in for tooling that pins @cesdk/node

For test runs or third-party packages that import @cesdk/node by literal name (e.g. internal importers/exporters), point your bundler/test runner's module alias at @cesdk/node-native — its export shape matches @cesdk/node, so no call sites need to change:

// e.g. in a vitest/vite config
resolve: { alias: { '@cesdk/node': '@cesdk/node-native' } }

In application code, prefer importing @cesdk/node-native directly.

Video export

@cesdk/node-native is the first IMG.LY-distributed Node binding to expose video export. The two known foot-guns:

  • options.duration defaults to 0 for parity with the WASM @cesdk/node package. The encoder treats 0 as "render a single frame", which is rarely the intended outcome. Always pass an explicit duration:

    await engine.block.exportVideo(page, 'video/mp4', onProgress, {
      duration: 3.0,
      framerate: 30,
    });
    
  • Scenes loaded from an archive must wait for AV resources before exportVideo can make progress; otherwise the encoder spins on resources that haven't been fetched and the progress callback never fires. Pass waitForResources: true when loading a video scene whose assets aren't already in memory, or follow up with engine.block.forceLoadAVResource (or forceLoadResources) on each video-fill block before calling exportVideo:

    const scene = await engine.scene.loadFromArchiveURL(file_url, {
      waitForResources: true,
    });
    const [page] = engine.block.findByType('page');
    const mp4 = await engine.block.exportVideo(page, 'video/mp4', onProgress, {
      duration: engine.block.getTotalSceneDuration(scene),
      framerate: 30,
    });
    

The wrapper pumps update() internally during init(), so unlike @cesdk/node (WASM), you do not need to drive a setInterval pump loop around exportVideo. The export awaits naturally. Only call engine.update() directly if you're driving frame-perfect work outside of the built-in export path.

A runnable end-to-end example is in examples/video-export-3s.js of the source repository.

Cross-platform lockfiles (CI pitfalls)

@cesdk/node-native ships a different .node per platform via npm's optionalDependencies (the same pattern as esbuild, sharp, etc.). One rough edge to call out:

npm ci on CI using a lockfile generated on a different architecture succeeds at install but fails with ERR_DLOPEN_FAILED at runtime.

Recommended mitigations (pick one):

  • npm install --include=optional @cesdk/node-native on CI. Re-resolves optional dependencies for the actual host. Slightly slower than npm ci, robust.
  • Generate the lockfile on the same OS/arch as CI (a node:22 Docker image for Linux consumers).
  • Per-platform lockfiles. Over-engineering for most projects.

Docker images that bake @cesdk/node-native into a final artifact should always install inside the target image, never copy node_modules/ from a different-arch host.

Advanced: override the binary path

For air-gapped builds or custom distributions, set CESDK_NATIVE_BINARY_PATH to an absolute path to the .node binary. The loader uses it directly, bypassing require.resolve:

CESDK_NATIVE_BINARY_PATH=/opt/cesdk/cesdk_native.node node app.js

Set DEBUG=cesdk:native (or CESDK_DEBUG=1) to log loader resolution steps when triaging install issues.

Skipping the integrity check (cold-start-sensitive deploys)

On first init() the loader streams a SHA-256 over the ~32 MB .node and compares it to a checksum sidecar — a corruption/truncation guard (it is not anti-substitution; that's npm provenance). This adds a few tens of milliseconds to each cold start. For latency-sensitive serverless cold paths (e.g. AWS Lambda) where the artifact is already integrity-checked by the platform, you can skip it:

CESDK_SKIP_BINARY_VERIFY=1 node app.js

Leave verification on by default; only opt out when cold-start latency matters and the binary's integrity is guaranteed by other means.

Known limitations

  • IMG.LY banner and engine diagnostics on stderr. The native engine prints a one-time license banner and occasional engine logs (e.g. EventSubscriptionService::initEventCallbacks errorStateChanged) to stderr. Capture or redirect stderr (node app.js 2>/dev/null, child_process.spawn(..., { stdio: ['inherit', 'inherit', 'ignore'] })) if it interferes with machine-readable output. A setLogLevel API is tracked for a future release.
  • macOS binaries are not code-signed. Plain node doesn't enforce signatures on dlopen, so the addon loads out of the box for normal Node.js usage. Electron apps that notarize the containing app must sign the nested cesdk_native.node with their own Developer ID in their afterSign hook before notarization. Hosts that enforce strict library validation (some Electron variants, hardened runtime CI runners) will reject the unsigned binary; full Developer ID + notarization on our side is tracked as a follow-up.
  • Per-block error inspection. The engine emits a errorStateChanged log line when a block transitions to an error state (e.g. a video-fill resource fails to load). Read the per-block state via engine.block.getState(blockId) after update() to inspect the failure; a top-level error subscriber is tracked as a follow-up.

Troubleshooting

Failed to load the @cesdk/node-native addon for ...

Most commonly, npm skipped optionalDependencies during install. Reinstall:

npm install --include=optional @cesdk/node-native

For pnpm and Yarn, regenerate the lockfile on the target platform/arch (easiest via a Docker build), or use per-platform lockfiles. Neither package manager has a direct equivalent of npm's --include=optional.

Linux: version 'GLIBC_2.XX' not found

This build targets glibc ≥ 2.39 — see "Supported platforms". Stay on @cesdk/node (WASM) for older systems until a manylinux build lands.

macOS: library load disallowed by system policy

The published binary is not code-signed. npm install doesn't add the com.apple.quarantine xattr so this error doesn't show up there, but if you obtained the package via a browser download or a sideloaded zip, the quarantine flag will block dlopen. Strip it with:

xattr -d com.apple.quarantine node_modules/@cesdk/node-native-*/cesdk_native.node

License

The CreativeEditor SDK is a commercial product. Purchase a license at https://img.ly/pricing.

Keywords

cesdk

FAQs

Package last updated on 21 Sep 2026

Related posts