What we mean by "plug in" annotations

Most clients come to us with a web map already running — Mapbox, Leaflet, MapLibre, ArcGIS JS API, whatever fits their stack. The map shows data. The team wants to add a workflow on top: select a feature, add a note, mark it for review, hand it off to someone else.

Plugging in annotations means adding that workflow without rebuilding the map. The map keeps doing what it does. We add a layer (the annotations), a panel (the UI for working with them), and the wiring underneath (authentication, persistence, sync).

This article walks through how we typically integrate. The patterns are stack-agnostic — they apply whether your map is Mapbox or ArcGIS.

The four moving parts

1. The annotation data store

Annotations need to live somewhere. The default we recommend is PostGIS (a Postgres extension for geographic data). One table for annotation features, one table for annotation activity log, one table for review states. PostGIS handles the geometry indexing natively and integrates cleanly with most map backends.

If the client already runs a different store (Esri ArcGIS Online feature service, Firestore, Supabase), we adapt. The schema below stays consistent regardless of where it physically lives:

annotations
  id, geometry, class, attributes (JSONB),
  created_by, created_at, status, layer_id

annotation_log
  annotation_id, action, actor, timestamp, payload

annotation_review
  annotation_id, reviewer, decision, notes, timestamp

Three tables instead of one because the log and the review state have different access patterns and different retention rules.

2. The map layer

On the map side, annotations are a vector layer (GeoJSON source or vector tiles depending on volume). We render them with the host map's standard layer mechanism — a Mapbox layer with a custom paint spec, a Leaflet GeoJSON layer with a style function. Nothing exotic.

For maps with more than ~10,000 active annotations, we move from a single GeoJSON source to vector tiles. Mapbox's built-in tile server handles this; for self-hosted MapLibre we run pg_tileserv against PostGIS.

3. The annotation panel

The panel is the UI for working with selected features. It lives in a sidebar or modal, host-app-styled to match the rest of the map. We build it as a small React or vanilla-JS component that subscribes to map selection events. When the user selects an annotation, the panel populates with that annotation's attributes, edit controls, and review actions.

We cover the panel in more detail in a separate article — the short version is that it's a thin wrapper over the data store, not a separate application.

4. The sync layer

When two people edit the same annotation at once, you need conflict resolution. The pattern we use is optimistic-lock-with-server-resolution: every annotation carries a version number, the client sends the version it's editing, the server rejects the update if the version doesn't match, the client refreshes and retries.

For most workflows this is rare enough that you can show a "this changed while you were editing, refresh?" toast and call it done. For workflows with heavy concurrent editing, we add real-time presence (showing other users' cursors on the map) so people see who's working where before they collide.

Authentication: don't reinvent it

Annotations are user-attributed. Every edit logs who did it. That means the annotation system needs to know who the user is, which means it needs to integrate with the host app's auth.

We integrate with whatever's already running: OAuth via the host app's IdP, JWT tokens from an existing API gateway, ArcGIS Online identity, Azure AD. The annotation backend validates the token, extracts the user identity, attributes the edit. No new sign-on.

If the host app doesn't have auth yet, we can add it (Auth0, Clerk, Supabase Auth all work fine), but auth-first is a project of its own and we usually recommend doing it before adding annotations.

Typical integration timeline

For a project where the map is built and the data store is decided:

  • Day 1-3: Schema design and review with the client team. What annotation classes? What attributes? What review states? Locking this up front saves rework.
  • Day 4-8: Backend wire-up. Annotation tables in PostGIS (or the chosen store), REST or GraphQL API for read/write, auth integration.
  • Day 9-12: Map layer integration. Annotations show up on the map, can be styled, can be selected.
  • Day 13-18: Panel build. Edit, review, comment, history view, permissions.
  • Day 19-22: End-to-end testing with the client's actual data and user accounts.

Three weeks total for a clean integration into a working map. Faster if the schema is simple. Slower if there are complex permissions or multi-tenant requirements.

What slows projects down

Three things, in order of frequency.

Schema indecision. Project owners often don't have a strong opinion on classes and attributes until they see annotations on the map. We solve this by building a "minimum viable schema" in the first day and iterating from there. The pattern is: ship something annotators can use this week, refine it next week.

Authentication that isn't quite ready. "We have auth" often means "we have login" but not "we have a token-based API." Plugging in annotations needs the second. If you're not there yet, add it before us.

Trying to launch with too many users at once. Pilot with 2-5 power users for two weeks before opening to a wider team. The power users will surface workflow gaps that no design review catches.


Want to scope a web map annotation integration? Send a screenshot of the map and a paragraph on what workflows you want to add. We'll come back with a schema draft and integration timeline within 48 hours.