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.

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:
1
{
2
"en": [
3
{
4
"key": "categoryOne",
5
"value": "Category #1",
6
"translatorNote": "Label for the first category."
7
},
8
{
9
"key": "categoryTwo",
10
"value": "Category #2",
11
"translatorNote": "Label for the second category."
12
}
13
]
14
}
Copied!
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:
1
[
2
{
3
"id": "invert",
4
"label": "Invert",
5
"asset": "invert.png",
6
"kind": "simple",
7
"category": "Category #1"
8
},
9
{
10
"id": "grayscale",
11
"label": "Grayscale",
12
"asset": "grayscale.png",
13
"kind": "simple",
14
"category": "Category #2"
15
},
16
{
17
"id": "sepia",
18
"label": "Sepia",
19
"asset": "sepia.png",
20
"kind": "simple",
21
"category": "Category #2"
22
}
23
]
Copied!
If you're submitting a public app for review, set the value of the category property to the key of a localized label:
1
[
2
{
3
"id": "invert",
4
"label": "Invert",
5
"asset": "invert.png",
6
"kind": "simple",
7
"category": "categoryOne"
8
},
9
{
10
"id": "grayscale",
11
"label": "Grayscale",
12
"asset": "grayscale.png",
13
"kind": "simple",
14
"category": "categoryTwo"
15
},
16
{
17
"id": "sepia",
18
"label": "Sepia",
19
"asset": "sepia.png",
20
"kind": "simple",
21
"category": "categoryTwo"
22
}
23
]
Copied!
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:
1
canva.onPresetsRequest(async (opts) => {
2
const presets = [
3
{
4
id: "invert",
5
label: "Invert",
6
kind: "simple",
7
categoryId: "categoryOne",
8
},
9
{
10
id: "grayscale",
11
label: "Grayscale",
12
kind: "simple",
13
categoryId: "categoryTwo",
14
},
15
{
16
id: "sepia",
17
label: "Sepia",
18
kind: "simple",
19
categoryId: "categoryTwo",
20
},
21
];
22
23
// Download the thumbnail images
24
const thumbnails = await Promise.all(
25
presets.map((preset) => {
26
const assetUrl = state.assets[preset.id + ".png"];
27
return imageHelpers.fromUrl(assetUrl);
28
})
29
);
30
31
// Return the presets
32
return presets.map((preset, index) => {
33
return {
34
...preset,
35
image: thumbnails[index],
36
};
37
});
38
});
Copied!
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

1
const { imageHelpers } = window.canva;
2
const canva = window.canva.init();
3
4
const state = {
5
assets: null,
6
element: null,
7
canvas: null,
8
image: null,
9
selectedPreset: null,
10
};
11
12
canva.onReady(async (opts) => {
13
// Keep track of the assets
14
state.assets = opts.assets;
15
16
// Keep track of the element
17
state.element = opts.element;
18
19
// Download the image
20
state.image = await imageHelpers.fromElement(state.element, "preview");
21
22
// Convert the image into an HTMLCanvasElement
23
state.canvas = await imageHelpers.toCanvas(state.image);
24
25
// Render the image
26
document.body.appendChild(state.canvas);
27
28
if (opts.presetId) {
29
// Keep track of the selected preset
30
state.selectedPreset = opts.presetId;
31
32
// Render the image with preset applied
33
await renderImage();
34
}
35
});
36
37
canva.onPresetsRequest(async (opts) => {
38
const presets = [
39
{
40
id: "invert",
41
label: "Invert",
42
kind: "simple",
43
categoryId: "categoryOne",
44
},
45
{
46
id: "grayscale",
47
label: "Grayscale",
48
kind: "simple",
49
categoryId: "categoryTwo",
50
},
51
{
52
id: "sepia",
53
label: "Sepia",
54
kind: "simple",
55
categoryId: "categoryTwo",
56
},
57
];
58
59
// Download the thumbnail images
60
const thumbnails = await Promise.all(
61
presets.map((preset) => {
62
const assetUrl = state.assets[preset.id + ".png"];
63
return imageHelpers.fromUrl(assetUrl);
64
})
65
);
66
67
// Return the presets
68
return presets.map((preset, index) => {
69
return {
70
...preset,
71
image: thumbnails[index],
72
};
73
});
74
});
75
76
canva.onPresetSelected(async (opts) => {
77
// Keep track of the selected preset
78
state.selectedPreset = opts.presetId;
79
80
// Re-render the image when a user selects a preset
81
await renderImage();
82
});
83
84
canva.onImageUpdate(async (opts) => {
85
// Handle image update requests from Canva
86
state.image = opts.image;
87
88
// Re-render the image
89
await renderImage();
90
});
91
92
canva.onSaveRequest(async () => {
93
// Download the full image
94
state.image = await imageHelpers.fromElement(state.element, "full");
95
96
// Re-render the image
97
await renderImage();
98
99
// Return the image to Canva
100
return await imageHelpers.fromCanvas(state.image.imageType, state.canvas);
101
});
102
103
async function renderImage() {
104
const img = await imageHelpers.toImageElement(state.image);
105
const context = state.canvas.getContext("2d");
106
107
if (state.selectedPreset === "invert") {
108
context.filter = `invert(100%)`;
109
}
110
111
if (state.selectedPreset === "grayscale") {
112
context.filter = `grayscale(100%)`;
113
}
114
115
if (state.selectedPreset === "sepia") {
116
context.filter = `sepia(100%)`;
117
}
118
119
context.drawImage(img, 0, 0, state.canvas.width, state.canvas.height);
120
}
Copied!

presets.json

1
[
2
{
3
"id": "invert",
4
"label": "Invert",
5
"asset": "invert.png",
6
"kind": "simple",
7
"category": "categoryOne"
8
},
9
{
10
"id": "grayscale",
11
"label": "Grayscale",
12
"asset": "grayscale.png",
13
"kind": "simple",
14
"category": "categoryTwo"
15
},
16
{
17
"id": "sepia",
18
"label": "Sepia",
19
"asset": "sepia.png",
20
"kind": "simple",
21
"category": "categoryTwo"
22
}
23
]
Copied!

labels.json

1
{
2
"en": [
3
{
4
"key": "categoryOne",
5
"value": "Category #1",
6
"translatorNote": "Label for the first category."
7
},
8
{
9
"key": "categoryTwo",
10
"value": "Category #2",
11
"translatorNote": "Label for the second category."
12
}
13
]
14
}
Copied!

invert.png

grayscale.png

sepia.png

Last modified 27d ago