JupyterGIS puts a collaborative map editor inside a JupyterLab extension
JupyterGIS - Collaborative GIS editor in Jupyter
At a glance
- What is it?
- JupyterGIS is a JupyterLab extension that provides an in-browser GIS with real-time collaboration, QGIS project import and export, a Python API for scripting, and a .jGIS document format that holds layers and styles together. It is the flagship project of the GeoJupyter community and it also runs entirely in the browser through JupyterLite.
- Who is it for?
- Adopt JupyterGIS if your team already works in Jupyter and wants to open, edit and share maps without leaving the notebook environment, because the.jGIS document, the Python API and the QGIS import together mean a map can live next to the analysis that uses it. Be aware that real-time collaboration, the headline feature, is not yet supported in JupyterLite, including in the static deployments people are most likely to try first.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the project is, and who it is for
JupyterGIS is described as an in-browser Geographical Information System with real-time collaboration, and the tagline is in-browser, collaborative GIS built on Jupyter. The README is precise about two things in the first paragraph: it is built on Project Jupyter but is independently useable, and as the flagship project of the GeoJupyter community it aims to enable organizations, researchers and students to easily explore and work with geospatial data.
The independently useable part matters. A JupyterLab extension that only worked inside a full Jupyter Server deployment would be a plugin for a plugin. Being usable as a standalone map, as an IDE, or from within a notebook is what lets the same tool serve a researcher running a local analysis and an organisation deploying a shared map service.
The highlights list is the feature inventory, and it is a long one.
Real-time collaboration is described as like Google Docs for GIS, with spatial annotations and comments. QGIS interoperability means you can import and export QGIS project files. A Python API gives scriptable workflows in collaborative notebooks. Many data formats are supported: GeoJSON, GeoTIFF and COG, GeoParquet, GeoPackage, Shapefile, PMTiles, WMS and WMTS, and more.
Then the parts that go beyond a map viewer. Grammar-driven symbology gives flexible rule-based styling, similar to Vega-Lite. Storymaps are guided narratives through your maps. Hillshade and heatmaps cover terrain and density. GDAL processing rasterises, warps and translates, in the browser or on the server. STAC and OpenEO give cloud-native data catalog and processing support. Pangeo integration provides dynamic raster tile serving through TiTiler backed by the Pangeo stack. A layer gallery offers a pre-built layer catalog for quick setup. Embeddable means you can use it as a standalone map, an IDE, or from within a notebook. And AI skills give map operations in natural language through Jupyter AI.
The primary language is TypeScript, the licence is BSD-3-Clause, the default branch is main, and the last push was on 2026-10-20. The recent releases are v0.16.8 from 2026-10-15, v0.16.7 from 2026-10-02 and v0.16.6 from 2026-09-25, so this is a project on a steady fortnightly cadence rather than one that ships occasionally.
The .jGIS document, and why the format matters
The examples directory is the clearest evidence of what the project actually stores, and the file extensions tell the story.
There are .jGIS files in the examples alongside the other formats: buildings.jGIS, cloud_optimized_geotiff.jGIS, earthquakes.jGIS, europe_demo.jGIS, geoZarr.jGIS, geoparquet.jGIS, hillshade.jGIS, image.jGIS, local.jGIS and macrostrat.jGIS. There is one lowercase .jgis, geopackage.jgis, and two QGIS project files, buildings.qgz and geotiff.qgz.
The pairing is the point. The .jGIS document is the native format that holds the map together, and the .qgz files show that the same content can be expressed as a QGIS project. The QGIS interoperability feature, import and export QGIS project files, means a map authored in JupyterGIS can be opened in QGIS and a map authored in QGIS can be brought into JupyterGIS.
That interchange is the feature that makes this usable in an existing workflow rather than only inside Jupyter. A lot of people produce map styling in QGIS, and a lot of people analyse data in notebooks, and having the two meet at a shared document format is what removes the export-to-image step.
The example notebooks show the other half, and they are named for what they do rather than for a numbered step: 01-Create_a_new_JGIS_document.ipynb is the introductory one, and then 99-Explore_data_in_a_map.ipynb, 99-Open_an_existing_JGIS_Project_and_add_layers.ipynb, 99-Symbology.ipynb, 99-OpenEO-titiler-local.ipynb, 99-OpenEO-NDCI_venice_lagoon-Microsoft.ipynb, 99-OpenEO-ESA-catalogue.ipynb and demo-cng2026.ipynb. There is also esa_catalogue.py as a plain script alongside the notebooks.
Three of those examples are about OpenEO, and that is worth noting. OpenEO is an API for cloud-hosted raster and vector processing, and having examples that connect to the ESA catalogue, to a Microsoft offering and to a local TiTiler instance in one repository tells you that the OpenEO support is meant to work across the range of providers rather than with one.
There is also a maplibre/ directory in the examples, and a data/ directory, and the whole set is what a JupyterLite deployment would serve, since a static site needs the data next to the notebook.
Installing it, and the four routes with their trade-offs
There are four installation routes, and the differences between them matter more than the commands themselves.
Mamba is the recommended one:
mamba install -c conda-forge jupytergispip is the familiar alternative:
pip install jupytergisPixi is the third:
pixi add jupytergisAnd Docker, which gives you the whole stack with a port mapping:
docker run -p 8888:8888 ghcr.io/geojupyter/jupytergis:latestwith the build source in a separate repository, jupytergis-docker.
The choice between these is mostly about the Python environment. Mamba and Pixi both give you a solved environment, which matters for a package that pulls in GDAL, which is a large native dependency with its own platform packages. pip puts the burden on your existing environment, which is where the friction will be if you already have a scientific stack installed.
The Docker route is the one that gets you a working collaboration-capable server, because the port mapping exposes a full Jupyter server rather than a library in your own environment.
The browser route is separate again and worth understanding. There is a JupyterLite deployment that runs entirely in the browser via WebAssembly with no installation needed, and there is a hosted instance on Notebook.link powered by Notebook.link and JupyterLite. The README also points at a JupyterLite static deployment procedure for people who want to publish their own maps as a static site.
That procedure is four steps. You create a repository using the xeus-lite-demo template, you edit environment.yml and add jupytergis-lite, you add your data and .jGIS files under the content/ directory, and under Settings and Pages you set the GitHub Pages deployment source to GitHub Actions.
The limitation is stated twice, once for the hosted instance and once for the static deployment, and it is the same one: real-time collaboration is not yet supported in JupyterLite, and collaboration is not yet supported in JupyterLite static deployments. That is the single most important caveat in the README, because collaboration is the headline feature and the browser build is the easiest one to try.
Grammar-driven symbology and the AI skills angle
Two of the highlights deserve more than a line each, because they are the features that are not obviously about maps at all.
Grammar-driven symbology is described as flexible rule-based styling, similar to Vega-Lite. The comparison is the useful part, because it tells you the shape of the feature. In Vega-Lite a specification is a grammar, a declarative language where a chart type plus encodings produce a visualisation, and the point is that the author describes what they want rather than drawing it. Grammar-driven symbology in a GIS is the same idea applied to map styling: you write rules that map a data property to a visual property, and the renderer applies them to every feature that matches.
For a map that is a bigger deal than it sounds, because traditional cartographic styling is a long list of hand-set rules per layer, and the layers in a real project are many. If the style is a function of the data rather than a set of per-layer instructions, then a change to the data changes the map without a styling edit, and two maps built from the same data look the same by construction.
The other feature is AI skills, described as map operations in natural language via Jupyter AI. That is a new capability and the README gives it one line, so there is not much to say about its scope. But the placement is meaningful. Jupyter AI operates over the notebook, and a notebook running JupyterGIS has a live map, so a natural-language instruction can act on the map the same way it acts on a dataframe.
Together with the Python API, described as scriptable workflows in collaborative notebooks, and the storymaps feature for guided narratives through your maps, these three point at the same underlying idea: a map in JupyterGIS is not a picture at the end of a pipeline, it is a live object in a session you can program against.
The storymaps feature is worth a word on its own, because guided narrative is a form of communication that a static map cannot do. A storymap walks a reader through a sequence of map states with accompanying text, which is how a spatial argument is made in a report, a lesson or a policy document. That is a publishing feature, and it sits oddly with a tool whose headline is editing and collaboration, which is a sign of how broad the project is trying to be.
The layer gallery, described as a pre-built layer catalog for quick setup, is the other publishing-adjacent feature. It means you do not have to find and configure a basemap or an administrative boundary layer before your first map, which is the difference between a first run that produces a map and a first run that produces an empty canvas.
A monorepo with a TypeScript side, a Python side and a Jupyter extension
The repository layout is unusual for a JupyterLab extension, and it is the clearest statement of what the project has become.
There is a packages/ directory for the TypeScript side, a python/ directory for the Python packages, a lite/ directory for the JupyterLite build, a docs/ directory, an examples/ directory, a binder/ directory, and a ui-tests/ directory. The root package.json is named @jupytergis/jupytergis-root and marked private, which means it is the workspace root rather than a published package.
The build is managed by lerna with an nx.json on top, and the scripts show how the two halves fit together. The build target runs lerna run build, the watch target runs lerna watch scoped to the package being worked on with its dependents included, and the dev target is python scripts/dev-install.py, which is the tell that a TypeScript change requires a Python-side reinstall for a developer to see it.
The Python packaging is in pyproject.toml, and the releaser configuration lists five Python packages that ship together:
python_packages = [
"python/jupytergis:jupytergis",
"python/jupytergis_core:jupytergis_core",
"python/jupytergis_lab:jupytergis_lab",
"python/jupytergis_qgis:jupytergis_qgis",
"python/jupytergis_lite:jupytergis_lite",
]Each name maps to a directory under python/, and the suffixes describe the roles: core is the shared logic, lab is the JupyterLab extension, qgis is the QGIS interoperability layer, and lite is the browser build. The root project requires Python 3.12 or newer and its build backend is hatchling, with the version read out of python/jupytergis/jupytergis/__init__.py.
The test and typecheck dependency groups are worth noting. The typecheck group pulls mypy alongside PyQt6-stubs, types-geopandas and types-requests, which tells you that the Python side type-checks against the Qt bindings and against the geospatial stack.
The lint configuration is the most opinionated part. Ruff is configured with select set to ALL, which turns on every rule Ruff has, and then a long list of exceptions is switched off, each with a comment explaining why. The exceptions are the arguments-style rules for missing type annotations on function arguments, plus a few others. Switching on every rule and then disabling the noisy ones is a stricter starting point than choosing rules, and it means new rules arrive enabled by default.
Ruff is also configured with extend-include set to *.ipynb, so the notebooks are linted as source.
Community process is documented rather than assumed. There is a Contributor Guide in the docs, a Zulip channel under the Jupyter organisation, and community meetings linked from the GeoJupyter calendar.
Editorial conclusion
Adopt JupyterGIS if your team already works in Jupyter and wants to open, edit and share maps without leaving the notebook environment, because the.jGIS document, the Python API and the QGIS import together mean a map can live next to the analysis that uses it. Be aware that real-time collaboration, the headline feature, is not yet supported in JupyterLite, including in the static deployments people are most likely to try first. Verify first that your data is in one of the formats it reads, GeoJSON, GeoTIFF and COG, GeoParquet, GeoPackage, Shapefile, PMTiles or WMS and WMTS, that the collaboration server is available in the deployment you choose rather than only the browser build, and that the Python version you run matches the requires-python of 3.12 or newer that the root package declares.
Frequently asked questions
What is JupyterGIS?
It is an in-browser Geographical Information System with real-time collaboration, built on Project Jupyter but independently useable. As the flagship project of the GeoJupyter community it aims to enable organisations, researchers and students to explore and work with geospatial data.
How do I install JupyterGIS?
Four routes are documented: mamba install -c conda-forge jupytergis as the recommended one, pip install jupytergis, pixi add jupytergis, and a Docker image run with docker run -p 8888:8888 ghcr.io/geojupyter/jupytergis:latest. There is also a browser-only build through JupyterLite.
Does JupyterGIS support real-time collaboration in the browser?
Not yet. The README states that real-time collaboration is not yet supported in JupyterLite, and repeats that collaboration is not yet supported in JupyterLite static deployments, so the browser build lacks the headline feature.
What data formats and standards does JupyterGIS work with?
GeoJSON, GeoTIFF and COG, GeoParquet, GeoPackage, Shapefile, PMTiles, WMS and WMTS, and more. It also has STAC and OpenEO support, GDAL processing for rasterising, warping and translating, and Pangeo integration for dynamic raster tile serving through TiTiler.
Can I open a QGIS project in JupyterGIS?
Yes. QGIS interoperability is a listed feature, meaning you can import and export QGIS project files, and the examples directory contains both .jGIS documents and .qgz files to work from.
How do I publish a JupyterGIS map as a static site?
Create a repository from the xeus-lite-demo template, add jupytergis-lite to environment.yml, put your data and .jGIS files under content/, and set the GitHub Pages deployment source to GitHub Actions. Note that collaboration is not supported in that setup.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/geojupyter-jupytergis)Was this useful?