Polotno
Export & Import

Server-side Image Generation with Node.js

Render images from Polotno JSON on the backend using polotno-node

Is it possible to generate images from Polotno JSON on the backend?

Yes, you can leverage the polotno-node package.

Using polotno-node you can use most of the Polotno Store API. The common usage is image export from Polotno JSON:

const fs = require('fs');
// import polotno-node API
const { createInstance } = require('polotno-node');

async function run() {
  // create working instance
  const instance = await createInstance({
    // to create your own API key please go here: https://polotno.com/cabinet
    key: 'nFA5H9elEytDyPyvKL7T',
  });

  // load sample json
  const json = JSON.parse(fs.readFileSync('polotno.json'));
  // here you can manipulate JSON somehow manually
  // for example replace some images or change text

  // then we can convert json into image
  const imageBase64 = await instance.jsonToImageBase64(json); // default is PNG
  // write image into local file
  fs.writeFileSync('out.png', imageBase64, 'base64');

  // also we can export design into lower size
  // and change image type
  const jpegImage = await instance.jsonToImageBase64(json, {
    pixelRatio: 0.5, // make image twice smaller
    mimeType: 'image/jpeg',
  });
  fs.writeFileSync('out.jpg', jpegImage, 'base64');

  // enable rich text rendering if your design uses it
  const richTextImage = await instance.jsonToImageBase64(json, {
    richTextEnabled: true, // enable rich text support
  });
  fs.writeFileSync('out-rich.png', richTextImage, 'base64');

  // close instance
  instance.close();
}

run();

Instance options

createInstance() accepts a few options that control the headless browser itself:

OptionDefaultPurpose
keyYour Polotno API key.
useFontCachetrueCache Google Fonts responses for the process lifetime and serve them to every page.
navigationTimeout120000Budget in milliseconds for the initial navigation to the client page.
protocolTimeoutBudget for individual DevTools protocol calls. Distinct from navigationTimeout.
requestInterceptorIntercept every page request, for example to block or rewrite asset loads.
executablePathLaunch this browser binary instead of the one polotno-node picks. Ignored when you pass your own browser.
browserArgsExtra command-line flags, added to the flags polotno-node already passes.
const instance = await createInstance({
  key: 'nFA5H9elEytDyPyvKL7T',
  navigationTimeout: 240000, // 4 minutes, for a slow machine
});

navigationTimeout covers loading the client page, not rendering a design. Raise it if instance creation fails on a slow or heavily loaded machine. Setting it to 0 disables the limit, which turns a wedged load into a permanent hang rather than an error — prefer a large value over 0.

Use executablePath to run a browser the machine already has, such as /usr/bin/google-chrome-stable in a container image, instead of the one puppeteer downloaded.

Changed in 3.3.0

@sparticuz/chromium's flags are now applied only when polotno-node launches that package's own binary — on Linux, with no executablePath of your own. Previously they were added on macOS too. Pass them through browserArgs if you relied on that.

Restricted media hosts

Cloud-metadata addresses such as 169.254.169.254 are blocked by default; normal URLs, localhost and private hosts still work. Restrict them further when the design JSON comes from your users:

await instance.jsonToVideoFile(json, 'out.mp4', {
  blockPrivateNetwork: true, // also block loopback and private addresses
  fetchGuard: (url) => isAllowed(url), // your own rule, replaces the checks above
});

Error Handling

Rendering failures inside the headless browser — broken images, missing fonts, invalid designs — are re-thrown on the Node side as plain Errors with a code (e.g. IMAGE_FAILED, FONT_FAILED, DESIGN_INVALID) and structured details such as elementId. Branch on the code, not the message text — see Error Handling.

On this page