Examples
Ready-to-use code examples for common use cases.
Each example below is self-contained. Copy the code, adjust the selector and image values, and you are ready to go. See the Getting Started guide for installation instructions.
Basic Editor
Minimal setup to get started. Just point Imigi at a container element and an image.
<div id="editor" style="width:100%;height:600px;"></div>
<script>
Imigi.init({
selector: '#editor',
image: 'photo.jpg',
});
</script>
The container element must have explicit dimensions. If the container has zero height, the editor canvas will not be visible.
Custom Toolbar
Build your own toolbar with undo, zoom, and custom save button positioned exactly where you want them.
Imigi.init({
selector: '#editor',
image: 'photo.jpg',
ui: {
menubar: {
position: 'top',
items: [
{ type: 'undoWidget', align: 'left' },
{ type: 'zoomWidget', align: 'center' },
{
type: 'button',
icon: '<svg>...</svg>',
label: 'Save',
align: 'right',
action: (editor) => editor.tools.export.save('my-image', 'png', 0.95),
},
],
},
},
});
You can mix built-in widgets (undoWidget, zoomWidget) with custom buttons. Use align to position items to the left, center, or right of the menubar. See the Configuration page for all available widget types.
Overlay / Modal Mode
Open the editor as a fullscreen overlay that appears on demand, perfect for edit-in-place workflows.
// Button to open editor
document.getElementById('edit-btn').addEventListener('click', () => {
editor.open();
});
const editor = await Imigi.init({
selector: '#editor',
image: 'photo.jpg',
ui: {
mode: 'overlay',
visible: false,
},
onOpen: () => {
document.body.style.overflow = 'hidden';
},
onClose: () => {
document.body.style.overflow = '';
},
});
Setting ui.visible to false prevents the editor from rendering immediately. Call editor.open() when you are ready to show it. The onOpen and onClose callbacks let you control page scroll behavior.
Auto-save to localStorage
Automatically persist the editor state so users never lose their work, even after a page refresh.
const editor = await Imigi.init({
selector: '#editor',
image: 'photo.jpg',
});
// Save state on every change
let saveTimer;
editor.on('object:modified', () => {
clearTimeout(saveTimer);
saveTimer = setTimeout(() => {
localStorage.setItem('editorState', editor.getState());
}, 2000);
});
// Restore saved state on load
const saved = localStorage.getItem('editorState');
if (saved) {
editor.setState(saved);
}
The 2-second debounce prevents excessive writes to localStorage during rapid edits. Adjust the timeout value based on your use case. For more on state management, see the Advanced guide.
Upload to Server
Send the edited image to your backend API when the user clicks Save.
Imigi.init({
selector: '#editor',
image: 'photo.jpg',
onSave: async (data, filename, format) => {
// Convert base64 to blob
const response = await fetch(data);
const blob = await response.blob();
const formData = new FormData();
formData.append('image', blob, filename);
const result = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
if (result.ok) {
alert('Image saved successfully!');
}
},
});
Restricted Tools (Only Show Certain Tools)
Limit the editor to only the tools your users need. This example shows only crop, filter, and text.
Imigi.init({
selector: '#editor',
image: 'photo.jpg',
ui: {
nav: {
replaceDefault: true,
items: [
{ name: 'crop', icon: '...', action: 'crop' },
{ name: 'filter', icon: '...', action: 'filter' },
{ name: 'text', icon: '...', action: 'text' },
],
},
},
});
Setting replaceDefault: true removes all built-in nav items and only shows the ones you specify. Without it, your items are appended to the defaults.
Programmatic Image Processing
Use Imigi headlessly -- hide the UI and control everything through the API for batch processing or automated workflows.
const editor = await Imigi.init({
selector: '#editor',
image: 'photo.jpg',
ui: { visible: false },
});
// Apply filter
editor.tools.filter.apply('grayscale');
// Add text
editor.tools.text.add('Watermark', {
fontSize: 24,
fill: 'rgba(255,255,255,0.5)',
fontFamily: 'Arial',
});
// Export
const dataUrl = editor.tools.export.getDataUrl('png', 0.95);
Setting ui.visible to false hides the entire editor interface. This is ideal for server-side-like processing in the browser, such as adding watermarks or applying filters to images before uploading.
Custom Color Presets
Replace or extend the default color picker palette with your brand colors.
Imigi.init({
selector: '#editor',
image: 'photo.jpg',
ui: {
colorPresets: {
replaceDefault: true,
items: [
'#FF0000', '#FF6B00', '#FFD700',
'#00FF00', '#0066FF', '#8B00FF',
'#FFFFFF', '#808080', '#000000',
],
},
},
});
Custom Fonts
Load custom web fonts for the text tool. Imigi handles font loading automatically.
Imigi.init({
selector: '#editor',
image: 'photo.jpg',
tools: {
text: {
items: [
{ family: 'Roboto', src: 'https://fonts.googleapis.com/css2?family=Roboto' },
{ family: 'Playfair Display', src: 'https://fonts.googleapis.com/css2?family=Playfair+Display' },
{ family: 'Fira Code', src: 'https://fonts.googleapis.com/css2?family=Fira+Code' },
],
},
},
});
You can use any Google Fonts URL or a direct link to a .woff2 / .ttf file. Fonts are loaded lazily when the text tool is first opened.
Custom Sticker Categories
Organize your own stickers into named categories. Set replaceDefault to false to keep the built-in stickers alongside your custom ones.
Imigi.init({
selector: '#editor',
image: 'photo.jpg',
tools: {
stickers: {
replaceDefault: false,
items: [
{
name: 'Brand Logos',
items: [
{ src: '/stickers/logo1.png', name: 'Logo 1' },
{ src: '/stickers/logo2.png', name: 'Logo 2' },
],
},
{
name: 'Social Icons',
items: [
{ src: '/stickers/twitter.svg', name: 'Twitter' },
{ src: '/stickers/github.svg', name: 'GitHub' },
],
},
],
},
},
});
Dark Theme with Custom Primary Color
Create a fully custom dark theme with your own brand color palette using CSS custom properties.
Imigi.init({
selector: '#editor',
image: 'photo.jpg',
ui: {
activeTheme: 'custom-dark',
themes: [{
name: 'custom-dark',
isDark: true,
colors: {
'--be-foreground-base': '255 255 255',
'--be-primary': '139 92 246',
'--be-background': '15 10 30',
'--be-background-alt': '25 20 45',
'--be-paper': '35 30 55',
},
}],
},
});
Color values use space-separated RGB format (e.g. '139 92 246') rather than hex. This allows Imigi to apply alpha transparency internally. See the Theming page for all available CSS variables.
Multiple Editors on Same Page
You can run multiple independent editor instances on the same page, each with its own configuration.
const editor1 = await Imigi.init({
selector: '#editor-1',
image: 'photo1.jpg',
});
const editor2 = await Imigi.init({
selector: '#editor-2',
image: 'photo2.jpg',
});
Each editor instance consumes its own memory and canvas resources. Running many editors simultaneously on a single page may impact performance on lower-end devices.
Crop to Specific Size (e.g., Profile Picture)
Force the crop tool to open by default with a locked aspect ratio -- ideal for profile pictures, thumbnails, or social media images.
Imigi.init({
selector: '#editor',
image: 'photo.jpg',
ui: {
defaultTool: 'crop',
},
tools: {
crop: {
defaultRatio: '1:1',
presets: [
{ ratio: '1:1', name: 'Square' },
],
},
},
onSave: (data) => {
document.getElementById('profile-pic').src = data;
},
});
Use defaultTool: 'crop' to open the crop panel automatically when the editor loads. Combine with a single preset to enforce a specific aspect ratio. See the Tools Guide for all crop presets.
Watermark on Export
Automatically stamp a text watermark onto every exported image.
Imigi.init({
selector: '#editor',
image: 'photo.jpg',
watermarkText: '© My Company 2024',
});
The watermark is applied at export time and does not appear on the editing canvas. For image-based watermarks, see the Advanced guide.
Profile Picture Editor
Build an avatar cropper that opens as an overlay, locks the crop ratio to 1:1, and uploads the result.
let imigi;
Imigi.init({
selector: '#avatar-editor',
ui: {
mode: 'overlay',
visible: false,
defaultTool: 'crop',
showExportPanel: false,
menubar: { position: '' },
nav: { replaceDefault: true, items: [] },
},
tools: {
crop: {
defaultRatio: '1:1',
allowCustomRatio: false,
presets: [{ ratio: '1:1', name: 'Square' }],
},
export: { defaultFormat: 'png', defaultQuality: 0.9 },
},
onSave: async (data) => {
await uploadProfilePicture(data);
},
}).then(editor => { imigi = editor; });
// Open for editing
async function editProfilePicture(imageUrl) {
await imigi.open({ image: imageUrl });
}
With Size Limit
Enforce a maximum output size so profile pictures never exceed 500 × 500 pixels.
Imigi.init({
selector: '#avatar-editor',
tools: {
crop: { defaultRatio: '1:1', allowCustomRatio: false },
resize: { maxWidth: 500, maxHeight: 500 },
},
onSave: async (data) => {
const img = new Image();
img.onload = async () => {
if (img.width > 500 || img.height > 500) {
const canvas = document.createElement('canvas');
canvas.width = 500;
canvas.height = 500;
canvas.getContext('2d').drawImage(img, 0, 0, 500, 500);
data = canvas.toDataURL('image/png');
}
await uploadProfilePicture(data);
};
img.src = data;
},
});
Screenshot Annotation
Configure a streamlined annotation editor with only drawing, shapes, text, and redact tools -- ideal for annotating screenshots and bug reports.
Imigi.init({
selector: '#annotation-editor',
ui: {
activeTheme: 'light',
nav: {
position: 'left',
replaceDefault: true,
items: [
{ name: 'draw', icon: PenIcon, action: 'draw' },
{ name: 'shapes', icon: ShapesIcon, action: 'shapes' },
{ name: 'text', icon: TextIcon, action: 'text' },
{ name: 'redact', icon: BlurIcon, action: 'redact' },
],
},
},
tools: {
draw: {
brushSizes: [2, 4, 6, 8],
brushTypes: ['PencilBrush'],
},
shapes: {
items: [
{ name: 'arrow', type: 'Path', options: { /* arrow path data */ } },
{ name: 'rectangle', type: 'Rect', options: { fill: 'transparent', stroke: '#ff0000', strokeWidth: 2 } },
{ name: 'circle', type: 'Circle', options: { fill: 'transparent', stroke: '#ff0000', strokeWidth: 2 } },
],
},
redact: { defaultMode: 'blur', defaultBlurAmount: 20 },
},
objectDefaults: {
shape: { fill: 'transparent', stroke: '#ff0000' },
},
});
With Clipboard Paste
Allow users to paste screenshots directly from their clipboard into the editor.
document.addEventListener('paste', async (e) => {
const items = e.clipboardData?.items;
if (!items) return;
for (const item of items) {
if (item.type.startsWith('image/')) {
const blob = item.getAsFile();
const dataUrl = await blobToDataURL(blob);
await imigi.resetEditor({ image: dataUrl });
break;
}
}
});
function blobToDataURL(blob) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.readAsDataURL(blob);
});
}
Social Media Templates
Instagram Post Editor
Pre-configured for Instagram post dimensions (1080 × 1080) with social-media-friendly crop presets and custom fonts.
Imigi.init({
selector: '#instagram-editor',
blankCanvasSize: { width: 1080, height: 1080 },
tools: {
crop: {
defaultRatio: '1:1',
allowCustomRatio: false,
presets: [
{ ratio: '1:1', name: 'Post' },
{ ratio: '4:5', name: 'Portrait' },
{ ratio: '1.91:1', name: 'Landscape' },
],
},
text: {
items: [
{ family: 'Montserrat', src: 'fonts/montserrat.woff2' },
{ family: 'Playfair Display', src: 'fonts/playfair.woff2' },
{ family: 'Open Sans', src: 'fonts/opensans.woff2' },
],
},
},
ui: {
colorPresets: {
items: [
'#ffffff', '#000000', '#e1306c', '#f77737',
'#fcaf45', '#833ab4', '#405de6', '#5851db',
],
},
},
});
Story Templates
Story-sized canvas (1080 × 1920) with a template loader in the menubar.
Imigi.init({
selector: '#story-editor',
blankCanvasSize: { width: 1080, height: 1920 },
ui: {
menubar: {
items: [{
type: 'button',
label: 'Templates',
align: 'left',
menuItems: [
{ label: 'Quote', action: (editor) => loadTemplate(editor, 'quote') },
{ label: 'Announcement', action: (editor) => loadTemplate(editor, 'announcement') },
{ label: 'Question', action: (editor) => loadTemplate(editor, 'question') },
],
}],
},
},
});
async function loadTemplate(editor, templateName) {
const template = await fetch(`/templates/${templateName}.json`).then(r => r.json());
await editor.setState(template);
}
Server Integration
Save to Cloud Storage (Presigned URLs)
Upload edited images directly to S3 or Google Cloud Storage using presigned URLs.
Imigi.init({
selector: '#editor',
onSave: async (data, filename, format) => {
// Get presigned URL from your backend
const { uploadUrl, fileUrl } = await fetch('/api/get-upload-url', {
method: 'POST',
body: JSON.stringify({ filename, contentType: `image/${format}` }),
}).then(r => r.json());
// Convert to blob
const response = await fetch(data);
const blob = await response.blob();
// Upload to S3 / GCS
await fetch(uploadUrl, {
method: 'PUT',
body: blob,
headers: { 'Content-Type': `image/${format}` },
});
console.log('Uploaded to:', fileUrl);
},
});
Auto-Save Draft to Server
Automatically save the editor state to your backend on every change, with a debounce to avoid excessive requests.
let saveTimeout;
let lastState = null;
Imigi.init({
selector: '#editor',
onLoad: async (editor) => {
// Load draft if one exists
const draft = await fetch('/api/drafts/current').then(r => r.json());
if (draft?.state) {
await editor.setState(draft.state);
}
// Setup auto-save on changes
editor.on('object:modified', scheduleAutoSave);
editor.on('object:added', scheduleAutoSave);
editor.on('object:removed', scheduleAutoSave);
},
});
function scheduleAutoSave() {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(autoSave, 3000);
}
async function autoSave() {
const state = imigi.getState();
if (state === lastState) return;
await fetch('/api/drafts/save', {
method: 'POST',
body: state,
});
lastState = state;
console.log('Draft saved');
}
Collaborative Editing (Basic)
Sync object changes between multiple users in real-time using WebSocket.
const ws = new WebSocket('wss://api.example.com/collab/room123');
Imigi.init({
selector: '#editor',
onLoad: (editor) => {
// Send changes to other users
editor.on('object:modified', (e) => {
const objectData = e.target.toJSON();
ws.send(JSON.stringify({
type: 'object:modified',
id: e.target.id,
data: objectData,
}));
});
},
});
// Receive changes from other users
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'object:modified') {
const obj = imigi.fabric.getObjects().find(o => o.id === message.id);
if (obj) {
obj.set(message.data);
imigi.fabric.renderAll();
}
}
};
This is a minimal example for illustration. Production collaborative editing requires conflict resolution, operation ordering, and handling object creation/deletion across clients.
Quick Recipes
Rotate Image 90°
function rotate90() {
imigi.openTool('crop');
const state = imigi.state;
state.crop.setRotation(state.crop.rotation + 90);
}
Add Logo Overlay
async function addLogo(logoUrl) {
await imigi.tools.import.addImageFromUrl(logoUrl);
const logo = imigi.fabric.getActiveObject();
logo.set({
left: 20,
top: 20,
scaleX: 0.2,
scaleY: 0.2,
originX: 'left',
originY: 'top',
});
imigi.fabric.renderAll();
}
Export Multiple Sizes
async function exportMultipleSizes() {
const sizes = [
{ width: 1200, height: 630, name: 'og' },
{ width: 800, height: 800, name: 'square' },
{ width: 400, height: 400, name: 'thumbnail' },
];
const original = imigi.getState();
for (const size of sizes) {
const dataUrl = await imigi.tools.export.getDataUrl('png', 0.9);
// Resize dataUrl to size.width x size.height and download
}
// Restore original state
await imigi.setState(original);
}
Upload, Edit, Download
A complete flow: user uploads a file, edits it, and downloads the result.
<input type="file" id="upload" accept="image/*">
<button id="download" disabled>Download</button>
<div id="editor" style="width:100%;height:600px"></div>
<script>
let imigi;
Imigi.init({
selector: '#editor',
onLoad: (editor) => {
imigi = editor;
document.getElementById('download').disabled = false;
},
});
// Handle upload
document.getElementById('upload').onchange = async (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async (event) => {
await imigi.resetEditor({ image: event.target.result });
};
reader.readAsDataURL(file);
};
// Handle download
document.getElementById('download').onclick = () => {
imigi.tools.export.save('edited-image', 'png', 0.9);
};
</script>