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 every v2 call to its v3 equivalent.
npm install gdal3.js@beta). 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 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@beta
npm install -D @cpp.js/plugin-vite # or -webpack / -rollup / -rspack / -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.open(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, [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 | Gdal.getLastErrorMsg(), Gdal.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. - License is unchanged: the MIT License with an LGPL-2.1 default build, in both lines.
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 if you init with fs: { opfs: true }). 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 from v2 are gone, mostly
v2's hand-rolled argument parsing rejected some real GDAL switches (-a_ullr #53, -gcp #95). v3 hands your option vector straight to GDAL's own options parsers (GDALTranslateOptions & friends), so the native CLI documentation applies as-is.
Will v2 still be maintained?
Yes: 2.8.x remains on npm and still receives fixes (most recently the GeoTIFF codec work and a worker memory-leak fix, April to May 2026). New feature work happens on v3. There is no removal date for v2.