Open-source project
mapnik/node-mapnik avatar
mapnik/node-mapnik

node-mapnik, the Node bindings for the Mapnik renderer, and the platform list that constrains it

Bindings to mapnik for node.js

546 stars166 forksC++BSD-3-Clause

At a glance

What is it?
node-mapnik exposes the Mapnik C++ map renderer inside a Node.js process, so a stylesheet can be rendered and a shapefile read without a separate tile server. The catch is the prebuilt binary matrix: four platforms, a native core pinned behind the wrapper version, and a source build the documentation still marks as work in progress.
Who is it for?
node-mapnik suits a Node service on Linux or macOS on x64 or arm64 that wants in-process tile rendering or shapefile to GeoJSON conversion, and that is prepared to pin the native core version. It does not suit Windows deployments, platforms that forbid native addons, or a team that needs a released version stream.
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 28 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Node bindings that put the Mapnik C++ renderer inside a JavaScript process

node-mapnik is the set of bindings that lets Node.js call Mapnik, the C++ map renderer. The project is a native addon, not a wrapper around a separate service, which is the distinction that decides whether you want it.

The package on npm is @mapnik/mapnik, and the current version in the repository manifest is 4.8.2. Because the renderer runs in your own process, a tile request does not cross a socket and does not need a running map server: the stylesheet is parsed, the map is rendered, and the image is encoded inside the same Node event loop that serves your application. For a static tile pipeline, a batch job that turns shapefiles into GeoJSON, or a service that renders a map per request, that in-process model is the entire reason to use the bindings rather than a REST tile service.

The API surface mirrors Mapnik's own object model, and the samples in the README make that visible. A map is a mapnik.Map constructed with a width and a height, stylesheets are loaded through map.load, a mapnik.Image is the render target, and encoding happens on the image object. Datasources are reached through mapnik.Datasource with a type and a file, and features come out through a featureset you iterate.

Licensing is BSD-3-Clause for the bindings, and the repository also carries a LICENSE.txt file. The project is not archived, and the last push was on 2026-09-25, though the repository has no GitHub releases, so there is no tag to point a dependency at.

Rendering a map from a stylesheet, step by step

The full render path in the README is the best way to understand the object model, because it exercises every stage in order: register, load, zoom, render, encode, write.

js

js const fs = require('node:fs'); const mapnik = require('@mapnik/mapnik');

// register fonts and datasource plugins mapnik.register_default_fonts(); mapnik.register_default_input_plugins();

var map = new mapnik.Map(256, 256); map.load('./test/stylesheet.xml', function(err,map) { if (err) throw err; map.zoomAll(); var im = new mapnik.Image(256, 256); map.render(im, function(err,im) { if (err) throw err; im.encode('png', function(err,buffer) { if (err) throw err; fs.writeFile('map.png',buffer, function(err) { if (err) throw err; console.log('saved map image to map.png'); }); }); }); });

code

The two registration calls at the top are the ones people forget. mapnik.register_default_fonts() loads the default font set, and mapnik.register_default_input_plugins() loads the datasource plugins. Without them a stylesheet that names a font or a shapefile source fails, and the error will not tell you that the cause was a missing registration step.

The rest is a chain of callbacks. map.load takes a stylesheet path and hands back a map, zoomAll computes the extent that fits the loaded layers, a mapnik.Image of the same size is created as the canvas, and map.render fills it. The image is then encoded, in the example as png, and the buffer is written to map.png. The console message at the end confirms the file landed.

The callback style is what you will notice as a JavaScript developer. Every operation that touches the native renderer is asynchronous, so error handling is repeated at each level with a thrown error. In a production service you would want those throws turned into rejected promises or an error response rather than an uncaught exception, and that wrapping is yours to write.

Beyond tiles: image conversion and shapefile to GeoJSON

The bindings are not limited to map rendering, and the two smaller samples show a different side of the API.

Image handling is a one-liner. The README converts a jpeg to a png with:

js

js var mapnik = require('@mapnik/mapnik'); new mapnik.Image.open('input.jpg').save('output.png');

code

The open and save methods hang off the image object, so this is the same Image class that is used as a render target above, which means a pipeline can decode an existing image and then render over it without switching libraries.

