Library / SDK
mapillary/mapillary-js avatar
mapillary/mapillary-js

MapillaryJS renders street-level imagery in the browser with WebGL

Interactive, extendable street imagery map experiences in the browser, powered by WebGL

522 stars91 forksTypeScriptMIT

At a glance

What is it?
MapillaryJS is a client-side library for interactive street imagery on the web, and it takes three kinds of input, spatial data, semantic data and textures, and renders them with WebGL. It is built on three.js and a set of specialised geometry libraries, and the version line shows a v5 rewrite still in beta.
Who is it for?
Adopt MapillaryJS if you are building a street-level imagery viewer and want a scene graph, camera controls and an extension model rather than a tile map with photos on top. Skip it if you only need to show a sequence of photographs on a map, since that is a much smaller problem, and if you need a stable major version today, because the released line is 4.x while the main branch carries a 5.0 beta.
Can I use it commercially?
Yes. MIT 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 24 days 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 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Three kinds of input, one WebGL scene

MapillaryJS is described as a client-side JavaScript library for interactive, extendable street imagery map experiences on the web. It takes spatial, semantic and texture data and renders it using WebGL.

That three-way split of the input is the clearest statement of what the library does, and it is worth unpacking because it covers three different kinds of map content.

Spatial data is the geometry: where things are. Semantic data is the meaning attached to them: a sign, a shop front, a traffic light, a piece of text. And textures are the images that carry the appearance.

A street imagery product needs all three, and the difficulty is that they do not arrive together or live in the same place. A camera on a car produces images with poses. A separate process may produce annotations about those images. A map service produces the basemap underneath. A viewer that renders all of that in one consistent 3D scene has to reconcile three coordinate systems and three update rates.

That is what makes this a substantial library rather than a photo carousel on a map. The rendering is a scene graph in WebGL, and the library's job is to keep the imagery, the annotations and the map in agreement as the user moves through the scene.

The customisation story is stated next, and it is the second half of the design. It can be customized with camera controls, user interactivity and data providers, and it can be augmented with geospatial rendering, animation and content placement.

The distinction between those two lists is meaningful. Customisation means changing how the built-in viewer behaves, and augmentation means adding a feature the library does not have. So there is a documented extension model, and the documentation has a page on developing extensions, alongside a getting started guide, examples, an API reference and a migration guide.

The project is TypeScript, MIT licensed, with the default branch main and the last push on 2026-09-29. The homepage is mapillary.github.io/mapillary-js.

Three ways to load it, and one of them needs no build step

The installation section is organised as three variants, and each one ends with a working viewer, which is a good structure for a documentation page.

The first is for an ES6 bundler. You install the package with Yarn or npm:

sh
yarn add mapillary-js

Then you use a CSS loader or include the CSS file in the head of your HTML file:

html
<link
  href="https://unpkg.com/[email protected]/dist/mapillary.css"
  rel="stylesheet"
/>

And then you include the following code in your JavaScript file:

js
import { Viewer } from "mapillary-js";

const viewer = new Viewer({
  accessToken: "<your access token>",
  container: "<your HTML element ID>",
  imageId: "<your image ID for initializing the viewer>",
});

That is the whole integration. A Viewer is constructed with three things: the access token, the id of the HTML element it should occupy, and the id of the image the viewer should initialise on.

The second variant is the same thing in TypeScript, and the only difference is that the options are typed:

ts
import { Viewer, ViewerOptions } from "mapillary-js";

const options: ViewerOptions = {
  accessToken: "<your access token>",
  container: "<your HTML element ID>",
  imageId: "<your image ID for initializing the viewer>",
};
const viewer = new Viewer(options);

The ViewerOptions type is exported from the package, so the options object is checked against the library's own declaration rather than being an untyped literal. For a library whose options include camera behaviour, interaction settings and data providers, that typing is worth having.

The third variant needs no build step at all. You include the JavaScript and CSS files in the head of your HTML file:

html
<script src="https://unpkg.com/[email protected]/dist/mapillary.js"></script>
<link
  href="https://unpkg.com/[email protected]/dist/mapillary.css"
  rel="stylesheet"
/>

Then you add a container to the body:

html
<div id="mly" style="width: 400px; height: 300px;"></div>

And then you use the global UMD name for MapillaryJS, which is mapillary:

js
<script>
var { Viewer } = mapillary;

