Advanced Usage
Deep-dive into advanced patterns, Fabric.js access, custom extensions, and performance optimization.
State Management
Imigi allows you to save and restore the complete editor state as JSON. This is useful for implementing auto-save, persisting edits across sessions, or sending the state to a server for later retrieval.
Saving State
const state = editor.getState();
// state is a JSON string containing all objects, filters, etc.
localStorage.setItem('editorState', state);
// or send to server
await fetch('/api/save-state', {
method: 'POST',
body: state,
headers: { 'Content-Type': 'application/json' },
});
Restoring State
// From string
const savedState = localStorage.getItem('editorState');
if (savedState) {
await editor.setState(savedState);
}
// From URL
await editor.setStateFromUrl('/api/get-state/123');
Custom Properties in State
By default, getState() serializes all standard Fabric.js properties. If you have added custom properties to objects, you can include them in the state by passing an array of property names.
// Include custom properties when saving
const state = editor.getState(['myCustomProp', 'anotherProp']);
The state string can be stored in any persistence layer: localStorage, a database, or a file system. It contains everything needed to fully reconstruct the editor contents.
Accessing Fabric.js Canvas
Imigi exposes the underlying Fabric.js canvas instance through the editor.fabric property. This gives you full access to the Fabric.js API for advanced manipulation beyond what the built-in tools provide.
const canvas = editor.fabric;
// Get all objects
const objects = canvas.getObjects();
// Add a custom Fabric.js object
const rect = new fabric.Rect({
left: 100,
top: 100,
width: 200,
height: 150,
fill: '#3b82f6',
opacity: 0.8,
});
canvas.add(rect);
canvas.renderAll();
// Listen to Fabric.js events directly
canvas.on('object:modified', (e) => {
console.log('Object modified:', e.target);
});
When adding objects directly through Fabric.js, they will not be tracked by the Imigi undo/redo history unless you manually trigger a history snapshot. Use the built-in tools API when possible for full integration with the editor's history system.
Custom Filters
You can create and register custom image filters using a 5x4 color matrix. This allows you to define unique visual effects beyond the built-in filter presets.
// Create a custom filter
const customFilter = {
name: 'My Custom Filter',
matrix: [
0.393, 0.769, 0.189, 0, 0,
0.349, 0.686, 0.168, 0, 0,
0.272, 0.534, 0.131, 0, 0,
0, 0, 0, 1, 0
],
};
// Register the filter
editor.tools.filter.addCustom('custom-warm', customFilter);
// Apply it
editor.tools.filter.apply('custom-warm');
The matrix array is a 5x4 color transformation matrix applied via WebGL. Each row represents the output for red, green, blue, and alpha channels respectively. The fifth column is the offset value for each channel.
Custom Sticker Categories
Add your own sticker categories with custom images. You can either extend the default stickers or replace them entirely.
Imigi.init({
selector: '#editor',
tools: {
stickers: {
replaceDefault: false,
items: [
{
name: 'Brand Assets',
items: [
{ src: '/assets/logo.png', name: 'Company Logo' },
{ src: '/assets/badge.svg', name: 'Badge' },
{ src: '/assets/watermark.png', name: 'Watermark' },
],
},
],
},
},
});
Set replaceDefault: true to completely remove the built-in stickers and only show your custom categories.
Custom Fonts
Load custom fonts (including Google Fonts) into the text tool so users can pick from your curated font list.
// Load Google Fonts
Imigi.init({
selector: '#editor',
tools: {
text: {
items: [
{ family: 'Inter', src: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700' },
{ family: 'Playfair Display', src: 'https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700' },
{ family: 'JetBrains Mono', src: 'https://fonts.googleapis.com/css2?family=JetBrains+Mono' },
],
},
},
});
Fonts are loaded lazily when the text tool is opened. Ensure the font src URL is accessible from the user's browser. Self-hosted font files also work -- just point to your CSS file that declares the @font-face rules.
Custom Frames
Add custom decorative frames to the frame tool. Frames are PNG or SVG images that wrap around the edited image.
Imigi.init({
selector: '#editor',
tools: {
frame: {
replaceDefault: false,
items: [
{
name: 'Polaroid',
src: '/frames/polaroid.png',
// Frame definition properties
},
],
},
},
});
Like stickers, you can set replaceDefault: false to keep the built-in frames and add yours alongside them, or set it to true to replace them entirely.
Performance Optimization
Imigi uses WebGL for rendering filters and effects. On lower-end devices or with very large images, you may need to tune performance settings.
Texture Size
The textureSize option controls the maximum WebGL texture dimension. Reducing it lowers GPU memory usage at the cost of some rendering quality.
// Reduce for better performance on low-end devices
Imigi.init({
selector: '#editor',
textureSize: 2048, // Default is 4096
});
Large Images
When working with high-resolution images, keep the following tips in mind:
- Use
textureSizeto limit WebGL memory consumption. - Consider resizing images before loading them into the editor.
- Use JPEG format for photos -- it produces smaller files than PNG.
- Disable filters that are not needed to reduce rendering passes.
- Use the zoom tool's resize mode for better performance when panning large canvases.
Memory Management
Always clean up the editor instance when you are done to release canvas memory, event listeners, and WebGL contexts.
// Clean up when done
editor.close();
// Set reference to null
editor = null;
Failing to call editor.close() when discarding the editor can lead to memory leaks, especially in single-page applications where the editor is mounted and unmounted repeatedly.
Security Considerations
CORS / Cross-Origin Images
When loading images from a different domain, you must enable CORS to allow the canvas to export pixel data.
Imigi.init({
selector: '#editor',
image: 'https://other-domain.com/photo.jpg',
crossOrigin: true, // Enable CORS
});
The server hosting the image must send proper CORS headers (e.g. Access-Control-Allow-Origin: *). Setting crossOrigin: true on the client side alone is not sufficient -- the server must cooperate.
Tainted Canvas
If a cross-origin image is drawn onto the canvas without CORS, the browser "taints" the canvas for security reasons. A tainted canvas cannot be exported -- calling toDataURL() or getImageData() will throw a SecurityError.
The crossOrigin option tells the browser to request the image with CORS credentials. As long as the server responds with the correct headers, the canvas remains clean and exportable.
If you see a "Tainted canvases may not be exported" error in the console, it means the image server is not sending CORS headers. Either configure the server or use a proxy (see below).
Proxy for CORS
If you cannot configure CORS headers on the image server, route requests through your own backend proxy.
// Use a proxy to bypass CORS restrictions
const proxyUrl = '/api/proxy?url=' + encodeURIComponent(imageUrl);
Imigi.init({
selector: '#editor',
image: proxyUrl,
});
When using a proxy, validate and sanitize the incoming URL on the server side to prevent open-redirect or SSRF attacks. Only allow proxying to trusted image domains.
Batch Processing
You can use Imigi programmatically (with a hidden UI) to process multiple images in sequence. This is useful for applying consistent edits across a set of images.
async function processImages(urls) {
const editor = await Imigi.init({
selector: '#editor',
ui: { visible: false },
});
const results = [];
for (const url of urls) {
await editor.resetEditor({ image: url });
editor.tools.filter.apply('grayscale');
const dataUrl = editor.tools.export.getDataUrl('jpeg', 0.8);
results.push(dataUrl);
}
editor.close();
return results;
}
Setting ui.visible to false hides the editor interface. The canvas still renders in the background, so all tools and export methods remain functional.
Plugin Pattern
Create reusable editor presets for common use cases. For example, a profile picture editor that always starts with a square crop and limits the output size.
// Create reusable editor presets
function createProfileEditor(selector, options = {}) {
return Imigi.init({
selector,
ui: {
defaultTool: 'crop',
nav: {
replaceDefault: true,
items: [
{ name: 'crop', action: 'crop' },
{ name: 'filter', action: 'filter' },
{ name: 'finetune', action: 'finetune' },
],
},
},
tools: {
crop: {
defaultRatio: '1:1',
presets: [{ ratio: '1:1', name: 'Square' }],
},
resize: {
maxWidth: 500,
maxHeight: 500,
},
},
...options,
});
}
// Use it
const editor = await createProfileEditor('#editor', {
image: 'avatar.jpg',
onSave: (data) => uploadAvatar(data),
});
This pattern lets you encapsulate editor configurations as composable factory functions, making it easy to maintain consistent behavior across your application.
Server-Side Rendering (SSR) Considerations
Imigi requires browser APIs (window, document, canvas) that are not available in server-side environments like Node.js. When using SSR frameworks such as Next.js, Nuxt, or Remix, you must ensure Imigi only loads on the client.
Check for Window
if (typeof window !== 'undefined') {
const { Imigi } = await import('imigi');
await Imigi.init({ selector: '#editor', image: 'photo.jpg' });
}
Dynamic Imports (Next.js)
import dynamic from 'next/dynamic';
const EditorComponent = dynamic(
() => import('../components/Editor'),
{ ssr: false }
);
For detailed framework-specific examples, see the Integrations page which covers Next.js, Nuxt, and other SSR frameworks.
Debugging
When troubleshooting issues, you can inspect the editor's internal state and canvas objects using these helper methods.
// Access internal state
console.log(editor.state);
// Check dirty status
console.log('Has changes:', editor.isDirty());
// List all objects
console.log('Objects:', editor.tools.objects.getAll());
// Current zoom level
console.log('Zoom:', editor.tools.zoom.getLevel());
editor.isDirty() returns true if the editor has unsaved changes since the last save or load. This is useful for prompting users before they navigate away from a page with unsaved work.
Avoid relying on internal state properties in production code. The internal structure may change between versions. Use the documented API methods instead.