Skip to main content

reflex-mapgl-maplibre

Interactive MapLibre GL JS maps for Reflex, in pure Python. A thin, idiomatic wrapper around react-map-gl via its react-map-gl/maplibre endpoint — markers, popups, GeoJSON sources & layers, clustering, heatmaps, controls, feature interaction and imperative camera control, with no JavaScript or React required.

Status: beta (0.3.0). Core map, markers/popups, sources/layers, controls, multi-map provider and optional deck.gl/draw overlays are implemented, with a 77-test contract suite (100% wrapper coverage) and a demo app mapped to the upstream examples gallery. See specs/ for the full design.

Why

Reflex has no built-in map component. react-map-gl/maplibre is the standard React wrapper for MapLibre, but it is unreachable from Python without a wrapper. This package exposes it as Reflex components and ships a demo app that reproduces the upstream examples.

Install

uv add reflex-mapgl-maplibre      # or: pip install reflex-mapgl-maplibre

It pulls the npm packages react-map-gl and maplibre-gl into the compiled frontend automatically, and injects maplibre-gl/dist/maplibre-gl.css for you. It also hands MapLibre 6 (ESM-only) its Vite-bundled web worker through worker_url. Pass your own worker_url= only if you self-host the worker file.

Quickstart

import reflex as rx
import reflex_mapgl_maplibre as mapgl

STYLE = "https://demotiles.maplibre.org/style.json"  # key-less basemap

def index() -> rx.Component:
    return mapgl.map(
        mapgl.navigation_control(position="top-right"),
        mapgl.marker(longitude=-122.4, latitude=37.8, color="#ef4444"),
        initial_view_state={"longitude": -122.4, "latitude": 37.8, "zoom": 11},
        map_style=STYLE,
        style={"width": "100%", "height": "100vh"},
    )

app = rx.App()
app.add_page(index)

Components

Factory Wraps Purpose
mapgl.map(...) Map (default export) Root map canvas, camera, style, events
mapgl.marker(...) Marker DOM marker at a lng/lat
mapgl.popup(...) Popup Floating info bubble
mapgl.source(...) Source Data source (GeoJSON, tiles, clustering)
mapgl.layer(...) Layer Style layer (circle/line/fill/symbol/heatmap/…)
mapgl.navigation_control(...) NavigationControl Zoom + compass
mapgl.geolocate_control(...) GeolocateControl Find my location
mapgl.fullscreen_control(...) FullscreenControl Fullscreen toggle
mapgl.scale_control(...) ScaleControl Scale bar
mapgl.attribution_control(...) AttributionControl Attribution box
mapgl.terrain_control(...) TerrainControl 3D terrain toggle (raster-dem source)
mapgl.globe_control(...) GlobeControl Mercator ↔ globe projection toggle
mapgl.logo_control(...) LogoControl Positionable MapLibre logo
mapgl.map_provider(...) MapProvider Multi-map / synced maps

Optional, heavier overlays live in reflex_mapgl_maplibre.advanced (deckgl_overlay, draw_control) and pull extra npm dependencies on demand.

deck.gl + MapLibre 6: @deck.gl/mapbox 9.4 still reads the internal map.transform, which MapLibre 6 removed. Without a fix, a map with 3D terrain stops rendering in both overlaid and interleaved=True modes. The deckgl_overlay bridge re-exposes the fields deck.gl needs (elevation, height, _nearZ, _farZ) from MapLibre's camera, so terrain works in both modes. With interleaved=True deck layers share MapLibre's depth buffer, so layers at altitude 0 are hidden under raised terrain. Give positions a z, or use deck.gl's TerrainExtension (@deck.gl/extensions) to drape them.

Full contract: specs/api/component-api-v1.md.

Imperative camera control

map(...) returns an instance whose camera helpers produce event specs you can attach to any handler. The map needs an id:

m = mapgl.map(id="main", initial_view_state={...}, map_style=STYLE)

rx.button("Paris",    on_click=m.fly_to(2.35, 48.85, zoom=11))
rx.button("Fit area", on_click=m.fit_bounds([[-10, 36], [30, 60]], padding=40))

fly_to, ease_to, jump_to, fit_bounds are forwarded to the MapLibre MapRef (see Technical Design DD-003). fit_bounds(..., absolute_padding=True) uses MapLibre 6.12's persistent-padding mode.

3D terrain, sky & globe (MapLibre 6)

mapgl.map(
    mapgl.source(id="dem", type="raster-dem", url=TERRAIN_TILEJSON, tile_size=256),
    mapgl.terrain_control(position="top-right", source="dem", exaggeration=1.5),
    terrain={"source": "dem", "exaggeration": 1.5},
    sky={"sky-color": "#7dbbf0", "horizon-color": "#e6f0fa"},
    initial_view_state={"longitude": 11.39, "latitude": 47.28, "zoom": 12, "pitch": 70},
    map_style=STYLE,
)

mapgl.map(mapgl.globe_control(), projection="globe", map_style=STYLE)

Style global state

Layers can read ["global-state", "<name>"] in paint, layout or filter. Seed the values with global_state= (MapLibre 6.14) and change them from Python with set_global_state. The layers update without rebuilding the style:

m = mapgl.map(
    mapgl.source(
        mapgl.layer(id="quakes", type="circle",
                    filter=[">=", ["get", "mag"], ["global-state", "minMag"]]),
        id="quakes-src", type="geojson", data=EARTHQUAKES,
    ),
    id="main",
    global_state={"minMag": 0},
    map_style=STYLE,
)
rx.button("M4.5+", on_click=m.set_global_state("minMag", 4.5))