var viewer = new Viewer({
  accessToken: "<your access token>",
  container: "mly",
  imageId: "<your image ID for initializing the viewer>",
});
</script>

The UMD build is the one that matters for embedding in something you do not control, such as a page with a build system you did not choose. The destructuring assignment at the top is the UMD convention, where the library attaches itself to a global object and you pull the named exports off it.

The container element in the CDN example carries explicit width and height, which is worth noticing. The viewer needs a sized element to render into, and with the CDN route there is no CSS framework guaranteeing that your div has a size. A zero-height div is the most common reason a WebGL viewer appears to do nothing.

Tokens, and the option to bring your own data

The token question is answered before the installation instructions, which is the right order, because it determines whether you need an account at all.

To start using MapillaryJS with data from the Mapillary platform, you need an account. When signed in, you need to register an application to get a client access token. And when providing your own data, no access token is needed.

That last sentence is the important one for anyone with their own imagery. A street imagery platform is not the only source of street imagery. A city with its own camera fleet, a company with its own survey vehicles, a research group with its own dataset: all of those have data that the library can render, and the token requirement is only about the Mapillary platform's own data and its own API.

So the library is usable as a general street imagery viewer with your own data source. The Mapillary platform is one provider, and the extension mechanism exists precisely so that you can add others.

The documentation makes that concrete with a link to the extension guide alongside the one for providing your own data. That is the architectural statement of the project: the viewer is the product and the data source is pluggable.

For the token itself, the guidance is the standard shape for a browser-side token. You register an application in the developer dashboard, you get a client access token, and you put it in the Viewer options. A client token rather than a secret is the right design for something that runs in a page, since anything in the browser is public by definition, and the token identifies your application and scopes what it may request rather than acting as a credential.

The dependency list, which is a map of the engineering

The package metadata has a dependency list that tells you more about how the library works than the prose does.

Three.js is pinned at 0.152.2 and is the renderer. It is a runtime dependency rather than a peer dependency, so the version is fixed, and the types package is pinned to match. The library therefore controls the version of the engine it draws with, which is the right choice for a library whose rendering behaviour depends on engine specifics.

Then there is the geometry stack, and each entry has a clear job. earcut is a polygon triangulation library, and it is what turns a polygon into triangles. rbush is a spatial index for fast bounding box and rectangle queries. polylabel finds the pole of inaccessibility of a polygon, which is the point on a shape furthest from its edge, and it is used for placing a marker in the most visible spot. martinez-polygon-clipping does boolean operations on polygons, which is what you need to cut a shape against a viewport boundary. And s2-geometry is the Spherical Geometry library, which handles operations on the sphere rather than on a flat plane, which is what you need when the data is geographic.

The last of those two is the one that shows the difference between a map viewer and a geospatial viewer. Working in spherical coordinates is the correct model for data that came from GPS, and the libraries that ignore that produce results that are subtly wrong at scale.

Then the data handling. pbf is the Protocol Buffers format, and it is what vector tiles and map data are commonly encoded in. pako is zlib compression in JavaScript, which is what you need to decompress a tiled response. rxjs is the reactive extensions library, which is how the viewer streams updates into the scene. And virtual-dom is a dependency that is unexpected until you know what it is for: it lets the viewer put DOM elements at positions in the 3D scene, which is how the annotation markers that label a shop front or a street name end up crisp and clickable rather than drawn as textured quads.

The virtual-dom dependency is the single most interesting entry, because it explains a feature that street imagery viewers all have and few explain: text and icons that stay sharp and interactive while the scene moves. Rendering them into the 3D scene as images loses the text quality, and the alternative of rendering them as DOM overlays requires knowing where they are in space, which is what virtual-dom does.

The development dependencies are also telling. Rollup with the commonjs and node-resolve plugins is the bundler, jest is the test runner, autoprefixer and caniuse-lite handle CSS prefixing against the browserslist target of the last two versions, express and concurrently serve the examples and the development server, and flowgen generates type declarations from the TypeScript sources.

Where the version line stands

The release tags and the package version tell two different stories, and the difference matters.

The tagged releases are v4.1.2 from 2023-11-15, v4.1.1 from 2022-09-08 and v4.1.0 from 2022-03-03. The package file on the main branch says 5.0.0-beta.6.

So the published, stable line is 4.x, and the main branch is working on 5.0 and has already published six betas of it. The last push was on 2026-09-29, so the work is current even though the stable release is older.

