Skip to content

Markers

The Annotation API lets you place individual interactive markers, circles, lines, and polygons on the map with built-in tap and drag callbacks.

Five European landmark markers. Tap any to see its name.

When to use the Annotation API

Use annotations when you have fewer than ~50 features and need individual interactivity (tap callbacks, draggable). For large datasets use Style Layers.

Prerequisites

Annotations only work once the style has loaded and if their type is part of the widget's annotationOrder. See Prerequisites.

Add a symbol (icon + text)

iconImage references an image from the active style's sprite, or one you registered yourself with addImage / addImageFromAsset. There are no icons that exist in every style, so register your own first (see Custom image markers below) and reference it by name:

await addImageFromAsset(controller, 'my-pin', 'assets/markers/pin.png');

final symbol = await controller.addSymbol(
  const SymbolOptions(
    geometry: LatLng(48.8566, 2.3522),
    iconImage: 'my-pin',        // a name you registered with addImage
    iconSize: 1.0,
    textField: 'Paris',
    textOffset: Offset(0, 1.5),
    textAnchor: 'top',
    textSize: 14,
    textColor: '#1a1a2e',
    textHaloColor: '#ffffff',
    textHaloWidth: 2,
  ),
);

Icons are style-dependent

iconImage must name an image the map actually has. If you reference a name that isn't in the style's sprite and wasn't registered with addImage, the symbol renders nothing and the console logs "image … could not be loaded". Many styles (including the MapLibre demo style) ship no general-purpose marker sprite, so register your own image rather than assuming a built-in name exists.

Tap callback

controller.onSymbolTapped.add((Symbol symbol) {
  print('Tapped: ${symbol.options.textField}');
});

Update a symbol

await controller.updateSymbol(
  symbol,
  const SymbolOptions(
    iconColor: '#2ECC71',    // change color
    textField: 'Paris, FR', // change label
  ),
);

Remove a symbol

await controller.removeSymbol(symbol);
await controller.clearSymbols(); // remove all

Add multiple symbols at once

final symbols = await controller.addSymbols([
  const SymbolOptions(geometry: LatLng(48.86, 2.35), iconImage: 'my-pin'),
  const SymbolOptions(geometry: LatLng(51.50, -0.13), iconImage: 'my-pin'),
  const SymbolOptions(geometry: LatLng(52.52, 13.40), iconImage: 'my-pin'),
]);

Where icons come from

iconImage resolves against the images the map currently has:

  • The active style's sprite — some styles bundle a named icon set (e.g. a style built on the Maki icons exposes marker-15, restaurant-15, …). These names only exist if that style includes them; they are not guaranteed.
  • Images you register at runtime with addImage / addImageFromAsset — always available regardless of the style. This is the portable choice.

Because the MapLibre demo style (and many others) ship no general-purpose marker sprite, the examples here register their own image rather than relying on a built-in name.

Custom image markers

Register your image once with addImage() (in onStyleLoadedCallback), then reference it by name as the iconImage:

final bytes = await rootBundle.load('assets/markers/pin.png');
await controller.addImage('my-pin', bytes.buffer.asUint8List());

await controller.addSymbol(
  const SymbolOptions(
    geometry: LatLng(48.8566, 2.3522),
    iconImage: 'my-pin',
    iconSize: 1.0,
  ),
);

Other annotation types

// Circle
final circle = await controller.addCircle(
  CircleOptions(
    geometry: const LatLng(48.86, 2.35),
    circleRadius: 20,
    circleColor: '#296CA8',
    circleOpacity: 0.5,
  ),
);
controller.onCircleTapped.add((Circle c) { ... });

// Line
final line = await controller.addLine(
  LineOptions(
    geometry: const [LatLng(48.86, 2.35), LatLng(51.50, -0.13)],
    lineColor: '#E74C3C',
    lineWidth: 3,
  ),
);

// Fill (polygon)
final fill = await controller.addFill(
  FillOptions(
    geometry: const [
      [LatLng(48.7, 2.2), LatLng(49.0, 2.2), LatLng(49.0, 2.5), LatLng(48.7, 2.5)],
    ],
    fillColor: '#296CA8',
    fillOpacity: 0.3,
  ),
);

Key APIs