Quick start
Setup is two steps: get a cpp.js project (scaffold a fresh one, or add cpp.js to an app you already have), then drop in gdal3.js. After that you import GDAL like a module and convert your first file on any runtime: browser, Node or React Native.
Prerequisites
gdal3.js builds GDAL to WebAssembly (and to native libraries) through cpp.js, so cpp.js's build toolchain needs to be in place first:
- Docker, the cpp.js image carries the web and Android build toolchain; it's pulled automatically on the first build.
- Node.js 22+.
- React Native also needs CMake 3.28+, plus Xcode and CocoaPods for iOS.
Install links and the full list are in the cpp.js prerequisites.
Set up with an AI assistant
Using Cursor, Claude Code, Copilot or similar? Hand it this prompt and it'll wire gdal3.js into your project:
Set up gdal3.js v3 in my project. Detect my bundler and package manager, then: install gdal3.js, the matching @cpp.js/plugin-* (vite / webpack / rspack / rollup / metro), and the platform package (@gdal3.js/wasm for web); create cppjs.config.js at the project root importing "@gdal3.js/wasm/cppjs.config.mjs"; add the cpp.js plugin to my bundler config; then add a minimal example that opens a file with Gdal.open. Reference: https://gdal3.js.org/docs/getting-started/
Prefer to wire it up yourself? Keep going.
New project
The fastest way to a cpp.js-ready project is to scaffold one. The bundler, its cpp.js plugin and a starter cppjs.config.js come wired, with nothing to configure by hand.
npm init cpp.js@beta
Answer the prompts to pick whatever target fits, web (React, Vue, Svelte or vanilla), mobile (React Native), Node or Cloudflare Workers, plus a framework and bundler where they apply. That's your cpp.js project, now add gdal3.js.
Add to an existing project
Already have an app? Integrate cpp.js into it: add the bundler plugin and wire it in. Below it's Vite; Webpack, Rollup, Rspack and Metro (React Native) follow the same shape with their own plugin, see the bundlers guide.
npm install -D @cpp.js/plugin-vite
import { defineConfig } from "vite";
import viteCppjsPlugin from "@cpp.js/plugin-vite";
export default defineConfig({
plugins: [viteCppjsPlugin()],
});
That's cpp.js wired in. It reads a cppjs.config.js from your project root, which you'll create in the next step. Now add gdal3.js.
Add gdal3.js
However you got here, you now have a cpp.js project. Add GDAL, the package depends on your target:
npm install gdal3.js @gdal3.js/wasm
npm install gdal3.js @gdal3.js/android @gdal3.js/ios
Then declare it in cppjs.config.js at your project root. Scaffolded a project? The file is already there with a sample dependency, swap it out. Added cpp.js to an existing app? Create it now. Pick the config for your target:
import wasm from "@gdal3.js/wasm/cppjs.config.mjs";
export default {
dependencies: [wasm],
paths: { config: import.meta.url },
};
import ios from "@gdal3.js/ios/cppjs.config.mjs";
import android from "@gdal3.js/android/cppjs.config.mjs";
export default {
dependencies: [ios, android],
paths: { config: import.meta.url },
};
gdal3.js is the API; @gdal3.js/wasm carries the prebuilt GDAL binaries for web, Node and Cloudflare, while @gdal3.js/android and @gdal3.js/ios ship the native libraries. Now write code, pick your runtime below.
Use it
Write your pipeline in C++ and call it from JS, or drive GDAL straight from JavaScript, the API is the same shape either way. The C++ path reaches the complete GDAL API and runs a whole pipeline in one call without crossing the JS↔wasm boundary each time; the JavaScript path is the quickest to wire up. More on the tradeoff in two ways to use it.
From C++
Put your C++ under src/native: declare a class, implement it, and cpp.js exposes it to JavaScript. You reach GDAL however you like, version() reads GDAL_RELEASE_NAME straight from GDAL's own <gdal_version.h>, while convert() uses the high-level wrapper to run a full pipeline (to GeoPackage, reprojected to Web Mercator) with options as a plain std::vector<std::string>.
#pragma once
#include <string>
class Native {
public:
static std::string version();
static std::string convert(std::string inputPath, std::string outputPath);
};
#include "native.h"
#include <gdal_version.h>
#include <gdal3js/Gdal.h>
#include <gdal3js/Dataset.h>
std::string Native::version() {
return GDAL_RELEASE_NAME; // "3.13.1", straight from GDAL's own header
}
std::string Native::convert(std::string inputPath, std::string outputPath) {
auto ds = Gdal::open(inputPath);
auto out = ds->vectorTranslate(outputPath, {"-f", "GPKG", "-t_srs", "EPSG:3857"});
out->close();
return outputPath;
}
Then import the header from your app code and call either method:
import { initCppJs, Native } from "./native/native.h";
await initCppJs();
const version = await Native.version(); // GDAL version string
const out = await Native.convert(inputPath, outputPath);
From JavaScript
Prefer to stay in JavaScript? Import the wrapper, plus GDAL's own headers when you want a raw call, and use them directly:
import "gdal3.js/Dataset.h";
import { initCppJs, Gdal } from "gdal3.js/Gdal.h"; // wrapper
import { GDALGetDriverCount } from "@cpp.js/package-gdal/gdal.h"; // GDAL's own header
const Module = await initCppJs();
await GDALGetDriverCount(); // e.g. 189, straight from GDAL
const ds = await Gdal.openEx(inputPath);
const opts = await Module.toVector("VectorString", ["-f", "GPKG", "-t_srs", "EPSG:3857"]);
const out = await ds.vectorTranslate(outputPath, opts);
await out.close();
Where your input and output files live, and the optional init settings, are covered in the virtual file system and configuration guides.
Where to next
- Virtual file system: where files live, how to read results back.
- Frameworks: Webpack, Rollup, Rspack, Vue, Svelte specifics.
- Examples: reprojection, mosaics, hillshades, zipped Shapefiles.
- Migration: coming from v2? The full mapping table.