Polotno Docs
API Reference

Element

Elements represent graphical objects in a page or group

Element represents a graphical object on the page or inside a group. An element can have one of these types:

  • text
  • image
  • svg
  • line
  • figure
  • video
  • table
  • group
const element = store.activePage.addElement({
  type: 'text',
  x: 50,
  y: 50,
  fill: 'black',
  text: 'hello',
});

// logs 50
console.log(element.x);

// set new position
element.set({ x: 100 });

Basic actions

Read properties

At any time, you can access any property of an element. See documentation for every element type to view all available properties.

const element = store.selectedElements[0];
// logs type of element
console.log(element.type);
// logs id of element
console.log(element.id);

element.set(attrs)

Set new attributes to the element.

text.set({ x: text.x + 10, text: 'new text' });

Custom properties

You can't write any arbitrary property directly on an element. If you want to store additional data, use the custom attribute.

// 🛑 this line will not work, because elements have no attribute `userId`
element.set({ userId: '12' });
// 🟢 you can write such data into "custom" attribute
element.set({ custom: { userId: '12' } });
// read custom attribute
console.log(element.custom?.userId);

element.moveUp()

Move element up on z-index.

text.moveUp();

element.moveDown()

Move element down on z-index.

text.moveDown();

element.moveTop()

Move element to the top of the page or a group.

text.moveTop();

element.moveBottom()

Move element to the bottom of the page or a group.

text.moveBottom();

Locking

Use draggable, contentEditable, styleEditable, removable and resizable attributes to lock element editing.

// lock the object
element.set({
  // can element be moved and rotated
  draggable: false,
  // can we change content of element?
  contentEditable: false,
  // can we change style of element?
  styleEditable: false,
  // can we resize element?
  resizable: false,
  // can we remove an element?
  removable: false,
});

console.log(element.locked); // true

// unlock it
element.set({
  // can element be moved and rotated
  draggable: true,
  // can we change content of element?
  contentEditable: true,
  // can we change style of element?
  styleEditable: true,
  // can we resize element?
  resizable: true,
  removable: true,
});

See Element Locking for comprehensive documentation on locking properties, use cases, and custom UI examples.

Text element

Text elements allow you to create text on the canvas.

Here is the example of default properties:

page.addElement({
  type: 'text',
  x: 0,
  y: 0,
  rotation: 0,
  locked: false, // deprecated
  blurEnabled: false,
  blurRadius: 10,
  brightnessEnabled: false,
  brightness: 0,
  shadowEnabled: false,
  shadowBlur: 5,
  shadowOffsetX: 0,
  shadowOffsetY: 0,
  shadowColor: 'black',
  shadowOpacity: 1,
  name: '', // name of element, can be used to find element in the store
  text: 'Hey, polotno',
  // placeholder is working similar to input placeholder
  // it will be rendered if no text is defined
  // and we will use it in input element too
  // useful for template canvas, where users will need to replace text elements
  // important (!) placeholders are removed from export result
  placeholder: '',
  fontSize: 14,
  fontFamily: 'Roboto',
  fontStyle: 'normal', // can be normal or italic
  fontWeight: 'normal', // can be normal or bold or some other CSS variations
  textDecoration: '',
  fill: 'black',
  align: 'center',
  width: 0,
  strokeWidth: 0,
  stroke: 'black',
  lineHeight: 1,
  letterSpacing: 0, // % from font size,
  backgroundEnabled: false,
  backgroundColor: '#7ED321',
  backgroundOpacity: 1,
  backgroundCornerRadius: 0.5, // % from half line height
  backgroundPadding: 0.5, //% from half line height

  // can user select element?
  // if false, element will be "invisible" for user clicks
  selectable: true,
  // use for absolute positing of element
  alwaysOnTop: false,
  // also we can hide some elements from the export
  showInExport: true,
  // can element be moved and rotated
  draggable: true,
  // can we change content of element?
  contentEditable: true,
  // can we remove element from UI with button or keyboard?
  removable: true,
  // can we resize element?
  resizable: true,
  // can we change style of element?
  styleEditable: true,
});

text.toggleEditMode()

Enable edit mode for the text. It puts the cursor inside the text and a user can do regular text editing. You can call text.toggleEditMode() programmatically (e.g. right after creating a new text element).

Image element

Image element draws an image on the canvas. By default images do smart-cropping to fit into its size.

