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');

  // close instance
  instance.close();
}

run();

Instance options

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

OptionDefaultPurpose
key:Your 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.
protocolTimeout:Budget for individual DevTools protocol calls. Distinct from navigationTimeout.
requestInterceptor:Intercept every page request, for example to block or rewrite asset loads.
executablePath:Launch this browser binary instead of the one polotno-node picks. Ignored when you pass your own browser.
headless'shell'Which headless mode to launch. Defaults to Chrome's headless shell wherever polotno-node resolves the browser itself. Pass true for full Chrome. Ignored when you pass your own browser.
browserArgs:Extra 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.

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. An asset that fails with a transient error – a reset connection, a DNS blip, a 5xx from the asset host – is retried automatically (three attempts in total, with backoff) before the render fails; there is nothing to turn on.

On this page