The data path is the more interesting one for GIS work. The shapefile to GeoJSON sample registers the shape datasource plugin explicitly, using mapnik.settings.paths.input_plugins to locate the plugin file, then constructs a Datasource with type shape and a file path:

js

js const fs = require('node:fs'); const path = require('node:path'); const mapnik = require('@mapnik/mapnik');

mapnik.register_datasource(path.join(mapnik.settings.paths.input_plugins,'shape.input')); var ds = new mapnik.Datasource({type:'shape',file:'test/data/world_merc.shp'}); var featureset = ds.featureset() var geojson = { "type": "FeatureCollection", "features": [ ] } var feat = featureset.next(); while (feat) { geojson.features.push(JSON.parse(feat.toJSON())); feat = featureset.next(); } fs.writeFileSync("output.geojson",JSON.stringify(geojson,null,2));

code

The loop then pulls features one at a time from the featureset and pushes each one into a FeatureCollection as parsed JSON, finishing by writing output.geojson. Two details are worth copying. The plugin registration is explicit, because the shape plugin is not among the default input plugins in this sample, and the feature iteration is a manual next() loop rather than a for-of, which is the shape of the Mapnik featureset interface.

The README points to the test directory and to a separate sample code repository for more of these patterns, so the three examples here are a starting point rather than the full API.

Prebuilt binaries for four platforms, and what happens on a fifth

Installation is the part of this project with the sharpest edges, and the reason is that the native core has to exist for your platform before any of the API above can run.

Since v4.6.0 the module is published as universal binaries on the GitHub npm registry and on npmjs, built with node-addon-api, node-gyp-build and prebuildify. The supported platform list is short: linux-x64, linux-arm64 from v4.6.5, darwin-x64 and darwin-arm64. Windows is not in that list, and neither is any 32-bit target or an architecture such as riscv.

The mechanism underneath is worth understanding. The manifest declares four optional dependencies, one per platform, each pinning a core package at version 4.3.2: @mapnik/core-linux-x64, @mapnik/core-linux-arm64, @mapnik/core-darwin-x64 and @mapnik/core-darwin-arm64. npm installs the one matching the current platform, and node-gyp-build, wired to the install script, picks that binary at install time. The install hook itself is node-gyp-build, with preinstall and postinstall shell scripts alongside it.

The version mismatch is the thing to notice. The main package is at 4.8.2 while the platform cores it depends on are pinned at 4.3.2, so a fresh install combines a newer JavaScript layer with an older native core. If you hit behaviour that does not match the version you installed, that pin is the first place to look.

The prebuildify script targets Node 26.10.0, which is the runtime the prebuilt binaries were produced against.

Installing from npm, and the GitHub registry path with a token

The normal install is one command:

bash

bash npm install @mapnik/mapnik

code

If you are pulling from the GitHub npm registry instead, the README describes a two step setup. You check for a config file called .npmrc and create one if it does not exist, then edit it to include a scope mapping and an auth token line, with TOKEN replaced by your personal access token:

bash

bash @mapnik:registry=https://npm.pkg.github.com //npm.pkg.github.com/:_authToken=XXXXXXXXXXXXXXXXXXXXXXXXX

code

The documentation notes that the GitHub Packages route requires authentication, and that this path installs the latest node-mapnik 4.6.x series, which it calls the recommended option.

The source build is where the project is candid that it is unfinished. The section heading reads Source Build [WIP]. On macOS and Linux you are told to ensure that the mapnik-config program is available and on your ${PATH}, then run npm install --build-from-source, and to prebuild binaries run npm run prebuildify. Building from source therefore means you already have a Mapnik installation with its config tool on the path, which is a real prerequisite for anyone without one.

One documented build detail: SSE support is enabled by default on x86_64.

For the source build, the Makefile-style recipe in the README is not a Makefile at all, it is the melange-style description of a from-source build, and the equivalent make target in the repository layout is a separate concern.

Depending on it from another package, and running the tests

If node-mapnik is a dependency of another package rather than a script you run, the README gives the package.json entry to copy:

bash

bash "dependencies" : { "@mapnik/mapnik":"*" } // replace * with a given semver version string

code

