Advanced

AJAX Persistence

Configure the api option to load, save, and delete annotations via HTTP endpoints. Each field accepts a URL string (default fetch behavior) or a custom function for full control over the request.

URL-based configuration
JavaScript
AnnotateImage.annotate(img, {
  editable: true,
  api: {
    load: '/api/annotations/load',
    save: '/api/annotations/save',
    delete: '/api/annotations/delete'
  },
  onError: function(ctx) {
    console.error('Failed to ' + ctx.type + ':', ctx.error.message);
  }
});

When URLs are provided, the library uses the following HTTP behavior:

  • load — sends a GET request and expects an AnnotationNote[] JSON response
  • save — sends a POST with a JSON body and expects a { annotation_id?: string } response
  • delete — sends a POST with a JSON body
Function-based configuration

For full control over the request lifecycle, provide functions instead of URLs. Each function must return a Promise.

JavaScript
AnnotateImage.annotate(img, {
  editable: true,
  api: {
    load: function() {
      return fetch('/my-api/notes')
        .then(function(r) { return r.json(); });
    },
    save: function(note) {
      return fetch('/my-api/notes', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(note)
      }).then(function(r) { return r.json(); });
    },
    delete: function(note) {
      return fetch('/my-api/notes/' + note.id, {
        method: 'DELETE'
      }).then(function() {});
    }
  }
});

Custom Labels

Override the default UI button text for internationalization or to use icon-only controls. Any field you omit keeps its default value.

Default labels
Field Default
addNote "Add Note"
save "OK"
delete "Delete"
cancel "Cancel"
placeholder ""
German example
JavaScript
AnnotateImage.annotate(img, {
  editable: true,
  notes: [],
  labels: {
    addNote: 'Notiz hinzufügen',
    save: 'Speichern',
    delete: 'Löschen',
    cancel: 'Abbrechen',
    placeholder: 'Notiz eingeben...'
  }
});
Icon-only example
JavaScript
AnnotateImage.annotate(img, {
  editable: true,
  notes: [],
  labels: {
    addNote: '',
    save: '',
    delete: '',
    cancel: ''
  }
});
Live demo
Default (English)
Starry Night
German
Starry Night
Icon-only
Starry Night

Error Handling

The onError callback receives an AnnotateErrorContext object whenever an API operation fails. If no callback is provided, errors are logged to the console.

TypeScript
interface AnnotateErrorContext {
  type: 'load' | 'save' | 'delete';
  error: Error;
  note?: AnnotationNote;
}
JavaScript
AnnotateImage.annotate(img, {
  editable: true,
  notes: [],
  api: { load: '/api/notes' },
  onError: function(ctx) {
    if (ctx.type === 'load') {
      alert('Failed to load annotations: ' + ctx.error.message);
    } else {
      console.error(ctx.type + ' failed:', ctx.error);
      if (ctx.note) {
        console.error('Affected note:', ctx.note.id);
      }
    }
  }
});

Accessibility

The library includes built-in keyboard navigation support for all interactive elements.

Key Action
Tab Navigate between annotation areas and controls
Enter / Space Activate buttons and open editable annotations
Escape Cancel editing

Additional accessibility features:

  • Annotation areas and buttons have role="button" and tabindex attributes
  • Focus is managed automatically when entering and exiting edit mode
  • Visible focus styles are applied to all interactive elements
  • Focus trapping is enforced during annotation editing to prevent tabbing out of the editor

Editability

Editability is controlled at two levels, giving fine-grained control over what users can modify.

  1. Global editable option — controls whether the “Add Note” button appears and whether new annotations can be created.
  2. Per-note editable field — controls whether individual existing notes can be clicked to edit.
JavaScript
AnnotateImage.annotate(img, {
  // Global: allow adding new notes
  editable: true,
  notes: [
    // This note CAN be edited by clicking
    { id: "1", top: 50, left: 50, width: 100, height: 100,
      text: "Editable note", editable: true },
    // This note CANNOT be edited (read-only)
    { id: "2", top: 200, left: 200, width: 100, height: 100,
      text: "Read-only note", editable: false }
  ]
});

Use the setEditable() instance method to toggle editing at runtime:

JavaScript
// Disable editing at runtime
instance.setEditable(false);

// Re-enable
instance.setEditable(true);