Simple presets

Create an editing extension that provides presets.

A simple preset is a preset that users can't customize. It's a binary choice: the presets is applied or it's not. This is in contrast to customizable presets, which let users adjust the settings of a preset after applying it.

This tutorial explains how to create a simple preset that inverts the colors of a user's image.

Step 1: Set up an editing extension

If you haven't already, set up an editing extension that renders the user's image. The following snippet provides the boilerplate of an extension that's suitable for this tutorial:

const { imageHelpers } = window.canva;
const canva = window.canva.init();
const state = {
image: null,
canvas: null,
};
canva.onReady(async (opts) => {
// Download the user's image
state.image = await imageHelpers.fromElement(opts.element);
// Convert the user's image into a HTMLCanvasElement
state.canvas = await imageHelpers.toCanvas(state.image);
// Render the user's image
document.body.appendChild(state.canvas);
});
canva.onImageUpdate(async (opts) => {
// Handle image update requests from Canva
state.image = opts.image;
await renderImage();
});
canva.onSaveRequest(async () => {
// Return the image to Canva
return await imageHelpers.fromCanvas("image/jpeg", state.canvas);
});
async function renderImage() {
const img = await imageHelpers.toImageElement(state.image);
const context = state.canvas.getContext("2d");
context.drawImage(img, 0, 0, state.canvas.width, state.canvas.height);
}

Step 2: Enable presets

By default, editing extensions don't support presets. The feature needs to be enabled.

To enable presets:

  1. Navigate to an app via the Developer Portal.

  2. From the Extensions page, expand the Editing panel.

  3. Click Enable presets.

Step 3: Render a thumbnail for each preset

When a user opens the Effects panel, Canva emits an event. Extensions that support presets must listen for this event by registering a callback with the onPresetsRequest method:

canva.onPresetsRequest(async (opts) => {
// code goes here
});

This callback receives an opts object that provides the user's image via the image property. You can use this property to dynamically generate thumbnails for each preset.

Canva runs the onReady callback immediately before the onPresetsRequest callback, so be mindful of any potential side-effects the onReady callback may cause.

To display presets in the Effects panel, the onPresetsRequest callback must return an array of presets. Each preset must be an object with the following properties:

  • id

  • label

  • image

  • kind

The following snippet creates a preset with the user's (unmodified) image as the thumbnail:

canva.onPresetsRequest(async (opts) => {
return [
{
id: "invert",
label: "Invert",
image: opts.image,
kind: "simple",
},
];
});

Where possible, extensions should dynamically generate preset thumbnails, but it's also possible for an extension to provide static thumbnails.

Preset thumbnails must be at least 256 × 256 pixels.

Step 4: Keep track of the selected preset

When a user opens an extension by selecting a preset, Canva passes the id of the selected preset into the onReady callback, via the presetId property.

To keep track of the presetId, create a selectedPreset property in the state object:

const state = {
image: null,
canvas: null,
selectedPreset: null,
};

Then set the value of this property in the onReady callback:

canva.onReady(async (opts) => {
// Download the user's image
state.image = await imageHelpers.fromElement(opts.element);
// Convert the user's image into a HTMLCanvasElement
state.canvas = await imageHelpers.toCanvas(state.image);
// Render the user's image
document.body.appendChild(state.canvas);
// Keep track of the selected preset
state.selectedPreset = opts.presetId;
});

Selecting a preset when the extension is already open doesn't trigger the onReady callback again. Instead, Canva emits an event each time the user selects another preset.

To listen for these events, register a callback with the onPresetSelected method and, similar to the onReady callback, keep track of the selected preset:

canva.onPresetSelected(async (opts) => {
// Keep track of the selected preset
state.selectedPreset = opts.presetId;
});

Step 5: Apply the selected preset

When rendering the user's image, check if a preset is selected, and if it is, apply the effect to the user's image:

async function renderImage() {
const img = await imageHelpers.toImageElement(state.image);
const context = state.canvas.getContext("2d");
// Apply the selected preset
if (state.selectedPreset === "invert") {
context.filter = `invert(100%)`;
}
context.drawImage(img, 0, 0, state.canvas.width, state.canvas.height);
}

You need to implement this logic in the onReady callback:

canva.onReady(async (opts) => {
// Download the user's image
state.image = await imageHelpers.fromElement(opts.element);
// Convert the user's image into a HTMLCanvasElement
state.canvas = await imageHelpers.toCanvas(state.image);
// Render the user's image
document.body.appendChild(state.canvas);
// Keep track of the selected preset
state.selectedPreset = opts.presetId;
if (state.selectedPreset) {
// Re-render the image when a user selects a preset
await renderImage();
}
});

…and in the onPresetSelected callback:

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();
});

The opts.presetId property in the onReady callback is sometimes undefined. In these cases, the extension must render the user's image without a preset applied.

Example

const { imageHelpers } = window.canva;
const canva = window.canva.init();
const state = {
image: null,
canvas: null,
selectedPreset: null,
};
canva.onReady(async (opts) => {
// Download the user's image
state.image = await imageHelpers.fromElement(opts.element);
// Convert the user's image into a HTMLCanvasElement
state.canvas = await imageHelpers.toCanvas(state.image);
// Render the user's image
document.body.appendChild(state.canvas);
// Keep track of the selected preset
state.selectedPreset = opts.presetId;
if (opts.presetId) {
// Re-render the image when a user selects a preset
await renderImage();
}
});
canva.onPresetsRequest(async (opts) => {
return [
{
id: "invert",
label: "Invert",
image: opts.image,
kind: "simple",
},
];
});
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;
await renderImage();
});
canva.onSaveRequest(async () => {
// Return the image to Canva
return await imageHelpers.fromCanvas("image/jpeg", state.canvas);
});
async function renderImage() {
const img = await imageHelpers.toImageElement(state.image);
const context = state.canvas.getContext("2d");
// Apply the selected preset
if (state.selectedPreset === "invert") {
context.filter = `invert(100%)`;
}
context.drawImage(img, 0, 0, state.canvas.width, state.canvas.height);
}