Ctrl+K
v3.0.5

Examples

Ready-to-use code examples for common use cases.

Tip

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.

HTML
<div id="editor" style="width:100%;height:600px;"></div>
<script>
  Imigi.init({
    selector: '#editor',
    image: 'photo.jpg',
  });
</script>
Tip

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.

JavaScript
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),
        },
      ],
    },
  },
});
Tip

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.

JavaScript
// 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 = '';
  },
});
Tip

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.

JavaScript
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);
}
Tip

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.

JavaScript
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.

JavaScript
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' },
      ],
    },
  },
});
Tip

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.

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

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.

JavaScript
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.

JavaScript
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' },
      ],
    },
  },
});
Tip

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.

JavaScript
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.

JavaScript
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',
      },
    }],
  },
});
Tip

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.

JavaScript
const editor1 = await Imigi.init({
  selector: '#editor-1',
  image: 'photo1.jpg',
});

const editor2 = await Imigi.init({
  selector: '#editor-2',
  image: 'photo2.jpg',
});
Warning

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.

JavaScript
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;
  },
});
Tip

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.

JavaScript
Imigi.init({
  selector: '#editor',
  image: 'photo.jpg',
  watermarkText: '© My Company 2024',
});
Tip

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.

JavaScript
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.

JavaScript
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.

JavaScript
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.

JavaScript
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.

JavaScript
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.

JavaScript
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.

JavaScript
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.

JavaScript
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.

JavaScript
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();
    }
  }
};
Warning

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°

JavaScript
function rotate90() {
  imigi.openTool('crop');
  const state = imigi.state;
  state.crop.setRotation(state.crop.rotation + 90);
}

Add Logo Overlay

JavaScript
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

JavaScript
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.

HTML + JavaScript
<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>
On This Page
Basic Editor Custom Toolbar Overlay / Modal Mode Auto-save to localStorage Upload to Server Restricted Tools Programmatic Processing Custom Color Presets Custom Fonts Custom Sticker Categories Dark Theme Custom Primary Multiple Editors Crop to Specific Size Watermark on Export Profile Picture Editor Screenshot Annotation Social Media Templates Server Integration Quick Recipes Upload, Edit, Download