Search Docs
Loading...
Skip to content

To v1.77

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 default branch. 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 or errno surface under a catch-all code such as UTILS.STD_EXPECTED_STRING_ERROR.
  • silent: true flags errors the catalog marks as expected limitations. They are intended to be programmatically observable but suppressed from logs.
  • error.docsUrl points at the matching documentation page under https://img.ly/docs/cesdk/js/{docs}/; the binding builds the URL for you.

What is NOT a breaking change#

  • error.message still 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 default branch (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.name embeds the code. EngineError sets name to EngineError [<code>] (for example EngineError [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 constant name — narrow with isEngineError(error) (or instanceof) and branch on error.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 stable error.code instead.
  • Trailing newlines are gone. The rendered error.message no longer carries the trailing "\n" that the engine’s internal getMessage() 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 like ly.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:

SectionAsset sourcePlugin
Plain Text (Default / Elegant / Modern Tech)ly.img.textTextAssetSource
Text Stylesly.img.text.stylesTextAssetSource
Curved Textly.img.text.curvesTextAssetSource
Text Combinationsly.img.text.componentsTextComponentAssetSource

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