Examples (demo app)

The mapgl_maplibre_demo/ app maps the upstream examples gallery to one page each:

Page Example
/ Basic map
/controls Controls
/markers Markers & Popups
/geojson GeoJSON sources & layers
/clusters Clustering
/heatmap Heatmap
/interaction Click features → Reflex state
/animation Viewport animation (fly_to / fit_bounds)
/side-by-side Two maps under a MapProvider
/terrain 3D terrain, hillshade, sky and terrain_control (MapLibre 6)
/globe Globe projection, globe_control, logo_control and set_global_state

Run it:

uv sync                       # create .venv with the wrapper + dev tools
cd mapgl_maplibre_demo
uv run reflex run             # the local wrapper is installed in editable mode

Basemaps use key-less styles (MapLibre demotiles, CARTO). For MapTiler styles, pass map_style="https://api.maptiler.com/maps/streets/style.json?key=YOUR_KEY".

Development & publishing (Reflex custom component, uv)

This repo follows the Reflex custom-components layout (custom_components/, a demo app, and a publishable pyproject.toml) and uses uv as the package manager.

# 1. Set up the dev environment (creates .venv, installs the wrapper editable
#    plus the dev group: build, twine, pytest, pytest-cov, ruff).
uv sync

# 2. Quality gates.
uv run ruff check custom_components tests
uv run pytest                      # 77 tests, 100% wrapper coverage

# 3. Regenerate the .pyi type stubs (Reflex's stub generator), then build the
#    dist/ artifacts (.whl + .tar.gz) with the setuptools backend.
uv run python -m reflex_base.utils.pyi_generator custom_components/reflex_mapgl_maplibre
uv build

# 4. Validate the distribution metadata for PyPI.
uv run twine check dist/*

# 5. Manual publish (fallback; requires a PyPI API token).
uv publish                         # or: uv run twine upload dist/*
#   TestPyPI first:  uv publish --publish-url https://test.pypi.org/legacy/

Reflex 0.10 removed the reflex component CLI (init/build/share), so stubs come from reflex_base.utils.pyi_generator and artifacts from uv build. The published wheel ships the .pyi stubs and a py.typed marker (PEP 561) for IDE autocomplete.

Releasing

Releases are published by .github/workflows/publish.yml through PyPI Trusted Publishing (OIDC, no stored token):

  1. Bump version in pyproject.toml, refresh uv.lock (uv lock) and add the CHANGELOG.md entry; merge the PR into main once CI is green.
  2. Tag the merge commit on main and push the tag: git tag -a vX.Y.Z -m "vX.Y.Z" && git push origin vX.Y.Z.
  3. The workflow checks that the tag matches the package version, re-runs ruff and pytest, builds, runs twine check and uploads to PyPI.

Project layout

reflex-mapgl-maplibre/
├── custom_components/reflex_mapgl_maplibre/   # the wrapper package
│   ├── base.py        # shared base: library, CSS, NoSSR
│   ├── map.py         # Map root + imperative camera helpers
│   ├── markers.py     # Marker, Popup
│   ├── sources.py     # Source, Layer
│   ├── controls.py    # Navigation/Geolocate/Fullscreen/Scale/Attribution
│   ├── provider.py    # MapProvider
│   └── advanced.py    # deck.gl overlay & draw control (optional deps)
├── mapgl_maplibre_demo/                       # demo Reflex app
├── specs/                                     # SDD artifacts (see specs/README.md)
├── tests/                                     # compile/contract tests
└── scripts/create_github_repo.sh

Design docs (SDD)

This project is spec-driven. See specs/README.md: PRD · Component API Spec · Technical Design · Phases · Implementation Plan · Tasks · Upstream Analysis (examples catalog).

Compatibility

  • Python ≥ 3.11, Reflex ≥ 0.10 (tested on Reflex 0.10.0, Python 3.11–3.13)
  • Frontend: react-map-gl@^8.1.3, maplibre-gl@^6.14.0 (ESM-only, WebGL2 required)

License

MIT. Upstream react-map-gl and maplibre-gl are MIT-licensed; this wrapper is published with attribution.

Credits

Metadata

Release files for reflex-mapgl-maplibre 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for reflex-mapgl-maplibre 0.3.0
File Size Uploaded Provenance
reflex_mapgl_maplibre-0.3.0.tar.gz 29.5 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for reflex-mapgl-maplibre 0.3.0
File Interpreter ABI Platform
reflex_mapgl_maplibre-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 59.4 kB

Release files / reflex_mapgl_maplibre-0.3.0.tar.gz

Download URL reflex_mapgl_maplibre-0.3.0.tar.gz
Size 29.5 kB
Tags Source
SHA-256 checksum
How to use checksums
3b6e09f281e85da741bbc9bf2b8ab67a26a667bd6e671ae0aacb7d5c329b5d1d
BLAKE2b-256 checksum
How to use checksums
54ac361821d196bd68461841f2a4e41e0cea2a2a8146061b189d3832a920aff5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.

Transparency log

Release files / reflex_mapgl_maplibre-0.3.0-py3-none-any.whl

Download URL reflex_mapgl_maplibre-0.3.0-py3-none-any.whl
Size 29.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f2dfc76093ff9eeab1c760f08e79ae82d5387d6b54cf75a38293a3cc405739af
BLAKE2b-256 checksum
How to use checksums
2922b4dfcd025263fd43f5e0bcc57c91098d51adcac5cf92ed04a73dd05eb268
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.

Transparency log

Security for reflex-mapgl-maplibre 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for reflex_mapgl_maplibre-0.3.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page