page.addElement({
  type: 'image',
  x: 0,
  y: 0,
  rotation: 0,
  locked: false, // deprecated
  blurEnabled: false,
  blurRadius: 10,
  brightnessEnabled: false,
  brightness: 0,
  shadowEnabled: false,
  shadowBlur: 5,
  shadowOffsetX: 0,
  shadowOffsetY: 0,
  shadowColor: 'black',
  shadowOpacity: 1,
  name: '', // name of element, can be used to find element in the store
  // url path to image
  src: 'https://example.com/image.png',
  keepRatio: false, // can we change aspect ratio of element on resize
  stretchEnabled: false, // can we stretch image on any axis

  // url path to svg or image that will be used to clip image
  // can be used for image framing
  clipSrc: '',
  width: 100,
  height: 100,
  cropX: 0, // 0-1 : % from original image width
  cropY: 0, // 0-1 : % from original image height
  cropWidth: 1, // 0-1 : % from original image width
  cropHeight: 1, // 0-1 : % from original image height
  cornerRadius: 0,
  borderColor: 'black',
  borderSize: 0,
  flipX: false,
  flipY: false,

  // can user select element?
  // if false, element will be "invisible" for user clicks
  selectable: true,
  // use for absolute positing of element
  alwaysOnTop: false,
  // also we can hide some elements from the export
  showInExport: true,
  // can element be moved and rotated
  draggable: true,
  // can we change content of element?
  contentEditable: true,

  // can we remove element from UI with button or keyboard?
  removable: true,
  // can we resize element?
  resizable: true,
});

image.toggleCropMode()

Enter into "crop mode" of the image programmatically.

<Button
  minimal
  icon={<Crop />}
  // use capture handler to handle click event sooner
  onClickCapture={(e) => {
    // stop propagation to prevent autohide of crop mode on current click
    e.stopPropagation();
    element.toggleCropMode(true);
  }}
/>

Image filters

Polotno recognizes two filter categories, each with its own API pattern.

  1. Old filters (use element.set):
sepiaEnabled
grayscaleEnabled
blurEnabled, blurRadius
brightnessEnabled, brightness
  1. Combined filters (use element.setFilter):
cold
natural
warm

temperature
contrast
shadows
white
black
saturation
vibrance

Examples:

// 1. Old filters
shape.set({ sepiaEnabled: true });
shape.set({ blurEnabled: true, blurRadius: 12 });
shape.set({ brightnessEnabled: true, brightness: 0.25 }); // +25 %

// 2. One‑active preset (replaces previous preset)
shape.setFilter('warm');

// 3. Combined numeric filters (stackable)
shape.setFilter('temperature', -0.2);
shape.setFilter('contrast', 0.15);
shape.setFilter('vibrance', 0.3);

// disabling:
// Old filters off
shape.set({
  sepiaEnabled: false,
  blurEnabled: false,
  brightnessEnabled: false,
});

// Remove current preset
shape.setFilter('warm', null);

// Clear all combined filters
['temperature', 'contrast', 'shadows', 'white', 'black', 'saturation', 'vibrance']
  .forEach((f) => shape.setFilter(f, null));

SVG element

SVG elements work similarly to Image elements but can replace internal colors.

const svgElement = page.addElement({
  type: 'svg',
  src: 'https://example.com/image.svg',
  maskSrc: '', // should we draw mask image over svg element?
  keepRatio: false, // can we change aspect ratio of svg?
  stretchEnabled: false, // can we stretch image on any axis
  x: 0,
  y: 0,
  rotation: 0,
  locked: false, // deprecated
  blurEnabled: false,
  blurRadius: 10,
  brightnessEnabled: false,
  brightness: 0,
  shadowEnabled: false,
  shadowBlur: 5,
  shadowOffsetX: 0,
  shadowOffsetY: 0,
  shadowColor: 'black',
  shadowOpacity: 1,
  name: '', // name of element, can be used to find element in the store
  width: 100,
  height: 100,
  flipX: false,
  flipY: false,
  cornerRadius: 0,
  // can user select element?
  // if false, element will be "invisible" for user clicks
  selectable: true,
  // use for absolute positing of element
  alwaysOnTop: false,
  // also we can hide some elements from the export
  showInExport: true,
  // can element be moved and rotated
  draggable: true,
  // can we change content of element?
  contentEditable: true,
  // can we remove element from UI with button or keyboard?
  removable: true,
  // can we resize element?
  resizable: true,
  // map of originalColor -> newColor, used to replace colors in svg image
  // do not change it manually. Instead use `el.replaceColor(originalColor, newColor)`
  colorsReplace: {},
});

If you want to detect colors in an SVG string you can use this:

import { urlToString, getColors, useSvgColors } from 'polotno/utils/svg';

// in react component:
const Toolbar = ({ element }) => {
  const colors = useSvgColors(element.src); // will return array of colors detected in the svg image
};