The comment in that line is the important part: replace the wildcard with a given semver version string. The wildcard form is a placeholder, and a dependency spec that literally contains * resolves to whatever the newest published version is at install time, which for a native module with platform specific binaries is a bad default.

The tests use tape, and the manifest scripts section shows the exact command:

bash

bash npm test

code

The script in package.json expands that to tape test/*.test.js, so the runner is the tape library and the pattern picks up the spec files in the test directory that the README points to for more sample code. Dev dependencies list tape at ^v5.10.2, and the test directory in the repository layout is where the samples referenced from the README actually live, which makes it the best place to read the API in more detail than the three examples in the README itself.

The repository also carries a bench/ directory and a bin/ directory, and the manifest declares four executables: mapnik-index.js, mapnik-inspect.js, mapnik-render.js and mapnik-shapeindex.js. The names suggest the index, inspect and shape index operations are available from the command line, though the README does not document their flags.

Where node-mapnik is the wrong tool

The first wrong case is Windows, and it is not a matter of tuning. The supported platform list has no Windows entry, and the optional dependencies cover only linux and darwin on x64 and arm64. A Node application that must also run on Windows has no prebuilt path and no documented source build for that platform.

The second wrong case is a service where render latency matters and you cannot use a separate renderer. The bindings run the C++ renderer in your own process, which means a slow stylesheet blocks or delays work in the same event loop as everything else. Spinning up a dedicated tile server isolates that cost, at the price of a network hop.

The third wrong case is a deployment that forbids native modules. Some platforms, serverless functions in particular, do not allow arbitrary native addons, and node-gyp-build has nothing to select there. A pure JavaScript renderer or a hosted tile service is the fit for that constraint.

The fourth wrong case is a team that needs a stable version to pin. The repository has no GitHub releases, and the optional platform cores sit at 4.3.2 while the wrapper is at 4.8.2, so the version you depend on does not map cleanly onto the native code you run.

The fifth wrong case is expecting a batteries included GIS toolkit. The bindings expose Mapnik, and the sample code covers rendering, image conversion and shapefile reading. Anything beyond that, such as projections handling, is Mapnik's own surface rather than an extra convenience from this package.

Editorial conclusion

node-mapnik suits a Node service on Linux or macOS on x64 or arm64 that wants in-process tile rendering or shapefile to GeoJSON conversion, and that is prepared to pin the native core version. It does not suit Windows deployments, platforms that forbid native addons, or a team that needs a released version stream. Before adopting it, run npm install @mapnik/mapnik on the target platform and confirm the platform core package resolves, check the gap between the wrapper version 4.8.2 and the pinned core version 4.3.2 against the Mapnik behaviour you depend on, and read the Source Build section, which is still marked WIP and assumes mapnik-config is already on your ${PATH}.

Frequently asked questions

How do I install node-mapnik from npm?

Run npm install @mapnik/mapnik. Starting from v4.6.0 the module ships as universal binaries built with node-addon-api, node-gyp-build and prebuildify, with platform specific core packages pulled in as optional dependencies.

Which platforms does node-mapnik support?

The README lists linux-x64, linux-arm64 from v4.6.5, darwin-x64 and darwin-arm64. The manifest declares optional dependencies for those four platforms, each pinned at version 4.3.2.

How do I render a map from a stylesheet in Node.js?

Register the default fonts and input plugins, create a mapnik.Map with a size, load the stylesheet with map.load, call zoomAll, render into a mapnik.Image and encode it to a format such as png. The README gives the whole chain as a callback example that writes map.png.

How do I build node-mapnik from source?

The README marks the source build as work in progress. On macOS and Linux you need the mapnik-config program available on your ${PATH}, then run npm install --build-from-source, and npm run prebuildify to prebuild binaries. SSE support is enabled by default on x86_64.

How do I run the node-mapnik tests?

Run npm test, which the package manifest expands to tape test/*.test.js. The test directory also holds the sample code the README refers to for more usage patterns.

Official sources

  1. Issues
  2. License: BSD-3-Clause
  3. mapnik/node-mapnik on GitHub
  4. Project website
  5. README
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/mapnik-node-mapnik.svg)](https://hysenlabs.com/projects/mapnik-node-mapnik)

Was this useful?