Error handling
Two things happen when a generation or edit fails. The editor handles the user: it shows a localized message in place and stays usable — no wiring needed. Your app gets the uc:error event, for everything the built-in UI can't know: logging, metrics, or reacting to specific failures.
const editor = document.querySelector('uc-ai-enhancer');
editor.addEventListener('uc:error', (e) => {
const error = e.detail.error; // always an AiEnhancerError
console.warn(`generation failed: ${error.code}`, error);
});The event bubbles and crosses shadow DOM boundaries (composed), so a listener on a container — or document — works too.
The error object
detail.error is always an AiEnhancerError — whatever actually went wrong (a platform rejection, a network failure, even a custom provider throwing a string) is normalized into it:
| Field | Type | What it tells you |
|---|---|---|
code | AiEnhancerErrorCode | What failed — one of the known codes, or any other string (backend-minted, or frontend-originated like the React wrapper's engine_load_failed). 'unknown' when the failure carried no code (e.g. a network error). |
message | string | The raw, untranslated failure text — for logs, not for users (show localized messages instead). |
source | string | undefined | Which stage of a job reported the failure, when the backend says (e.g. 'ai_gateway'). Values are backend-defined — treat as log enrichment, not something to branch on. |
cause | unknown | The original thrown value, untouched — the underlying Error, or whatever a custom provider threw. |
Error codes
code is the field to branch on:
import type { AiEnhancerError } from '@uploadcare/ai-enhancer';
function report(error: AiEnhancerError) {
switch (error.code) {
case 'content_moderated':
// the prompt was blocked — nothing to retry
break;
case 'provider_unavailable':
case 'generation_timeout':
// transient — worth offering a retry
break;
default:
console.error('ai-enhancer failed:', error.code, error.message);
}
}The known codes come in three families — platform validation (bad input or setup: invalid_source, canvas_too_large, derivative_disabled, …), AI gateway (generation itself: content_moderated, provider_unavailable, generation_timeout, …), and upload pipeline (persisting the result: DownloadFileHTTPClientError and friends — yes, PascalCase: the backend passes those through as literal upload-service class names). The full list with their user-facing messages lives in the localization guide.
AiEnhancerErrorCode is deliberately open: the known codes are typed as literals (you get autocomplete and exhaustive switch support), but the backend can introduce new ones at any time, so any other string flows through rather than breaking. Don't treat the union as closed — keep a default branch.
React
The React wrapper delivers the same object to onError:
<AiEnhancer
pubkey="YOUR_PUBLIC_KEY"
onError={(error) => console.warn(error.code, error.cause)}
/>One React-specific case: if the lazily-loaded editor engine itself fails to load (e.g. a chunk request dies on a flaky connection), onError receives an AiEnhancerError with code: 'engine_load_failed' and the fallback stays rendered. That code is frontend-originated — it never appears in the backend families above.
Customizing the in-editor messages
The message the editor shows for a code is a locale string, overridable per-code without touching your event handling:
editor.localeDefinitionOverride = {
en: { 'ai-enhancer-error-content_moderated': 'That prompt isn’t allowed here.' },
};A code with no matching key (unknown or untranslated) falls back to the generic ai-enhancer-error message. See Localization for the mechanics and the full key list.
Server-side code
instanceof AiEnhancerError works in server code too — import the class from the side-effect-free errors entry, which is safe where the main entry (it registers custom elements) is not:
import { AiEnhancerError } from '@uploadcare/ai-enhancer/errors';
if (err instanceof AiEnhancerError && err.code === 'derivative_disabled') {
// e.g. surface a setup hint in your server logs
}What doesn't fire uc:error
- Cancellations. Closing the editor fires
uc:cancel; superseding or aborting an in-flight generation (Start over, unmount) is swallowed silently — aborts are a user action, not a failure. - Image display failures. If a result generated fine but its image fails to load into the canvas, that surfaces as a separate
uc:image-errorevent (detail.urlnames the failing URL) — the generation itself didn't fail.