That gap is exactly why the documentation includes a migration guide for v4, and why it is listed alongside the getting started guide and the API reference rather than buried.

It also explains something in the installation examples. The stylesheet and script URLs point at version 4.1.2, the latest stable release, rather than at the beta. The examples are therefore showing you the stable API even though the code in the repository is v5.

That is a careful choice, and it is the kind of detail that distinguishes documentation written by people who have had users file confused issues. If the examples used the beta URLs, every reader following them would be running a prerelease.

The CHANGELOG.md at the root and the release tags give you the history, and the fact that the v5 work is in beta rather than released is the single most important thing to check before you build on this.

The repository layout is conventional for a library of this kind. There is src/ for the source, types/ and declarations/ for the type definitions, styles/ for the stylesheets, test/ with a jest.config.js at the root, examples/ with a debug example and a documentation example, doc/ for the documentation sources, and config/ for the shared configuration. The rollup.config.js is the bundler configuration, and the Dockerfile plus server.js are for serving the examples.

Where this fits, and what to know before you start

The honest comparison is with the other ways to build a street imagery viewer.

The first is a tile map with photographs pinned to positions, which is what most map libraries give you and what MapillaryJS existed to improve on. The pitch on the project's own site is that the tracking software only shows a boring 2D map, and the argument is that the vertical dimension and the perspective of a camera position are the interesting part.

The second is a general 3D library such as three.js on its own. You could write a street imagery viewer in three.js directly, and that is what MapillaryJS does underneath. The value of the wrapper is everything above the renderer: the camera model, the interaction handling, the data providers, the extension mechanism and the DOM overlay for annotations. That is a lot of work to do twice, and it is the work that is specific to this problem.

The third is the Mapillary platform itself, which is a hosted product rather than a library. If you want to display street imagery and do not want to build anything, the platform is the answer and this library is not needed.

So the library is for the case where you have your own imagery, or you want to build a product on top of imagery, and you want the viewer mechanics rather than the data.

Before you start, three things to check.

The first is the version. If you need the stable release, take the 4.x line and read the v4 documentation. If you want the current code, you are on a beta and the API may move.

The second is the token requirement, which is only for the Mapillary platform's own data. If you bring your own, there is no account to create and no token to manage, and you are responsible for serving the imagery and the geometry in the shapes the library expects.

The third is the extension model, because it is the part you will spend the most time in. There is a dedicated guide for developing extensions, and examples are published separately from the library. A viewer that you intend to extend with your own controls, your own data provider or your own overlays is a project where the extension API is the product, and you should read that guide before you write the application code.

Editorial conclusion

Adopt MapillaryJS if you are building a street-level imagery viewer and want a scene graph, camera controls and an extension model rather than a tile map with photos on top. Skip it if you only need to show a sequence of photographs on a map, since that is a much smaller problem, and if you need a stable major version today, because the released line is 4.x while the main branch carries a 5.0 beta. Verify first whether the v5 changes affect you by reading the migration guide, that you have a client access token if you use the Mapillary platform's data, and that you can accept a dependency on three.js at a pinned version.

Frequently asked questions

What is MapillaryJS and what does it render?

It is a client-side JavaScript library for interactive, extendable street imagery map experiences on the web. It takes spatial data, semantic data and texture data and renders them using WebGL, and it can be customized with camera controls, user interactivity and data providers, and augmented with geospatial rendering, animation and content placement.

How do I install MapillaryJS and create a viewer?

Install it with `yarn add mapillary-js`, load the stylesheet, then create a Viewer with an access token, the id of your container element and the id of the image to initialise on, for example `new Viewer({ accessToken, container, imageId })`. There are three documented routes: an ES6 bundler, TypeScript with the exported ViewerOptions type, and a CDN route that uses the UMD global named mapillary with a script and stylesheet link in the head and a sized container div in the body.

Do I need a Mapillary account to use MapillaryJS?

Only if you use data from the Mapillary platform. To use that data you need an account, you need to register an application to get a client access token, and you pass that token to the Viewer. When you provide your own data, no access token is needed, which is what makes the library usable for a city with its own camera fleet or a research group with its own imagery dataset.

Official sources

  1. License: MIT
  2. mapillary/mapillary-js on GitHub
  3. Project website
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/mapillary-mapillary-js.svg)](https://hysenlabs.com/projects/mapillary-mapillary-js)

Was this useful?