Engine errors now carry a stable catalog code, a developer-facing hint, typed args, and an
optional docs anchor. Existing code that reads error.message keeps working. New code can branch
on the stable code — for example "SCENE.NOT_VALID" — and surface customer-facing copy or doc
links derived from the catalog entry.
This guide explains how to move off message-string matching to code-based branching, and how to migrate your text and caption preset setup.
What changed#
Engine APIs reached through CreativeEngine (and the framework wrappers built on it) used to throw
plain Error objects whose only signal was the rendered message. They now throw EngineError
instances: EngineError extends Error and adds code, category, hint, args, docsUrl, and
silent.
The change is additive — the previous “just read error.message” pattern still works, and
every engine error carries a non-empty catalog code, so you can branch on the codes you care
about and fall back to the message for the rest.
Type-safe error codes#
@cesdk/engine ships a generated EngineErrorCode — a string-literal union type (import it
with import type). Branch on a constant instead of a hand-typed string and catch typos at compile
time with 'SCENE.NOT_VALID' satisfies EngineErrorCode, or type a lookup table with
Partial<Record<EngineErrorCode, …>>. The code field itself stays a plain string, so an
unrecognized or future code never breaks your build — always keep a default branch for codes you
don’t handle.
Branch on error codes#
EngineError extends the standard Error, so existing try/catch flows that read error.message
keep working unchanged. New code branches on error.code:
import CreativeEngine, { EngineError, isEngineError, type EngineErrorCode} from '@cesdk/engine';
try { await engine.scene.create();} catch (error) { if (isEngineError(error)) { switch (error.code) { // `satisfies EngineErrorCode` turns a misspelled code into a compile-time error. case 'SCENE.NOT_VALID' satisfies EngineErrorCode: toast.show('No scene is loaded.'); break; default: // Any code you don't handle explicitly — fall back to the message. toast.show(error.message); }
if (error.docsUrl) { window.open(error.docsUrl, '_blank'); } } else { throw error; }}i18n with typed args#
error.args preserves the original types (number stays number, string stays string,
boolean stays boolean). Pipe them straight into i18next interpolation or
Intl.NumberFormat — no parsing of stringified values:
if ( isEngineError(error) && error.code === ('BLOCK.TEXT_INVALID_FONT_SIZE' satisfies EngineErrorCode)) { toast.show(t('error.blockTextInvalidFontSize', error.args));}Cross-cutting tips#
- Always keep a
defaultbranch. Every engine error carries a stable catalog code, but new catalog entries ship over time — handle codes you don’t recognize by falling back to the rendered message instead of crashing. Generic failures wrapped from third-party libraries orerrnosurface under a catch-all code such asUTILS.STD_EXPECTED_STRING_ERROR. silent: trueflags errors the catalog marks as expected limitations. They are intended to be programmatically observable but suppressed from logs.error.docsUrlpoints at the matching documentation page underhttps://img.ly/docs/cesdk/js/{docs}/; the binding builds the URL for you.
What is NOT a breaking change#
error.messagestill returns the engine’s rendered English string. Existing logging keeps working.- All previous error wording is preserved exactly where it was a stable contract; only error construction moved through the catalog. Where wording was inconsistent across call sites for the same logical failure, the catalog now renders a single normalized string.
Code stability#
Treat error.code (and its category prefix) as a stable, append-only contract:
- Existing codes are not renamed or repurposed. A code you branch on today keeps its meaning
in future releases, so
error.code === "SCENE.NOT_VALID"is safe to rely on long-term. - New codes are added over time as more engine sites and categories are catalogued — which is
exactly why you should always keep a
defaultbranch (see above) for codes you don’t recognize.
This is what makes code-based branching more durable than message-string matching: the message text is developer-facing English and may be reworded, but the code is the contract.
What might surprise you#
error.nameembeds the code.EngineErrorsetsnametoEngineError [<code>](for exampleEngineError [SCENE.NOT_VALID]), following the Node.js convention, so stack traces and pasted log lines identify the failure on their own. Don’t match on a constantname— narrow withisEngineError(error)(orinstanceof) and branch onerror.code.- Message-string matching becomes lossy on multi-site errors. Where the catalog consolidated
several inline
Error("…")sites into a single entry, the rendered message is now the catalog’s consolidated phrasing. If your code matches a substring that came from a specific site, switch to the stableerror.codeinstead. - Trailing newlines are gone. The rendered
error.messageno longer carries the trailing"\n"that the engine’s internalgetMessage()added for log formatting. Code that explicitly trimmed it will still work; code that depended on it being there will not.
Text and caption presets#
Version 1.77 reworks the built-in text and caption presets into reusable style presets that apply a complete look (font, color, outline, background, shadow, and animations) in one step. Captions use the same declarative format.
The default Text library (ly.img.text) keeps its ID, so most integrations need no changes — its content moved to the new style presets, and three more sources now feed the same entry.
Do I need to migrate?#
You are affected if you:
- match specific text asset IDs — the previous Title, Headline, and Body entries used IDs like
ly.img.text.title; the new presets use group-scoped IDs likely.img.text.default.title; - depend on the previous text preset content;
- self-host assets (the set of text asset-source folders changed — see below);
- depend on the previous caption preset content or format.
If you add new TextAssetSource(), new TextComponentAssetSource(), and new CaptionPresetsAssetSource() and use the default asset library, the plugins register everything for you: bump the SDK and you are done.
What changed#
The text presets are split across four asset sources, all surfaced in the single ly.img.text library entry as sections:
| Section | Asset source | Plugin |
|---|---|---|
| Plain Text (Default / Elegant / Modern Tech) | ly.img.text | TextAssetSource |
| Text Styles | ly.img.text.styles | TextAssetSource |
| Curved Text | ly.img.text.curves | TextAssetSource |
| Text Combinations | ly.img.text.components | TextComponentAssetSource |
The ly.img.text source keeps the ID it has used since earlier versions, but its content changed from the basic Title / Headline / Body entries to the new Plain Text style presets, with group-scoped asset IDs (for example ly.img.text.default.title). If you matched the old asset IDs or labels, update them or supply your own through the plugin config. If you self-host, copy the ly.img.text, ly.img.text.styles, ly.img.text.curves, and ly.img.text.components folders.
Dock and library configuration#
The default Text dock entry is ly.img.text — one library that holds all four text sections. If you use a custom dock or library layout, reference it directly:
entries: ['ly.img.text'],The text “Styles” inspector button restyles a block from the same library, minus the Text Combinations source (text designs create new blocks and aren’t valid restyle targets). You can customize which entries — and which of their sources — appear when replacing through cesdk.ui.setReplaceAssetLibraryEntries, which now accepts { entry, excludeSourceIds } objects in addition to plain entry IDs.
Caption presets#
The CaptionPresetsAssetSource plugin keeps its ly.img.caption.presets source ID, so no source-ID change is needed. Only the preset content and format changed, to the same declarative style presets that text uses. If you authored custom caption presets, update them to the new format.
Keyboard shortcuts#
What changed#
Keyboard shortcuts became a public, customizable registry (cesdk.shortcuts, and engine.shortcuts
when headless).
How it impacts you#
Keyboard shortcuts are not enabled by default. To turn them on, enable the
ly.img.keyboard.shortcuts feature through the Feature API.
How to enable it#
cesdk.feature.enable('ly.img.keyboard.shortcuts', true);