// in functions:
async function getSvgColors(element) {
  const svgString = await urlToString(element.src);
  const colors = getColors(svgString);
}

svgElement.replaceColor(oldColor, newColor)

Some SVG files support color replacement when a color is defined as fill on internal nodes. You can replace specific colors to modify the original image.

svgElement.replaceColor('red', 'blue');

Line element

Use line elements to draw lines and arrows on the canvas. For now, line elements may not support all available filters from JSON.

const lineElement = page.addElement({
  type: 'line',
  x: 0,
  y: 0,
  width: 400,
  height: 10,
  name: '', // name of element, can be used to find element in the store
  color: 'black',
  rotation: 0,
  dash: [], // array of numbers, like [5, 5]
  startHead: '', // can be empty, arrow, triangle, circle, square, bar
  endHead: '', // can be empty, arrow, triangle, circle, square, bar
  // can user select element?
  // if false, element will be "invisible" for user clicks
  selectable: true,
  // use for absolute positing of element
  alwaysOnTop: false,
  // also we can hide some elements from the export
  showInExport: true,
  // can element be moved and rotated
  draggable: true,
  // can we change content of element?
  contentEditable: true,
  // can we remove element from UI with button or keyboard?
  removable: true,
  // can we resize element?
  resizable: true,
  styleEditable: true,
});

Figure element

Use figure to draw basic shapes on the canvas. It has a large collection of shapes controlled by subType.

const figureElement = page.addElement({
  type: 'figure',
  subType: 'rect',
  x: 0,
  y: 0,
  width: 400,
  height: 400,
  fill: 'black',
  stroke: 'red',
  strokeWidth: 40,
  cornerRadius: 40,
});

Currently supported list of subType:

rect, circle, star, triangle, rightTriangle, diamond, pentagon, hexagon, speechBubble, cross, arc, cloud, rightArrow, leftArrow, downArrow, upArrow, asterisk1, asterisk2, bookmark, butterfly, cylinder, diamond2, door, drop1, drop2, explosion, flag, flower, frame, heart1, home, home2, hourglass, house, keyhole, kiss, leaf, lightning1, lightning2, magnet, mithosis, orangeRicky, party, pillow, polygon, rainbow, rhodeIsland, shell, shield1, shield2, skewedRectangle, softFlower, softStar, stairs1, stairs2, teewee, blob1, blob10, blob11, blob12, blob13, blob14, blob15, blob16, blob17, blob18, blob19, blob2, blob20, blob21, blob22, blob23, blob24, blob25, blob26, blob27, blob28, blob29, blob3, blob30, blob31, blob32, blob4, blob5, blob6, blob7, blob8, blob9

Video element

Use video to render a video on the canvas. Video element has many properties similar to the image element.

const videoElement = page.addElement({
  type: 'video',
  x: 0,
  y: 0,
  rotation: 0,

  // url path to video
  src: 'https://example.com/image.png',
  // start/end time of video in % from video duration
  // can be used for trimming video
  startTime: 0,
  endTime: 1,

  shadowEnabled: false,
  shadowBlur: 5,
  shadowOffsetX: 0,
  shadowOffsetY: 0,
  shadowColor: 'black',
  shadowOpacity: 1,
  name: '', // name of element, can be used to find element in the store

  width: 100,
  height: 100,
  cropX: 0, // 0-1 : % from original image width
  cropY: 0, // 0-1 : % from original image height
  cropWidth: 1, // 0-1 : % from original image width
  cropHeight: 1, // 0-1 : % from original image height
  borderColor: 'black',
  borderSize: 0,
  flipX: false,
  flipY: false,

  // can user select element?
  // if false, element will be "invisible" for user clicks
  selectable: true,
  // use for absolute positing of element
  alwaysOnTop: false,
  // also we can hide some elements from the export
  showInExport: true,
  // can element be moved and rotated
  draggable: true,
  // can we change content of element?
  contentEditable: true,

  // can we remove element from UI with button or keyboard?
  removable: true,
  // can we resize element?
  resizable: true,
});

Group element

Group element is a container for other elements. It can be used to move multiple elements together.

Here is the example of default properties and usage:

const page = store.addPage();
page.addElement({
  type: 'text',
  text: 'Hello world',
  id: 'text-1',
});
page.addElement({
  type: 'text',
  text: 'Hello world',
  id: 'text-2',
});
const group = store.groupElements(['text-1', 'text-2']);

group.set({
  name: 'group',
  opacity: 0.5,

  custom: {},

  visible: true,
  selectable: true,
  removable: true,
  alwaysOnTop: false,
  showInExport: true,
});

