Export Polotno JSON into images and pdf files. NodeJS package to work with Polotno SDK.
🚀 Optimize Your Workflow with Cloud Render API!
Instead of managing your own server infrastructure with
polotno-node
, consider using our Cloud Render API. It provides all the powerful export capabilities of Polotno with none of the server maintenance. Seamlessly convert your designs into images, PDFs, and videos at scale, with the reliability and speed of cloud-based rendering.Get started now and focus on what truly matters—creating stunning designs!
npm install polotno-node
const fs = require('fs');
const { createInstance } = require('polotno-node');
async function run() {
// create working instance
const instance = await createInstance({
// this is a demo key just for that project
// (!) please don't use it in your projects
// to create your own API key please go here: https://polotno.dev/cabinet
key: 'nFA5H9elEytDyPyvKL7T',
});
// load sample json
const json = JSON.parse(fs.readFileSync('polotno.json'));
const imageBase64 = await instance.jsonToImageBase64(json);
fs.writeFileSync('out.png', imageBase64, 'base64');
// close instance
instance.close();
}
run();
Create working instance of Polotno Node.
const { createInstance } = require('polotno-node');
const instance = await createInstance({
// this is a demo key just for that project
// (!) please don't use it in your projects
// to create your own API key please go here: https://polotno.dev/cabinet
key: 'nFA5H9elEytDyPyvKL7T',
// useParallelPages - use parallel pages to speed up rendering
// you can use false only for sequential calls
// it may break rendering if you call many parallel requests
// default is true
useParallelPages: false,
// url - url of the Polotno Client Editor
// client editor is just simple public html page that have `store` as global variable
// by default it will run local build
url: 'https://yourappdomain.com/client',
// browser - puppeteer browser instance
// by default it will use chrome-aws-lambda
// useful to set your own rendering props or use browserless
browser: browser,
});
Export json into data URL.
const json = JSON.parse(fs.readFileSync('polotno.json'));
// by default it will export first page only
const url = await instance.jsonToDataURL(json);
res.json({ url });
// export many pages:
for (const page of json.pages) {
const url = await instance.jsonToDataURL(
{ ...json, pages: [page] }, // for optimization, we can modify JSON to include only one page
{ pageId: page.id }
);
// do something with url
}
Export json into base64 string of image.
const json = JSON.parse(fs.readFileSync('polotno.json'));
// by default it will export first page only
const imageBase64 = await instance.jsonToImageBase64(json, {
mimeType: 'image/png',
}); // also 'image/jpeg' is supported
fs.writeFileSync('out.png', imageBase64, 'base64');
// export many pages:
for (const page of json.pages) {
const imageBase64 = await instance.jsonToImageBase64(
{ ...json, pages: [page] }, // for optimization, we can modify JSON to include only one page
{ pageId: page.id }
);
// do something with base64
}
Export json into base64 string of pdf file.
const json = JSON.parse(fs.readFileSync('polotno.json'));
// it will export all pages in the JSON
const pdfBase64 = await instance.jsonToPDFBase64(json);
fs.writeFileSync('out.pdf', pdfBase64, 'base64');
Export json into data url of pdf file.
const json = JSON.parse(fs.readFileSync('polotno.json'));
const url = await instance.jsonToPDFDataURL(json);
res.json({ url });
Export json into data url of GIF file with animations
const json = JSON.parse(fs.readFileSync('polotno.json'));
const url = await instance.jsonToGIFDataURL(json);
res.json({ url });
Export json into data url of GIF file with animations
const json = JSON.parse(fs.readFileSync('polotno.json'));
const base64 = await instance.jsonToGIFBase64(json);
fs.writeFileSync('out.gif', base64, 'base64');
NOTE: all export API will pass attrs
object into relevant export function from store
.
const url = await instance.jsonToDataURL(json, { pixelRatio: 0.2 });
// under the hood it will call:
// const url = await store.toDataURL({ pixelRatio: 0.2 });
You can add assetLoadTimeout
attribute to attrs
object. It will be used to set timeout for loading assets. By default it is 30000ms.
const url = await instance.jsonToPDFDataURL(json, { assetLoadTimeout: 60000 });
Timeout for loading fonts. By default it is 6000ms.
const url = await instance.jsonToPDFDataURL(json, { assetFontTimeout: 10000 });
Enabled experimental HTML text rendering. By default it is false
.
const url = await instance.jsonToPDFDataURL(json, {
htmlTextRenderEnabled: true,
});
Enabled vertical text resize and align. By default it is false
.
const url = await instance.jsonToPDFDataURL(json, {
textVerticalResizeEnabled: true,
});
If skipFontError
is true, it will not throw error font is not loaded or not defined. By default it is false
, so it will throw error.
const url = await instance.jsonToPDFDataURL(json, {
skipFontError: true,
});
If skipImageError
is true, it will not throw error an can't be loaded. By default it is false
, so it will throw error.
const url = await instance.jsonToPDFDataURL(json, {
skipImageError: true,
});
Control behavior of text on its overflow. Default is change-font-size
. It means it will automatically reduce font size to fit text into the box. Other options are:
resize
(change text element height to make text fit)ellipsis
(add ellipsis to the end of the text)
const url = await instance.jsonToPDFDataURL(json, {
textOverflow: 'resize',
});
Additinal options to overflow behaviour. Default is false
. It means the render will make sure no words are rendered into several lines. If you set it to true
, the render will split words into several lines if needed without reducing font size.
const url = await instance.jsonToPDFDataURL(json, {
textSplitAllowed: true,
});
Run any Polotno store API directly inside web-page context.
Warning: by default every run
and every export function will create a new page with its own editor and context. If you want to make and export after you use instance.run()
you must do it inside the same run
function.
// we can't directly use "json" variable inside the run function
// we MUST pass it as the second argument
const url = await instance.run(async (json) => {
// you can use global "config" object that has some functions from "polotno/config" module
window.config.addGlobalFont({
name: 'MyCustomFont',
url: 'https://example.com/font.otf',
});
// you can use global "store" object
store.loadJSON(json);
await store.waitLoading();
return store.toDataURL();
}, json);
window.config
is a global object that has some functions from polotno/config
module. You can use it to add custom fonts and customize some settings.
Not all options are supported yet. If you see anything missing, please create an issue. You can see all available options in client.js
file.
You should be able to change config before you call store.loadJSON
function and do you export.
const url = await instance.run(async (json) => {
// you can use global "config" object that has some functions from "polotno/config" module
window.config.unstable_setTextVerticalResizeEnabled(true);
// you can use global "store" object
store.loadJSON(json);
return store.toDataURL();
}, json);
Video export is deprecated on this package and not supported anymore.
You may need to have ffmpeg available in your system to make it work. Also you need to manually dependencies:
npm install fluent-ffmpeg axios
import { createInstance } from 'polotno-node';
import { jsonToVideo } from 'polotno-node/video-parallel';
// load sample json
const json = JSON.parse(fs.readFileSync('./test-data/video.json'));
await jsonToVideo(
// first argument is a function that returns an instance
// it can be a new instance on every call (useful for parallel rendering on local machine)
// or it can be a single instance (useful for cloud functions)
() =>
createInstance({
key: '...key...',
}),
json,
{
out: 'out.mp4', // output file name
parallel: 4, // number of parallel rendering processes
fps: 15, // frames per second
keepInstance: false, // keep instance open after rendering, make sure to use true if use just one instance
}
);
By default polotno-node
ships with the default Polotno Editor with its (hopefully) last version. If you use experimental API such as unstable_registerShapeModel
and unstable_registerShapeComponent
, the rendering may fail if you use unknown elements types.
In that case you can use your own client editor. You need to create a public html page with store
as global variable and mount just <Workspace />
component from polotno/canvas
module. Take a look into client.html
file and client.js
file in this repo as a demo. In your own version of the Editor you can use experimental API to define custom components.
Pass url
option to createInstance
function with public url of your client editor.
**Note: you will have to maintain the last version of your client editor by yourself. Better to keep using the last **
const { createInstance } = require('polotno-node');
const instance = await createInstance({
key: 'KEY',
url: 'https://yourappdomain.com/client',
});
polotno-node
should work by default on AWS Lambda. But in some cloud providers you may need to do extra steps to reduce function size.
EC2 has some troubles with loading fonts. To fix the issue install Google Chrome, it will load all required libraries.
curl https://intoli.com/install-google-chrome.sh | bash
Got it from here: puppeteer/puppeteer#765 (comment)
You can speed up your function execution a lot, if instead of using full browser you will use browserless.io service. It is a paid service not affiliated with Polotno.
Using browserless.io you can also make your function much smaller in size, so it will be possible to deploy to cloud provider with smaller limits, like Vercel.
const { createInstance } = require('polotno-node/instance');
const puppeteer = require('puppeteer');
const instance = await createInstance({
key: 'nFA5H9elEytDyPyvKL7T',
browser: await puppeteer.connect({
browserWSEndpoint: 'wss://chrome.browserless.io?token=API_KEY',
}),
url: 'https://yourappdomain.com/client', // see "Your own client" section
});
Also you can use @sparticuz/chromium-min to reduce function size. Make sure it is caching chromium binary in your cloud provider. Looks like Vercel is NOT doing that!
npm install @sparticuz/chromium-min
const { createInstance } = require('polotno-node/instance');
const chromium = require('@sparticuz/chromium-min');
const puppeteer = require('puppeteer-core');
const makeInstance = async () => {
const browser = await puppeteer.launch({
args: [
...chromium.args,
'--no-sandbox',
'--hide-scrollbars',
'--disable-web-security',
'--allow-file-access-from-files',
// more info about --disable-dev-shm-usage
// https://github.com/puppeteer/puppeteer/issues/1175#issuecomment-369728215
// https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#tips
'--disable-dev-shm-usage',
],
defaultViewport: chromium.defaultViewport,
executablePath: await chromium.executablePath(
'https://github.com/Sparticuz/chromium/releases/download/v110.0.1/chromium-v110.0.1-pack.tar'
),
headless: chromium.headless,
ignoreHTTPSErrors: true,
});
return await createInstance({
key: 'your-key',
browser,
});
};
const instance = await makeInstance();
In order to run the polotno-node in AWS Lambda, it's recommended to use a Lambda Layer configurations which will manage the dependencies, like chronium. In addition, the Lambda's configuration needs to be changed as well. It's recommeded to change the timeout to 30 seconds instead of default value (3 seconds) and Memory limit increase to minimum 512 Mb instead of 128 mb ( default value ).
Dependencies:
- @sparticuz/chromium
- puppeteer-core
- polotno-node
Pre-requirements:
- The chronium and puppeteer versions need to be satisfied. Please check this document.
- The minumum Memory limit needs to be
512 Mb
for AWS Lambda. Default is128 Mb
. - The timeout should be increased from default 3 seconds to higher value, for example up to 30 seconds. It depends on the Memory limit. For instance, if the memory limit was set to 1024, the polotno process might take ~15 secs, if 512 mb, ~30sec.
==========================================
How to create a configuration layer with a dependency on chronium
?
- Create a
.zip
file from a chronium project. For example:
mkdir chronium-112 && cd chronium-112
npm init -y
npm install @sparticuz/chromium@112.0
zip -r chronium.zip ./*
- Go to AWS console then open a Lambda section and click on
Layers
. - Following the documentation create a Layer with a chrnioum dependency by uploading a zip file. Keep in mind that environment like
nodejs18.x
should match between layer and function.
Approximatelly, the size of the zip, will be around
73 Mb
, so in order to upload, the S3 service will be needed. 4. Finally, open the Lambda function, select aCode
section, at the bottom click onAdd Layer
and select a created layer.
==========================================
How to run the full example with aws-cli ?
- Create a Lambda configuration layer, and upload a .zip archive which stores a chronium npm or git package.
export CH_VERSION=112.0
export BUCKET=example-bucket
export AWS_ACC_ID=000000000000
// create a chronium project for Layer
mkdir chronium-layer
cd chronium-layer
npm init -y
npm install @sparticuz/chromium@${CH_VERSION}
zip -r chronium.zip ./*
aws s3 cp chronium.zip s3://${BUCKET}/lambda-layers/chronium-${CH_VERSION}.zip
aws lambda publish-layer-version \
--layer-name chronium-polotno \
--content "S3Bucket=${BUCKET},S3Key=lambda-layers/chronium-{CH_VERSION}.zip" \
--description Chronium-dev-${CH_VERSION} \
--compatible-architectures x86_64 \
--compatible-runtimes nodejs18.x
- Create a JS file for a Lambda's handler
// create a folder
mkdir handler && cd handler
// init a node project
npm init -y
// install dependencies
npm install --save polotno-node puppeteer-core@19.8.0
// create a file handler
touch index.mjs
// add code example
echo "
import chromium from "@sparticuz/chromium";
import puppeteer from "puppeteer-core";
import { createInstance } from "polotno-node";
export const handler = () => {
const browser = await puppeteer.launch({
args: [
...chromium.args,
"--no-sandbox",
"--hide-scrollbars",
"--disable-web-security",
"--allow-file-access-from-files",
"--disable-dev-shm-usage",
],
defaultViewport: chromium.defaultViewport,
executablePath: await chromium.executablePath(),
headless: true,
ignoreHTTPSErrors: true,
});
const polotnoInstance = await createInstance({
key: process.env.POLOTNO_API_KEY,
browser,
});
const body = polotnoInstance.jsonToImageBase64(event.json)
return {
StatusCode: 200,
headers: {
'Content-Type': 'image/png'
},
body,
}
};
" >> index.mjs
// create file with variables
echo '{
"Variables": {
"POLOTNO_API_KEY": "your-api-key"
}
}
' >> vars.json
// archive the folder
zip -r index.zip ./*
// copy to S3
aws s3 cp index.zip s3://${BUCKET}/polotno-handler.zip
// crete a trust policy
echo '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
' >> trustPolicy.json
// create roles and policies
aws iam create-role --role-name lambda-polotno-role --assume-role-policy-document file://trustPolicy.json
aws iam attach-role-policy --role-name lambda-polotno-role --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
aws lambda create-function --function-name lambda-polotno \
--runtime nodejs18.x \
--zip-file fileb://index.zip \
--layers "arn:aws:lambda:eu-central-1:${AWS_ACC_ID}:layer:chronium-polotno:1" \
--handler index.handler \
--environment file://vars.json \
--role "arn:aws:iam::${AWS_ACC_ID}:role/lambda-polotno-role" \
--timeout 60 \
--memory-size 512
// try it
aws lambda invoke --function-name lambda-polotno --cli-binary-format raw-in-base64-out --payload file://polotno-project-json.json image
If you have an error like this
Unhandled Promise Rejection {"errorType":"Runtime.UnhandledPromiseRejection","errorMessage":"Error: Evaluation failed: ReferenceError: store is not defined\n at **puppeteer_evaluation_script**:3:9"
It may mean that Polotno Client Editor was not loaded in puppeteer
instance. It is possible that you are missing required files in node_modules
folder. I got this error when I was trying to run polotno-node
on Vercel. To fix the issue you need to add this config into vercel.json
:
"functions": {
"api/render.js": { // remember to replace this line with your function name
"includeFiles": "node_modules/polotno-node/\*\*"
},
}