Links

Preset categories

Organize presets into labelled categories.
An editing extension can organize presets into categories. This creates a visual hierarchy that makes it easier for users to navigate a large number of presets.
This topic explains how to organize presets into categories.
If any presets are organized into categories, all presets must be organized into categories. You can’t organize a subset presets into categories.

Prerequisites

  • You’re comfortable with JavaScript.
  • You know how to create an editing extension.
  • You know how presets work.
  • You’ve read the UX guidelines that apply to presets.

Step 1: Create a localized label for each category

Before submitting a public app for review, you must upload all user-facing strings to the extension via its Translation field. This includes the names of preset categories. Canva refers to these strings as localized labels.
You can skip this step if you're creating a team app or your app is under active development.
The following snippet demonstrates how to create a file that contains two localized labels:
{
"en": [
{
"key": "categoryOne",
"value": "Category #1",
"translatorNote": "Label for the first category."
},
{
"key": "categoryTwo",
"value": "Category #2",
"translatorNote": "Label for the second category."
}
]
}
To learn more about localized labels, see Translations.

Step 2: Assign a category to each preset

The next step is to assign categories to the extension's presets. The process for assigning categories to presets is a little different depending on whether the presets are static or dynamic.

Static presets

At the moment, extensions that support static presets must also support the onPresetsRequest callback, as described in the Dynamic presets section. This is a temporary requirement that will be phased out in the future.
In the extension’s Presets JSON file, add a category property to each preset.
If you're creating a team app or the app is still in development, set the value of the category property to the name of a category:
[
{
"id": "invert",
"label": "Invert",
"asset": "invert.png",
"kind": "simple",
"category": "Category #1"
},
{
"id": "grayscale",
"label": "Grayscale",
"asset": "grayscale.png",
"kind": "simple",
"category": "Category #2"
},
{
"id": "sepia",
"label": "Sepia",
"asset": "sepia.png",
"kind": "simple",
"category": "Category #2"
}
]
If you're submitting a public app for review, set the value of the category property to the key of a localized label:
[
{
"id": "invert",
"label": "Invert",
"asset": "invert.png",
"kind": "simple",
"category": "categoryOne"
},
{
"id": "grayscale",
"label": "Grayscale",
"asset": "grayscale.png",
"kind": "simple",
"category": "categoryTwo"
},
{
"id": "sepia",
"label": "Sepia",
"asset": "sepia.png",
"kind": "simple",
"category": "categoryTwo"
}
]
Presets with the same values for their category properties are arranged into groups.

Dynamic presets

In the extension's onPresetsRequest callback, add a categoryId property to each preset:
canva.onPresetsRequest(async (opts) => {
const presets = [
{
id: "invert",
label: "Invert",
kind: "simple",
categoryId: "categoryOne",
},
{
id: "grayscale",
label: "Grayscale",
kind: "simple",
categoryId: "categoryTwo",
},
{
id: "sepia",
label: "Sepia",
kind: "simple",
categoryId: "categoryTwo",
},
];
// Download the thumbnail images
const thumbnails = await Promise.all(
presets.map((preset) => {
const assetUrl = state.assets[preset.id + ".png"];
return imageHelpers.fromUrl(assetUrl);
})
);
// Return the presets
return presets.map((preset, index) => {
return {
...preset,
image: thumbnails[index],
};
});
});
The value of this property must be the key of a localized label.
Presets with the same values for their categoryId properties are arranged into groups.

Example

index.js

const { imageHelpers } = window.canva;
const canva = window.canva.init();
const state = {
assets: null,
element: null,
canvas: null,
image: null,
selectedPreset: null,
};
canva.onReady(async (opts) => {
// Keep track of the assets
state.assets = opts.assets;
// Keep track of the element
state.element = opts.element;
// Download the image
state.image = await imageHelpers.fromElement(state.element, "preview");
// Convert the image into an HTMLCanvasElement
state.canvas = await imageHelpers.toCanvas(state.image);
// Render the image
document.body.appendChild(state.canvas);
if (opts.presetId) {
// Keep track of the selected preset
state.selectedPreset = opts.presetId;
// Render the image with preset applied
await renderImage();
}
});
canva.onPresetsRequest(async (opts) => {
const presets = [
{
id: "invert",
label: "Invert",
kind: "simple",
categoryId: "categoryOne",
},
{
id: "grayscale",
label: "Grayscale",
kind: "simple",
categoryId: "categoryTwo",
},
{
id: "sepia",
label: "Sepia",
kind: "simple",
categoryId: "categoryTwo",
},
];
// Download the thumbnail images
const thumbnails = await Promise.all(
presets.map((preset) => {
const assetUrl = state.assets[preset.id + ".png"];
return imageHelpers.fromUrl(assetUrl);
})
);
// Return the presets
return presets.map((preset, index) => {
return {
...preset,
image: thumbnails[index],
};
});
});
canva.onPresetSelected(async (opts) => {
// Keep track of the selected preset
state.selectedPreset = opts.presetId;
// Re-render the image when a user selects a preset
await renderImage();
});
canva.onImageUpdate(async (opts) => {
// Handle image update requests from Canva
state.image = opts.image;
// Re-render the image
await renderImage();
});
canva.onSaveRequest(async () => {
// Download the full image
state.image = await imageHelpers.fromElement(state.element, "full");
// Re-render the image
await renderImage();
// Return the image to Canva
return await imageHelpers.fromCanvas(state.image.imageType, state.canvas);
});
async function renderImage() {
const img = await imageHelpers.toImageElement(state.image);
const context = state.canvas.getContext("2d");
if (state.selectedPreset === "invert") {
context.filter = `invert(100%)`;
}
if (state.selectedPreset === "grayscale") {
context.filter = `grayscale(100%)`;
}
if (state.selectedPreset === "sepia") {
context.filter = `sepia(100%)`;
}
context.drawImage(img, 0, 0, state.canvas.width, state.canvas.height);
}

presets.json

[
{
"id": "invert",
"label": "Invert",
"asset": "invert.png",
"kind": "simple",
"category": "categoryOne"
},
{
"id": "grayscale",
"label": "Grayscale",
"asset": "grayscale.png",
"kind": "simple",
"category": "categoryTwo"
},
{
"id": "sepia",
"label": "Sepia",
"asset": "sepia.png",
"kind": "simple",
"category": "categoryTwo"
}
]

labels.json

{
"en": [
{
"key": "categoryOne",
"value": "Category #1",
"translatorNote": "Label for the first category."
},
{
"key": "categoryTwo",
"value": "Category #2",
"translatorNote": "Label for the second category."
}
]
}

invert.png

grayscale.png

sepia.png