// ungroup:
store.ungroupElements([group.id]);

Table element

A table element renders a grid of editable text cells on the canvas. Tables support configurable rows and columns, per-cell text styling, cell merging, per-side border customization, keyboard navigation, and auto-growing rows that expand to fit content.

Tables do not support shadow, blur, brightness, sepia, grayscale, or filter effects.

const table = page.addElement({
  type: 'table',
  x: 50,
  y: 50,
  width: 400,
  height: 250,
  rows: 3,
  cols: 4,

  // text styling defaults (inherited by all cells)
  fontSize: 30,
  fontFamily: 'Roboto',
  fontWeight: 'normal',
  fontStyle: 'normal',
  textDecoration: '',
  textTransform: 'none',
  fill: 'black',
  align: 'left',
  verticalAlign: 'top',
  lineHeight: 1.2,
  letterSpacing: 0,
  strokeWidth: 0,
  stroke: 'black',

  // border defaults
  borderColor: '#000000',
  borderWidth: 1,
  borderStyle: 'solid', // 'solid', 'dashed', 'dotted', 'none'

  // cell defaults
  cellBackground: 'transparent',
  cellPadding: 4,

  name: '',
  selectable: true,
  alwaysOnTop: false,
  showInExport: true,
  draggable: true,
  contentEditable: true,
  removable: true,
  resizable: true,
  styleEditable: true,
});

Cells, colWidths, and rowHeights are generated automatically when omitted. Each column and row starts with equal width/height.

Pre-populating cells

You can pass cell data when creating a table. Cells are stored in row-major order (left-to-right, top-to-bottom). Only properties that differ from the table defaults need to be specified.

page.addElement({
  type: 'table',
  x: 50,
  y: 50,
  width: 400,
  height: 200,
  rows: 2,
  cols: 3,
  cells: [
    { id: 'h1', text: 'Name' },
    { id: 'h2', text: 'Role' },
    { id: 'h3', text: 'Email' },
    { id: 'r1', text: 'Alice' },
    { id: 'r2', text: 'Engineer' },
    { id: 'r3', text: 'alice@example.com' },
  ],
});

Cell access

// Get a specific cell by row/column index (0-based)
const cell = table.getCell(0, 2); // row 0, column 2

// All visible (non-merged) cells
const visible = table.visibleCells;

// Currently focused cells
const focused = table.focusedCells;

// Cell currently being edited (or undefined)
const editing = table.editingCell;

Row operations

// Add a row at index (pushes existing rows down)
table.addRow(1);

// Remove a row by index
table.removeRow(2);

// Resize a row border between row[index] and row[index+1]
// delta is a fraction of total height
table.resizeRow(0, 0.05);

// Make all rows the same height
table.distributeRowsEvenly();

Column operations

// Add a column at index (pushes existing columns right)
table.addColumn(2);

// Remove a column by index
table.removeColumn(0);

// Resize a column border between col[index] and col[index+1]
// delta is a fraction of total width
table.resizeColumn(1, -0.03);

// Make all columns the same width
table.distributeColumnsEvenly();

Cell focus and editing

// Focus a single cell
table.focusCell(cell.id);

// Add a cell to the current selection
table.focusCell(cell.id, true);

// Focus a rectangular range from the anchor cell to (row, col)
table.focusCellRange(2, 3);

// Clear all cell focus and exit editing
table.clearCellFocus();

// Enter text editing mode for a cell
table.enterCellEdit(cell.id);

// Exit text editing mode
table.exitCellEdit();

Cell styling

Each cell can override any of the table's text styling defaults. When a property is null or undefined, the cell inherits the table-level value.

// Update cell properties
cell.set({
  text: 'Hello',
  fontSize: 24,
  fill: 'red',
  fontWeight: 'bold',
  cellBackground: '#f0f0f0',
});

// Reset a cell property to inherit from the table
cell.set({ fontSize: null });

Borders

Each cell has four border sides (top, right, bottom, left). Border properties follow a three-level resolution: cell override → table default → hardcoded fallback.

// Set borders on multiple cells at once
// Automatically syncs adjacent cells
table.setCellBorders(
  [cell1.id, cell2.id],
  ['top', 'bottom'],
  { color: 'red', width: 2, style: 'dashed' },
);

// Set a single border on one cell (does NOT sync adjacent cells)
cell.setBorder('top', { color: 'blue', width: 1 });

// Read the effective border for a side
const border = cell.getEffectiveBorder('top');
// => { color: string, width: number, style: string }

table.clone()

Clone a table (generates new IDs for the table and all cells, remaps mergedInto references).

const copy = table.clone({ x: table.x + 50 });

On this page