Events
LibreDraw emits events during drawing and editing operations. Subscribe and unsubscribe using the on and off methods.
Event Map
interface LibreDrawEventMap {
create: CreateEvent;
update: UpdateEvent;
delete: DeleteEvent;
split: SplitEvent;
splitfailed: SplitFailedEvent;
setback: SetbackEvent;
setbackfailed: SetbackFailedEvent;
union: UnionEvent;
unionfailed: UnionFailedEvent;
rotate: RotateEvent;
selectionchange: SelectionChangeEvent;
modechange: ModeChangeEvent;
draftchange: DraftChangeEvent;
}Event origin
Every payload carries an origin telling you who caused the change:
type EventOrigin = 'api' | 'user';| Value | Meaning |
|---|---|
'api' | A public LibreDraw method was running: addFeatures(), deleteFeature(), setMode(), selectFeature(), clearSelection(), finishDrawing(), cancelDrawing(), undo(), redo(), … and anything they trigger. |
'user' | Pointer or touch input on the map, a toolbar button (including its Undo / Redo / Delete buttons), or a keyboard shortcut. |
Use it to keep a sync loop from reacting to its own changes:
draw.on('delete', (e) => {
if (e.origin === 'api') return; // we did this ourselves, no need to echo it
api.deleteParcel(e.feature.id);
});The value is decided by the call path, not by the kind of change: the same delete is 'api' from deleteFeature() and 'user' from the Delete key. A public method invoked from inside a 'user' listener stamps only its own events; the surrounding user-originated events keep 'user'.
create
Emitted when a new feature is created. In draw-point mode this happens on each click/tap. In draw-line mode it happens when the line is finalized. In draw-polygon mode it happens when the polygon is completed. In draw-rectangle mode it happens on the second corner click.
It also fires once per feature from addFeatures(), and from history: redoing a create emits it again, and undoing a delete, split, setback, or union emits create for every feature that comes back.
Payload: CreateEvent
interface CreateEvent {
origin: EventOrigin;
feature: LibreDrawFeature;
}| Property | Type | Description |
|---|---|---|
origin | EventOrigin | Who caused the change: 'api' or 'user' |
feature | LibreDrawFeature | The newly created Point, LineString, or Polygon feature |
Example
draw.on('create', (e) => {
console.log('New feature:', e.feature.id, e.feature.geometry.type);
if (e.feature.geometry.type === 'Polygon') {
console.log('Vertices:', e.feature.geometry.coordinates[0].length - 1);
}
});update
Emitted when an existing feature is modified. This includes vertex edits and dragging of polygons and lines, and point dragging in select mode. Undo and redo of any update (including rotations, see rotate) emit it as well, with feature / oldFeature describing the direction of the change.
Payload: UpdateEvent
interface UpdateEvent {
origin: EventOrigin;
feature: LibreDrawFeature;
oldFeature: LibreDrawFeature;
}| Property | Type | Description |
|---|---|---|
origin | EventOrigin | Who caused the change: 'api' or 'user' |
feature | LibreDrawFeature | The updated Point, LineString, or Polygon feature (new state) |
oldFeature | LibreDrawFeature | The feature before the update (previous state) |
Example
draw.on('update', (e) => {
console.log('Feature updated:', e.feature.id, e.feature.geometry.type);
console.log('Old coordinates:', e.oldFeature.geometry.coordinates);
console.log('New coordinates:', e.feature.geometry.coordinates);
});delete
Emitted when a feature is deleted (via toolbar button, Delete key, or deleteFeature() API). History emits it too: undoing a create (or a batch from addFeatures(), children in reverse order), undoing a split (two deletes), setback, or union, and redoing a delete or a split (the original polygon is deleted before split fires again).
Payload: DeleteEvent
interface DeleteEvent {
origin: EventOrigin;
feature: LibreDrawFeature;
}| Property | Type | Description |
|---|---|---|
origin | EventOrigin | Who caused the change: 'api' or 'user' |
feature | LibreDrawFeature | The deleted Point, LineString, or Polygon feature |
Example
draw.on('delete', (e) => {
console.log('Feature deleted:', e.feature.id, e.feature.geometry.type);
});split
Emitted when a polygon or line is successfully split into two, in split mode or through split() (origin tells which). Undoing a split emits a delete for each half and a create for the original; redoing it emits a delete for the original followed by split again.
Payload: SplitEvent
interface SplitEvent {
origin: EventOrigin;
originalFeature: LibreDrawFeature;
features: [LibreDrawFeature, LibreDrawFeature];
}| Property | Type | Description |
|---|---|---|
origin | EventOrigin | Who caused the change: 'api' or 'user' |
originalFeature | LibreDrawFeature | The source polygon before split |
features | [LibreDrawFeature, LibreDrawFeature] | The two resulting polygons |
Example
draw.on('split', (e) => {
console.log('Split source:', e.originalFeature.id);
console.log(
'Result polygons:',
e.features.map((f) => f.id)
);
});splitfailed
Emitted when a split fails for a geometric reason, in split mode or through split(). Argument errors of the API ('not-found', 'not-splittable') are only returned, not emitted.
Payload: SplitFailedEvent
type SplitFailReason =
| 'same-points'
| 'insufficient-vertices'
| 'has-holes'
| 'invalid-intersection-count'
| 'self-intersecting-result';
interface SplitFailedEvent {
origin: EventOrigin;
reason: SplitFailReason;
featureId: string;
}| Property | Type | Description |
|---|---|---|
origin | EventOrigin | Who caused the change: 'api' or 'user' |
reason | SplitFailReason | Reason of split failure |
featureId | string | Target feature ID |
Example
draw.on('splitfailed', (e) => {
console.warn('Split failed:', e.reason, e.featureId);
});setback
Emitted when a setback succeeds, in setback mode or through setback(). Undoing it emits a delete for the result and a create for the original; redoing it emits setback again.
Payload: SetbackEvent
interface SetbackEvent {
origin: EventOrigin;
originalFeature: LibreDrawFeature;
feature: LibreDrawFeature;
edgeIndex: number;
distance: number;
}| Property | Type | Description |
|---|---|---|
origin | EventOrigin | Who caused the change: 'api' or 'user' |
originalFeature | LibreDrawFeature | The source polygon before setback |
feature | LibreDrawFeature | Result polygon after setback |
edgeIndex | number | Applied edge index |
distance | number | Setback distance in meters |
Example
draw.on('setback', (e) => {
console.log('Setback applied:', e.originalFeature.id, '->', e.feature.id);
console.log('Edge:', e.edgeIndex, 'Distance(m):', e.distance);
});setbackfailed
Emitted when a setback fails for a geometric reason, in setback mode or through setback(). Argument errors of the API ('not-found', 'not-polygon', 'invalid-edge', 'invalid-distance') are only returned, not emitted.
Payload: SetbackFailedEvent
type SetbackFailReason = 'has-holes' | 'invalid-split';
interface SetbackFailedEvent {
origin: EventOrigin;
reason: SetbackFailReason;
featureId: string;
}| Property | Type | Description |
|---|---|---|
origin | EventOrigin | Who caused the change: 'api' or 'user' |
reason | SetbackFailReason | Reason of setback failure |
featureId | string | Target feature ID |
Example
draw.on('setbackfailed', (e) => {
console.warn('Setback failed:', e.reason, e.featureId);
});union
Emitted when two polygons are merged into one, in union mode or through union(). The merge is one history step: undoing it emits a delete for the merged polygon and a create for each source polygon, and redoing it emits union again.
Payload: UnionEvent
interface UnionEvent {
origin: EventOrigin;
originalFeatures: [LibreDrawFeature, LibreDrawFeature];
feature: LibreDrawFeature;
}| Property | Type | Description |
|---|---|---|
origin | EventOrigin | Who caused the change: 'api' or 'user' |
originalFeatures | [LibreDrawFeature, LibreDrawFeature] | The two source polygons in selection order |
feature | LibreDrawFeature | The merged polygon. It has a new id and the properties of the first source |
Example
draw.on('union', (e) => {
console.log(
'Merged:',
e.originalFeatures.map((f) => f.id),
'->',
e.feature.id
);
});unionfailed
Emitted when a union fails for a geometric reason, in union mode or through union(). The store is left untouched; in the mode the first polygon stays selected so another partner can be picked. Argument errors of the API ('not-found', 'unsupported-count') are only returned, not emitted.
Payload: UnionFailedEvent
type UnionFailReason = 'not-polygon' | 'has-holes' | 'disjoint' | 'invalid-result';
interface UnionFailedEvent {
origin: EventOrigin;
reason: UnionFailReason;
featureIds: [string, string];
}| Property | Type | Description |
|---|---|---|
origin | EventOrigin | Who caused the change: 'api' or 'user' |
reason | UnionFailReason | Reason of union failure |
featureIds | [string, string] | IDs of the two target polygons in selection order |
| Reason | Meaning |
|---|---|
'disjoint' | The polygons do not touch, so the result would be a MultiPolygon |
'has-holes' | A target has a hole, or the merged outline would enclose a hole |
'not-polygon' | A target is not a Polygon |
'invalid-result' | The geometry engine could not produce a usable polygon |
Example
draw.on('unionfailed', (e) => {
console.warn('Union failed:', e.reason, e.featureIds);
});rotate
Emitted when a rotation is committed in rotate mode, either by releasing a drag or by executing the angle input. Each commit is one history step.
Undo and redo of a rotation emit update events rather than rotate, because the history stores a rotation as a plain feature replacement.
Payload: RotateEvent
interface RotateEvent {
origin: EventOrigin;
originalFeature: LibreDrawFeature;
feature: LibreDrawFeature;
angle: number;
}| Property | Type | Description |
|---|---|---|
origin | EventOrigin | Who caused the change: 'api' or 'user' |
originalFeature | LibreDrawFeature | The feature before this rotation |
feature | LibreDrawFeature | The feature after this rotation |
angle | number | Angle applied by this step in degrees, positive for clockwise |
Example
draw.on('rotate', (e) => {
console.log(`${e.originalFeature.id} rotated by ${e.angle}°`);
});selectionchange
Emitted when the set of selected features changes.
Payload: SelectionChangeEvent
interface SelectionChangeEvent {
origin: EventOrigin;
selectedIds: string[];
}| Property | Type | Description |
|---|---|---|
origin | EventOrigin | Who caused the change: 'api' or 'user' |
selectedIds | string[] | Array of currently selected feature IDs. Empty array when nothing is selected. |
Example
draw.on('selectionchange', (e) => {
if (e.selectedIds.length > 0) {
console.log('Selected:', e.selectedIds);
// Enable delete button in your UI
deleteButton.disabled = false;
} else {
console.log('Selection cleared');
deleteButton.disabled = true;
}
});modechange
Emitted when the active mode changes.
Payload: ModeChangeEvent
interface ModeChangeEvent {
origin: EventOrigin;
mode: ModeName;
previousMode: ModeName;
}| Property | Type | Description |
|---|---|---|
origin | EventOrigin | Who caused the change: 'api' or 'user' |
mode | ModeName | The new active mode ('idle', 'draw-point', 'draw-line', 'draw-polygon', 'draw-rectangle', 'select', 'split', 'setback', 'union', or 'rotate') |
previousMode | ModeName | The previous mode |
Example
draw.on('modechange', (e) => {
console.log(`${e.previousMode} → ${e.mode}`);
// Update your UI based on mode
drawButton.classList.toggle('active', e.mode === 'draw-polygon');
selectButton.classList.toggle('active', e.mode === 'select');
splitButton.classList.toggle('active', e.mode === 'split');
setbackButton.classList.toggle('active', e.mode === 'setback');
});draftchange
Emitted whenever the in-progress draft of a drawing mode ('draw-polygon', 'draw-line', or 'draw-rectangle') changes.
Fires when:
- A vertex is added by a click or tap (pointer down and up without dragging)
- A vertex is removed (long-press)
- The draft is finalized — via a click or tap on the first (polygon only) or last draft vertex, or
finishDrawing()— withvertexCount: 0 - The draft is discarded — via Escape or
cancelDrawing()— withvertexCount: 0 - The active mode transitions away from a drawing mode (deactivation), with
vertexCount: 0
In 'draw-rectangle' mode the draft holds at most the first corner: vertexCount is 1 after the first click and returns to 0 when the second corner creates the polygon or the corner is discarded.
Payload: DraftChangeEvent
interface DraftChangeEvent {
origin: EventOrigin;
vertexCount: number;
}| Property | Type | Description |
|---|---|---|
origin | EventOrigin | Who caused the change: 'api' or 'user' |
vertexCount | number | The number of vertices in the current draft (0 after finalization, cancellation, or mode exit) |
Example
draw.on('draftchange', (e) => {
// Enable a finish button once the polygon has enough vertices
finishBtn.disabled = e.vertexCount < 3;
vertexCountLabel.textContent = `Vertices: ${e.vertexCount}`;
});Removing Listeners
Use off with the same function reference to remove a listener:
const onCreateHandler = (e: CreateEvent) => {
console.log(e.feature);
};
// Subscribe
draw.on('create', onCreateHandler);
// Unsubscribe
draw.off('create', onCreateHandler);WARNING
Arrow functions defined inline cannot be removed. Always store a reference:
// This CANNOT be removed later
draw.on('create', (e) => console.log(e));
// This CAN be removed later
const handler = (e: CreateEvent) => console.log(e);
draw.on('create', handler);
draw.off('create', handler);