Migrating from v2 to v3
v3 is a ground-up rewrite on cpp.js: instead of a hand-maintained wrapper around five GDAL programs, the GDAL C++ classes are bound automatically and your bundler wires the assets. This page maps the main v2 calls to their v3 equivalents.
npm install gdal3.js). The v2 line (2.8.x) stays on npm and keeps receiving fixes; the JPEG/ZSTD/LERC codec PRs landed on the v2 branch in April and May 2026. Migrate when it suits you.
What changed, in one paragraph
In v2 you imported one package, called initGdalJs({ path, … }) with hand-configured asset paths, and used program-shaped helpers like Gdal.ogr2ogr(dataset, args). In v3 you import GDAL's C++ headers as JavaScript modules (import { initCppJs, Gdal } from 'gdal3.js/Gdal.h'), and a cpp.js bundler plugin (Vite, Webpack, Rollup, Rspack or Metro) wires the wasm, data and worker assets automatically. Programs became methods on the Dataset class, and the same imports run as a real native library on iOS and Android.
Installation
# v2
npm install gdal3.js@2
# v3
npm install gdal3.js
npm install -D @cpp.js/plugin-vite # or -webpack / -rollup / -metro
import { defineConfig } from 'vite';
import cppjs from '@cpp.js/plugin-vite';
export default defineConfig({
plugins: [cppjs()],
});
That replaces all of v2's CopyWebpackPlugin blocks, ?url imports and paths configuration, the source of most v2 setup issues. Framework-specific notes live in the frameworks guide.
API mapping (old → new)
| v2 | v3 |
|---|---|
import initGdalJs from 'gdal3.js' | import { initCppJs, Gdal } from 'gdal3.js/Gdal.h' (+ side-effect imports for Dataset.h, Driver.h, GCP.h, SubdatasetInfo.h) |
import … from 'gdal3.js/node.js' | Same import everywhere: the plugin picks the right build per platform |
initGdalJs({ path, paths }) | initCppJs(): asset paths resolved by the bundler plugin |
initGdalJs({ useWorker }) | initCppJs({ useWorker: true }) |
Gdal.open(file) (a File object) | Gdal.openEx(path): put bytes in OPFS or /vsimem/ first; see VFS guide |
Gdal.open(f, opts, ['vsizip']) | Gdal.openEx(path, flags, allowedDrivers, openOptions, siblingFiles), or open a /vsizip/… path directly |
Gdal.ogr2ogr(ds, args) | ds.vectorTranslate(outPath, opts) |
Gdal.gdal_translate(ds, args) | ds.translate(outPath, opts) |
Gdal.gdalwarp(ds, args) | Gdal.warp(destPath, dstDS, await Module.toVector('VectorDataset', [srcDS]), opts) |
Gdal.gdal_rasterize(ds, args) | ds.rasterize(outPath, opts) |
Gdal.gdalinfo(ds) / ds.info | ds.info(opts) · ds.vectorInfo(opts) · ds.multiDimInfo(opts) |
args: ['-f', 'GeoJSON'] | const opts = await Module.toVector('VectorString', ['-f', 'GeoJSON']) |
output name via outputName | You pass the full output path as the first argument |
Gdal.getFileBytes(path) | Outputs are real files in OPFS / host FS. Read them with the platform FS (browser: navigator.storage); list with Module.getFileList(dir) |
getOutputFiles() / .all list | ds.getFileList() (per dataset) or Module.getFileList(dir) |
Gdal.close(ds) | ds.close() |
Gdal.drivers.raster / .vector | await Module.toArray(await Gdal.getDrivers()), each with isRaster() / isVector() / isWritable() |
config: { env: { KEY: 'V' } } | Gdal.setConfigOption('KEY', 'V') |
logHandler / errorHandler | Unchanged: still initCppJs options. The error stack adds Gdal.getLastErrorMsg() / errorReset(); see error handling |
Breaking changes
- A bundler plugin is required for the module API. The zero-bundler path is the UMD
@gdal3.js/wasm-bundlebuild (or staying on v2's CDN build). Gdal.open()takes paths, notFileobjects. Write the bytes into OPFS or/vsimem/first, two lines of standard web API; see the getting started sample.- Program helpers moved onto
Dataset.ogr2ogr→vectorTranslate,gdal_translate→translate, and the option array must be converted withModule.toVector('VectorString', …). - Per-runtime entries are gone. No more
gdal3.js/node.js: the same import works in browser, Node and React Native. - The output story changed. v2's
/output+getFileBytesbecame real files in an OPFS-backed (browser) or host (Node/RN) filesystem. - Licensing. The source license moved from LGPL-2.1-or-later (v2) to MIT (v3), but the default build still ships as LGPL-2.1-or-later because it bundles GEOS, SpatiaLite and libiconv, the same copyleft as v2's build. Exclude those to drop the copyleft components; see the license page.
Common pitfalls
Forgetting Module.toVector for options
Program methods take a C++ vector<string>, not a JS array. Convert once: const opts = await Module.toVector('VectorString', ['-f', 'GPKG']).
Writing into a directory that doesn't exist
GDAL won't create intermediate directories: that's the classic v2 error ERROR 4: Failed to create … No such file or directory (issue #69). Create the tree first: await Module.FS.mkdirTree('/opfs/myapp/output').
Expecting output on the real disk in the browser
There is no real disk in a tab. Outputs land in the virtual filesystem, OPFS-backed when you run in a worker (useWorker: true), where it is on by default; pass fs: { opfs: false } to opt out. The VFS guide covers reading results back and offering downloads.
Multi-file formats (Shapefile)
A Shapefile conversion produces .shp + .shx + .dbf (+ .prj). Collect them with ds.getFileList() instead of assuming one path, same story as v2's .all attribute (issue #45).
Unknown-option errors come from GDAL, not the wrapper
Both v2 and v3 hand your option array straight to GDAL's own option parsers (GDALTranslateOptions & friends), so the native CLI documentation applies as-is and an "unknown option" error is GDAL's own. The practical difference is the GDAL version: v3 ships 3.13.1 (v2 is on 3.8), so some switches the older build rejected now parse.
Will v2 still be maintained?
Uncertain, honestly. 2.8.x is still on npm and got fixes as recently as April to May 2026 (the GeoTIFF codecs and a worker memory-leak fix), but continued maintenance isn't guaranteed: new work goes into v3, and v2 patches depend on spare time and community PRs. There's no removal date, but no firm commitment either, so plan to migrate to v3 when you can.