Skip to main content

Callbacks

Callbacks let you hook into editor lifecycle events and intercept user actions. Pass them via the callbacks property in your configuration.

Configuration

const config: PexelizeConfig = {
callbacks: {
onReady: () => { /* ... */ },
onLoad: (design) => { /* ... */ },
onChange: (design) => { /* ... */ },
onError: (error) => { /* ... */ },
onModuleSave: async (data) => { /* ... */ },
onPreview: (data) => { /* ... */ },
onContentDialog: async (data) => { /* ... */ },
onHeaderRowClick: () => { /* ... */ },
onFooterRowClick: () => { /* ... */ },
onLockedRowClick: (data) => { /* ... */ },
},
};

Callback Reference

onReady

Type: () => void Blocking: No

Fires when the editor is fully initialized and ready for interaction.

onReady: () => {
console.log('Editor is ready');
enableSaveButton();
},

onLoad

Type: (design: DesignJson) => void Blocking: No

Fires when a design is loaded into the editor (via loadDesign() or the initial design prop).

onLoad: (design) => {
console.log('Design loaded', design.body.rows.length, 'rows');
},

onChange

Type: (design: DesignJson) => void Blocking: No

Fires on every design modification. Use for auto-save or dirty-state tracking.

onChange: (design) => {
setIsDirty(true);
debouncedSave(design);
},
warning

onChange fires frequently. Debounce any expensive operations (network requests, large state updates) to avoid performance issues.

onError

Type: (error: EditorError) => void Blocking: No

Fires when the editor encounters an error.

onError: (error) => {
console.error('Editor error:', error.message, error.code);
reportToSentry(error);
},

onModuleSave

Type: (data: ModuleSaveData) => Promise<void> Blocking: Yes

Fires when the user saves a reusable module (content block). The editor waits for the returned promise to resolve before completing the save.

interface ModuleSaveData {
moduleId: string;
name: string;
design: DesignJson;
thumbnail?: string; // base64 PNG
}
onModuleSave: async (data) => {
await fetch('/api/modules', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
},

onPreview

Type: (data: PreviewData) => void Blocking: No

Fires when the user clicks the preview button. Use to open a custom preview instead of the built-in modal.

interface PreviewData {
html: string;
design: DesignJson;
}
onPreview: (data) => {
openCustomPreviewModal(data.html);
},

onContentDialog

Type: (data: ContentDialogData) => Promise<DesignJson> Blocking: Yes

Fires when the user opens a content dialog (e.g., template picker, saved content browser). Return a DesignJson to insert into the editor.

interface ContentDialogData {
type: 'template' | 'module' | 'content';
}
onContentDialog: async (data) => {
const selectedTemplate = await openTemplatePicker(data.type);
return selectedTemplate.design;
},

onHeaderRowClick

Type: () => void Blocking: No

Fires when the user clicks the locked header row. Use to show a settings dialog for the header.

onHeaderRowClick: () => {
openHeaderSettings();
},

onFooterRowClick

Type: () => void Blocking: No

Fires when the user clicks the locked footer row.

onFooterRowClick: () => {
openFooterSettings();
},

onLockedRowClick

Type: (data: { rowId: string }) => void Blocking: No

Fires when the user clicks any locked row (other than header/footer).

onLockedRowClick: (data) => {
console.log('Locked row clicked:', data.rowId);
openRowSettings(data.rowId);
},

Blocking vs Fire-and-Forget

CallbackBlocking
onReadyNo
onLoadNo
onChangeNo
onErrorNo
onModuleSaveYes — editor waits for the promise
onPreviewNo
onContentDialogYes — editor waits for the returned design
onHeaderRowClickNo
onFooterRowClickNo
onLockedRowClickNo
info

Blocking callbacks must return a Promise. The editor shows a loading indicator while waiting. If the promise rejects, the operation is cancelled and a toast notification is shown.

tip

For AI-related callbacks (onSmartTextGenerate, onImageGenerate, etc.), see the AI section.