Lesson 11 — AI Commands
AI features require a paid Canva subscription. These APIs only work when the user is signed into a Canva account with an active subscription. Scripts that call AI commands silently fail or return errors without one.
Affinity exposes its AI capabilities directly to scripts. This lesson covers the seven AI operations — image generation, generative inpainting, background removal, subject selection, depth map generation, colourisation, and image tracing — and shows how to combine them with the rest of the SDK.
These commands run natively inside Affinity, so they respect the document model, integrate with the undo stack, and need no external API keys. Every AI command shares a setup requirement that's easy to miss: it must run on the correct spread, with the right selection, and be awaited. Scripts that call them synchronously or without an active spread fail silently. The sequencing — set spread, set selection, then await the command — is covered here so you don't have to discover it through failed runs.
The commands also differ in what they operate on:
generateImagecreates a new raster node from a prompt (no selection needed).generativeEditImagefills a masked area of an existing raster layer.removeBackgroundandselectSubjectoperate on the selected raster node.detectDepthandcolouriseproduce a new layer from an existing one.imageTraceconverts a raster to a vectorPolyCurve.
By the end you'll be able to call all seven correctly, chain them with export pipelines, and drive them from dialog-driven workflows.
What this lesson covers
generateImage(prompt)— AI image generationgenerativeEditImage(prompt)— inpainting / generative fillremoveBackground()— background removalselectSubject()— smart subject selectiondetectDepth()— depth map generationcolourise()— grayscale to colourimageTrace()— raster to vector- Running AI commands correctly (spread, selection, async)
Why use the SDK AI commands?
Prefer these over external AI APIs. The SDK AI commands are native to Affinity, respect the edit model, integrate with the undo stack, and don't require network credentials.
Generate an image from a prompt
'use strict';
const { Document } = require('/document.js');
const { DocumentCommand } = require('/commands.js');
const doc = Document.current;
if (!doc) { console.log('No doc.'); }
else {
// Set the spread first — generateImage places onto the CURRENT spread
doc.executeCommand(DocumentCommand.createSetCurrentSpread(doc.currentSpread));
doc.generateImage('A misty mountain landscape at dawn, digital art style');
console.log('Image generation started.');
}
Set
currentSpreadexplicitly before callinggenerateImage. The image is placed on whichever spread is current at the time the command runs.
Generative edit (inpainting)
Select the region you want to edit (use a raster selection or select a layer), then:
'use strict';
const { Document } = require('/document.js');
const { DocumentCommand } = require('/commands.js');
const doc = Document.current;
doc.executeCommand(DocumentCommand.createSetCurrentSpread(doc.currentSpread));
// Requires a raster selection or selected image node
doc.generativeEditImage('Replace with a clear blue sky');
console.log('Generative edit started.');
Remove background
createRemoveBackground() strips the background from a selected image node, leaving the subject on a transparent layer.
'use strict';
const { DocumentCommand } = require('/commands.js');
const { Document } = require('/document.js');
const doc = Document.current;
const node = doc.selection.nodes.filter(n => n.isImageNode || n.isRasterNode).first;
if (!node) {
console.log('Select an image node first.');
} else {
doc.executeCommand(DocumentCommand.createSetCurrentSpread(doc.currentSpread));
doc.executeCommand(DocumentCommand.createRemoveBackground());
console.log('Background removal started.');
}
Select subject
AI-powered selection of the main subject in the current image:
'use strict';
const { Document, DocumentCommand } = require('/document.js');
const doc = Document.current;
if (!doc) { console.log('No document open.'); }
else {
doc.executeCommand(DocumentCommand.createSetCurrentSpread(doc.currentSpread));
doc.executeCommand(DocumentCommand.createSelectSubject());
console.log('Subject selection applied.');
// The raster selection is now set — you can use it for further operations
}
Detect depth
Generates a depth map from the selected image:
'use strict';
const { Document } = require('/document.js');
const doc = Document.current;
if (!doc) { console.log('No document open.'); }
else {
doc.detectDepth();
console.log('Depth detection started.');
}
Colourise
Converts a grayscale image to colour:
'use strict';
const { Document } = require('/document.js');
const doc = Document.current;
if (!doc) { console.log('No document open.'); }
else {
doc.colourise();
console.log('Colourisation started.');
}
Image trace (raster to vector)
Converts the selected raster/image node into vector curves:
'use strict';
const { Document } = require('/document.js');
const { DocumentCommand } = require('/commands.js');
const doc = Document.current;
if (!doc) { console.log('No document open.'); }
else if (doc.selection.nodes.isEmpty) { console.log('Select a raster or image node first.'); }
else {
const edgeThreshold = 0.5; // 0.0–1.0 — how sharp edges need to be detected
const curveFittingTolerance = 2.0; // lower = tighter fit, more nodes
// doc.imageTrace is the convenience form
doc.imageTrace(edgeThreshold, curveFittingTolerance, doc.selection);
console.log('Image trace complete.');
}
Batch image generation
Generate one image per prompt, each on its own artboard:
'use strict';
const { Document } = require('/document.js');
const { DocumentCommand } = require('/commands.js');
const PROMPTS = [
'A serene forest path in autumn',
'A futuristic cityscape at night',
'An abstract geometric pattern in blue and gold',
];
const doc = Document.current;
if (!doc) { console.log('No doc.'); }
else {
for (let i = 0; i < PROMPTS.length; i++) {
doc.executeCommand(DocumentCommand.createSetCurrentSpread(doc.currentSpread));
doc.generateImage(PROMPTS[i]);
console.log(`Generated image ${i + 1}/${PROMPTS.length}`);
}
}
Using AI commands with doc.promises (async)
For long-running AI operations, use doc.promises to avoid blocking the script (e.g. when chaining AI generation with export or document operations).
'use strict';
const { DocumentCommand } = require('/commands.js');
async function generateAndExport(prompt, outputPath) {
const { DocumentPromises, FileExportOptions, FileExportArea } = require('/document.js');
const docP = await DocumentPromises.getCurrent();
if (!docP) return;
const doc = docP.document;
await docP.executeCommand(DocumentCommand.createSetCurrentSpread(doc.currentSpread));
doc.generateImage(prompt);
// Export after generation
const opts = FileExportOptions.createWithPresetName('PNG');
const area = FileExportArea.createForCurrentSpread();
const records = await docP.export(outputPath, opts, area, null);
console.log(records.all[0]?.isSuccess ? 'Done.' : 'Failed.');
}
// Call with an example prompt and Desktop output path
// const { app } = require('/application.js');
// generateAndExport('Abstract blue waves', app.userDesktopPath + '/generated.png');
Dialog-driven image generator
Combine Dialog (Lesson 08) with AI commands for a practical tool:
'use strict';
const { Document } = require('/document.js');
const { DocumentCommand } = require('/commands.js');
const { Dialog, DialogResult } = require('/dialog.js');
const doc = Document.current;
if (!doc) { console.log('No doc.'); }
else {
const dlg = Dialog.create('Generate Image');
const col = dlg.addColumn();
const grp = col.addGroup('Prompt');
const promptBox = grp.addTextBox('Describe the image', '');
promptBox.isMultiLine = true;
promptBox.rowSpan = 4;
const result = dlg.runModal();
if (result == DialogResult.Ok) {
const prompt = promptBox.text.trim();
if (!prompt) {
console.log('No prompt entered.');
} else {
doc.executeCommand(DocumentCommand.createSetCurrentSpread(doc.currentSpread));
doc.generateImage(prompt);
console.log('Generating:', prompt);
}
}
}
Key gotchas
Always set currentSpread before AI commands. generateImage, generativeEditImage, removeBackground, and detectDepth all operate on the current spread. Forgetting this means the command runs on the wrong page.
AI commands are async under the hood. They trigger processing but return before it finishes. Use executeCommandAsync or doc.promises.executeCommand if you need to chain operations after completion.
selectSubject and removeBackground require an image node to be selected. They fail silently if the selection is wrong.
DocumentCommand factory equivalents
The AI commands shown here use doc convenience methods (doc.generateImage(), doc.removeBackground(), etc.). They also exist as DocumentCommand factories — createRemoveBackground(sel), createSelectSubject(sel) — which let you combine them with CompoundCommandBuilder and pass explicit selections instead of relying on doc.selection.
Exercises
Exercise 11.1 — Single image generator
Build a dialog with a text box for a prompt. Generate an image on the current spread when the user confirms.
Exercise 11.2 — Background removal batch
Find all image nodes in the selection. Remove the background from each one.
Exercise 11.3 — Multi-prompt generator
Accept a multi-line prompt box where each line is a separate prompt. Generate one image per line.
Exercise 11.4 — Trace and style
Select a raster image. Run imageTrace() to vectorise it, then apply a solid colour fill to the result.
SDK modules used in this lesson
/commands—DocumentCommand/document—Document,DocumentPromises/dialog—Dialog,DialogResult
Further examples
| Example | What it shows |
|---|---|
boldItalics.js | DocumentCommand for text attribute changes (bold/italic), Dialog usage |
See also
Example scripts
11a-image-generator-dialog.js
Download 11a-image-generator-dialog.js
'use strict';
// Example 10a — Dialog-driven image generator
// Lesson 10: AI Commands
//
// Shows a prompt dialog and generates an image on the current spread.
const { Document } = require('/document.js');
const { DocumentCommand } = require('/commands.js');
const { Dialog, DialogResult } = require('/dialog.js');
const doc = Document.current;
if (!doc) { console.log('No document open.'); }
else {
const dlg = Dialog.create('AI Image Generator');
const col = dlg.addColumn();
const grp1 = col.addGroup('Prompt');
const promptBox = grp1.addTextBox('Describe the image you want', '');
promptBox.isMultiLine = true;
promptBox.rowSpan = 5;
const grp2 = col.addGroup('Options');
const onNewSpread = grp2.addSwitch('Place on new spread', false);
const result = dlg.runModal();
if (result == DialogResult.Ok) {
const prompt = promptBox.text.trim();
if (!prompt) {
console.log('Enter a prompt and try again.');
} else {
// Always set current spread before AI commands
doc.executeCommand(DocumentCommand.createSetCurrentSpread(doc.currentSpread));
doc.generateImage(prompt);
console.log('Image generation started for prompt:');
console.log(' ', prompt);
}
} else {
console.log('Cancelled.');
}
}