Skip to content

Rendering Interactive Maps in the UI

PhotoPrism includes several high-resolution world maps that allow you to browse photos by location. Visit try.photoprism.app/library/places to try them on our demo.

Places UI Example

The API keys required to use these maps are unfortunately not free for us due to the number of users we have, see FAQ. Self-hosted users can configure their own map providers through the environment options documented on the configuration page.

Rendering Stack

MapLibre GL JS

Mapbox/MapLibre GL JS

Because Mapbox GL JS is no longer open-source, we now sponsor and use MapLibre GL JS for rendering maps in the UI. MapLibre GL is a fork from the last Mapbox GL version available under a permissive BSD license.

Statement by former Mapbox engineer Tom MacWright:

OSS, we hoped, was about enabling people and unlocking people’s ability to collaborate. It turns out that in 2020, it’s mostly helping companies and getting nothing in return. That’s not a dynamic you can build a sustainable business on.

Styles and Configuration

The list of map styles exposed to users is defined in frontend/src/options/options.js (MapsStyle(experimental)), and the user’s selection is stored under settings.maps.style. Only sponsored styles (marked with sponsor: true) ship by default; experimental/offline styles are hidden unless the experimental flag is enabled. When you add a new style:

  1. Update MapsStyle() with the value, display title, and the MapLibre style identifier (style).
  2. If the style requires a different API endpoint or token, extend frontend/src/common/maplibregl.js to inject the correct URLs before creating the map instance.
  3. Ship any additional CSS overrides in frontend/src/css/places.css.

The Map settings page (frontend/src/page/settings/general.vue) reads from the same MapsStyle() list. Make sure the strings are localized using $gettext() so the selection labels get extracted into the .po catalogs.

Sponsors and Tiles

Commercial map tiles that we sponsor (for example MapTiler terrain) remain optional. They are referenced via HTTPS endpoints inside frontend/src/common/maplibregl.js. Always keep attribution up to date by editing the strings inserted into map.addControl(new maplibregl.AttributionControl(...)) and the footer template in frontend/src/page/places.vue.

Tips for Contributors

  • Use MapLibre GL JS 3.x docs as the canonical API reference. Be careful when copying Mapbox-specific snippets since some APIs diverged after the fork.
  • Map styles can be edited or created with Maputnik. Store large JSON styles outside the bundle and reference them via CDN URLs instead of embedding them into Vue components.
  • The Places page loads maplibre lazily. If you add code that references maplibregl globally, ensure it runs after common/map.js resolves.
  • Keep performance in mind: clustering large marker sets happens on the worker thread, but tooltip/popover rendering is still on the main thread. Debounce expensive hover handlers and remove DOM nodes when a popup closes.