@honua/sdk-js

MapLibre gives you the map. Honua gives you everything else: typed clients for Esri GeoServices, OGC API (Features / Tiles / Maps / Processes), STAC, WMS/WMTS, WFS 2.0, and OData v4 — plus a one-call data→map bridge and a drop-in ArcGIS migration path.

Building on MapLibre → First Map Leaving ArcGIS → widget survival guide API reference Demo gallery

Classic Esri widgets are deprecated at ArcGIS JS 5.0 and removed at 6.0 (planned Q1 2027). Scan your app: npm run scan:arcgis:widgets -- ./src.

Honua JS SDK

OpenSSF Scorecard

npm types license node docs

MapLibre renders the map. Honua connects, queries, explains, and mounts the data.

@honua/sdk-js is the integration layer for the open map stack: typed clients for the protocols your data already speaks (Esri GeoServices, OGC API Features / Tiles / Maps / Processes, STAC, WMS, WMTS, WFS 2.0, OData v4), a one-call data→map bridge and MapLibre runtime, provider-pluggable geocoding and routing, and a drop-in ArcGIS compatibility layer with a codemod. MapLibre is the stable renderer path; the optional Cesium scene surface is beta on @honua/app-platform, and Kepler.gl integration is experimental.

Leaving ArcGIS? Every classic Esri widget was deprecated at ArcGIS JS SDK 5.0 and removal begins with 6.0 — as early as Q1 2027. If your app constructs one, that code stops compiling and running when you take the 6.0 upgrade. Run npm run scan:arcgis:widgets -- ./src for a per-file readiness report, then read the widget-removal survival guide — every deprecated widget mapped to its Honua/MapLibre disposition.

A public endpoint to a styled map

Ten application lines. No Honua server, no API key, no account — one lifecycle owner takes a public Esri Living Atlas FeatureServer through inspection, a bounded query, an explainable plan, and a styled MapLibre map:

import { createHonua } from "@honua/sdk-js";
import { maplibreRenderer } from "@honua/sdk-js/runtime";
import * as maplibregl from "maplibre-gl";
const endpoint = "https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis/rest/services/2020_Census_State_Apportionment/FeatureServer/0";
await using honua = createHonua();
const data = await honua.connect(endpoint);
const info = await data.inspect();
const plan = await data.explain({ returnGeometry: true, pagination: { limit: 100 } }, { sourceId: info.defaultSourceId });
const result = await data.query(plan);
const map = await data.mount("#map", { renderer: maplibreRenderer(maplibregl), query: plan, sourceId: info.defaultSourceId });
await map.ready;

For those ten lines the kernel owns discovery, the connection, cancellation, and the mounted map. result contains bounded execution evidence, while plan explains the accepted query. Leaving the await using scope disposes the map and connection; every operation also accepts an AbortSignal for caller cancellation. Run the canonical inspected workflow with npm run demo:quickstart:mock (examples/maplibre-quickstart/); the focused lower-level cookbook is docs/data-to-map-bridge.md.

For an application that opens more than one source, keep the same instance-scoped owner:

import { createHonua } from "@honua/sdk-js";

const honua = createHonua({ discoveryCacheMaxEntries: 128 });
try {
  const data = await honua.connect(
    "https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis/rest/services/2020_Census_State_Apportionment/FeatureServer/0",
  );
  const inspection = await data.inspect();
  const source = data.source<{ NAME: string; Seats_2020: number }>();

  console.log(inspection.cacheStatus, source.descriptor.id);
} finally {
  await honua.dispose();
}

inspect() reuses the immutable snapshot established by connect(); inspect({ refresh: true, signal }) revalidates it. Service roots with more than one source require an explicit sourceId in the locator/options or in source(id)—the kernel never chooses the first advertised source silently.

Release status: beta (0.1.7-beta.0). The 22-entrypoint stable tier is guarded by an API-surface gate; 26 experimental subpaths may change before 1.0, and 18 deprecated compatibility subpaths have explicit removal versions. See config/support-manifest.v1.json for the versioned support truth, config/public-surface.json for its generated package projection, support/projections/sdk-support.v1.json for the generic site/sample consumer contract, and the scope decision.

This README tracks the current development branch. The version above is its package baseline, not a claim that every capability described here is already present in the npm artifact with that version. For published behavior, use the tagged release documentation.

📚 Hosted docs: honua-io.github.io/honua-sdk-js — quickstart, the full guide corpus, the TypeDoc API reference, and the demo gallery.

Pick your path

🗺️ Building on MapLibre 🚦 Leaving ArcGIS 🤖 Connecting an AI assistant
You are… adding typed data access, styling, and interactions to a MapLibre (or brand-new) app facing the classic-widget removal at ArcGIS JS 6.0 (as early as Q1 2027) wiring a coding agent or assistant to live geospatial data
Start First Map — paste a public GeoServices or OGC Features endpoint, no account required npm run scan:arcgis:widgets -- ./src — per-file 6.0 readiness report from the migration scanner point honua-mcp at any public FeatureServer/OGC endpoint — no Honua server
Then Data-to-map bridge cookbookconnect()mountSource() strategies, styling, filters Widget survival guide — all 38 deprecated widgets mapped to automated / assisted / manual dispositions the protocol-neutral tool contract + agent skills for Claude Code and compatible agents
Go deeper MapLibre runtime · React bindings · geometry ops · geocoding & routing providers esri-compat drop-ins + the honua-migrate codemod · migration punch list NL map control · agent-safety threat model · coding-agent evals
Runnable proof examples/maplibre-quickstart/ — deterministic fixture plus separately gated anonymous-live evidence migration-workbench (npm run demo:migration-workbench) — scan → codemod → run, end to end cross-model MCP eval scorecard — dated runs, failures and the zero-LLM control included

Where it fits

The rendering war is settled — MapLibre is the open 2D engine of record and Cesium owns open 3D. What the open stack has been missing is the layer above the renderer: service clients, styling, interactions, editing, geocoding, migration tooling — the glue every team hand-rolls. That integration layer is what @honua/sdk-js owns:

The honest comparisons are the service-client libraries, not the renderers:

The numbers behind those claims — generated bundle sizes, a protocol-coverage matrix and an operation-level behaviour table against raw MapLibre / @esri/arcgis-rest-js / OpenLayers, and a scripted time-to-first-map benchmark with a runnable repro (npm run bench:ttfm) — live in docs/comparison.md. That page names the category boundary behind every column (headless client vs renderer vs all-in-one SDK) and backs each external figure with a dated, primary-sourced evidence record; a measurement of a superseded release line is labelled historical and is barred, in code, from supporting a current claim.

Two deployment tiers, named up front. 28 of the 35 generated support claims are open-endpoint: they run against standards-speaking endpoints you already have, or entirely in the client and build — no Honua account, no Honua Server. 7 are server-attach and execute only after attaching to a Honua Server facade; 1 of those link a roadmap issue for an open-endpoint path and the rest state why the server dependency is inherent, in the generated capability tiers table.

Honua Server is optional for standards clients. Supported GeoServices, OGC API Features, WFS 2.0, WMS 1.3, WMTS 1.0, STAC, and OData claims work against raw standards-speaking endpoints. OGC API Tiles (beta), Maps (beta), and Records (beta) also discover and use raw advertised paths. OGC API Processes keeps two honest lanes against a raw server: discovery (supported, standalone) and typed execution (experimental, standalone).

A Honua Server adds server-authored MapPackages, realtime, collaboration, compatibility metadata, a richer hosted /mcp operator catalog, and the facade-required execution paths. See the generated backend-agnostic capability matrix for every claim's tier, execution mode, and evidence link.

What Honua does not do

In the spirit of the migration punch list, the non-goals are explicit rather than implied:

Install

npm install @honua/sdk-js

Runtime support, stated up front:

Peer / runtime Supported range
Node.js >=20.19
maplibre-gl (optional peer) 5 and 6 (^5.0.0 || ^6.0.0) — the 6.x half ships with the next release; the current published beta declares ^5.0.0. MapLibre 6 is ESM-only and requires WebGL2
cesium (optional peer, scene surface) ^1.139.0
react / react-dom (optional peer, /react) ^18.2.0 || ^19.0.0

Starting from scratch? create-honua-app scaffolds a working app instead of assembling peers — a Vite + TypeScript (or React) starter that already connects to an endpoint and mounts a source, pinned to a published SDK version and rendering a committed fixture on the first npm run dev. The package is published on npm, so npm create honua-app is the shortest supported path; both starters also open in a browser playground with no install at all — see docs/playgrounds.md and docs/create-honua-app.md.

Everything documented here ships in @honua/sdk-js as subpath entrypoints (see INSTALL.md). Focused standalone packages are also published from this repository for consumers who only want a subset:

Package What it is
@honua/sdk-js The canonical install — full SDK with all subpath entrypoints + the honua CLI
@honua/mcp-server Platform-free geospatial MCP server (honua-mcp, honua-mcp-proxy) — see mcp/
@honua/react React provider, hooks, and map components (split build; docs/react.md)
@honua/geometry Curated turf/proj4 geometry ops + reprojection (split build; docs/geometry.md)
@honua/sdk Core client + contract only (split build)
@honua/sdk-esri-compat ArcGIS JS compatibility layer (split build)
@honua/honua-migrate Migration codemod + scanner, owned by the honua-migrate repository
@honua/app-platform Application-platform surfaces extracted from the SDK (split build; own pre-1.0 cadence)

The SDK split builds exist for packaging workflows and subset consumers; details in docs/split-packages.md. Existing @honua/sdk-js/migration imports follow the migration-tool transition policy.

Build-less / CDN usage

For static sites, prototypes, or CSP-strict pages that can't run a bundler, a prebuilt browser bundle is published under dist/browser/. Drop in the minified IIFE build and use the global window.HonuaSDK:

<script src="https://cdn.jsdelivr.net/npm/@honua/sdk-js/dist/browser/honua-sdk.min.js"></script>
<script>
  const client = new HonuaSDK.HonuaClient({ baseUrl: "https://your-honua-server.example" });
  // window.HonuaSDK exposes the same public API as `import ... from "@honua/sdk-js"`.
</script>

Or, for native ES module imports via an ESM CDN:

<script type="module">
  import { HonuaClient } from "https://esm.sh/@honua/sdk-js/browser";
  const client = new HonuaClient({ baseUrl: "https://your-honua-server.example" });
</script>

The runtime peers (maplibre-gl, cesium, @bufbuild/*, @connectrpc/*) are kept external — load them yourself when you need map rendering or gRPC transport. See docs/browser-bundle.md for details.

Bundle size

Honest about size: every subpath entrypoint carries a min+gzip byte budget that CI enforces on every PR (npm run verify:bundle-budgets), so drift fails the build instead of shipping. Sizes are measured the way a consumer builds — esbuild --bundle --minify, runtime peers external. The excerpt below is generated from that measurement, tree-shake guards included:

Entrypoint (gzip) Size
@honua/sdk-js/expr 2.4 KiB
@honua/sdk-js/geocoding 7.9 KiB
@honua/sdk-js/webmap 7.6 KiB
@honua/sdk-js/style 16.2 KiB
@honua/sdk-js/map 51.7 KiB
@honua/sdk-js (root) 208.7 KiB
{ HonuaClient } from the root (tree-shake guard) 67.7 KiB
{ connect } from the root (tree-shake guard) 169.9 KiB
{ createHonua } from the root (tree-shake guard) 198.8 KiB

The root is the whole reviewed kernel and the guards price its verbs honestly: importing { connect } alone costs 169.9 KiB gzip and { createHonua } 198.8 KiB against the 208.7 KiB root, so size-sensitive apps should import the focused subpaths rather than the root. Full per-entrypoint table (min + gzip, generated): docs/bundle-sizes.md; refresh the table and this excerpt together with npm run report:bundle-sizes.

For how these sizes sit against @arcgis/core and friends — at a named category boundary, with every external figure dated and primary-sourced — see the generated comparison page.

60-second quickstart

No Honua server required. This runs against a public Esri GeoServices endpoint — no API key, no account, no infrastructure — and walks the kernel's verbs in order: connect → query → explain (the fourth verb, mount, is the hero at the top of this page).

import { connect, explainQuery, envelope, queryFilter, type Query } from "@honua/sdk-js";

// 1. connect — a public Esri Living Atlas FeatureServer; nothing of Honua's is running.
const data = await connect({
  endpoint:
    "https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis/rest/services/2020_Census_State_Apportionment/FeatureServer/0",
  protocol: "auto",
  authorizationScopeFingerprint: "public",
});
const states = data.source<{ NAME: string; Total_Pop_2020: number }>();

// 2. query — one typed, protocol-neutral filter expression.
const query: Query = {
  filter: queryFilter.and(
    queryFilter.gt("Total_Pop_2020", 1_000_000),
    queryFilter.spatial("intersects", envelope(-125, 24, -66, 50)),
  ),
  outFields: ["NAME", "Total_Pop_2020"],
  pagination: { limit: 100 },
};

// 3. explain — the serializable plan, inspectable before anything executes.
const plan = explainQuery({ descriptor: states.descriptor, query });
console.log(plan.fingerprint, plan.steps.map((step) => `${step.engine}:${step.operation}`));

const result = await states.queryAll(query);
console.log(`Loaded ${result.features.length} states`);

Query.filter compiles to GeoServices SQL-92, CQL2, FES 2.0, OData $filter, or DuckDB SQL, and Query.temporalFilter compiles to the protocol's own time parameter (time=, datetime=) or an exact predicate on a named field. A construct the target cannot express throws HonuaCapabilityNotSupportedError naming the construct and the protocol — it is never silently dropped or widened. explainQuery returns the same plan surface agents and the honua explain CLI command use: compiled predicates, bounds, cache and fidelity decisions, all serializable. Assembling a Dataset by hand — explicit protocol, locator, and capabilities, no discovery — is covered in docs/guide.md.

The deprecated Query.where member remains operational only as source-native v1 migration compatibility; its grammar changes with the adapter, so new code should use Query.filter. The experimental @honua/sdk-js/query-planner adds a schema-verified builder and explain plans over the same filter shape.

The same query envelope works against any GeoServices, OGC API Features, WFS, OData, or STAC endpoint. Migrating from esri-leaflet? The raw GeoServices API remains available, but its where member below is explicitly GeoServices SQL rather than the deprecated protocol-neutral Query.where:

import { HonuaClient } from "@honua/sdk-js";

const geoServicesClient = new HonuaClient({
  baseUrl: "https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis",
});
const { features } = await geoServicesClient.queryFeatures({
  serviceId: "2020_Census_State_Apportionment",
  layerId: 0,
  where: "1=1",
  outFields: ["*"],
  returnGeometry: true,
  resultRecordCount: 25,
});

Run the complete First Map app locally — public endpoint in, inspected MapLibre map out:

npm install
npm run demo:quickstart:mock   # deterministic fixture lane (what CI runs)
npm run demo:quickstart        # paste or configure an anonymous public endpoint

See docs/quickstart.md for the canonical guided server-optional walkthrough, docs/standalone-capability-matrix.md for the backend-agnostic vs server-enhanced breakdown, and examples/maplibre-quickstart/ for the committed source.

Add a Honua Server

A Honua Server unlocks server-authored MapPackages (loadMapPackage()), realtime subscriptions, collaboration / saved maps, and the MCP + AI surfaces. Point the same code at a local server (docker compose up in a honua-server checkout), and gate production reads on the compatibility check:

const { supported, reasons } = await client.checkCompatibility();
if (!supported) throw new Error(`Unsupported Honua server: ${reasons.join("; ")}`);

The server-connected lane is the maplibre-quickstart example (npm run demo:quickstart:mock); see docs/quickstart.md and docs/quickstart-troubleshooting.md.

Command-line client (honua)

Installing the SDK also installs a first-class honua CLI — the same querying and catalog browsing without writing code. It wraps the SDK (no raw HTTP, no URL-encoding, no f=json), prints readable tables by default, and adds --json / --format geojson for machine output.

npm i -g @honua/sdk-js            # or: npx @honua/sdk-js honua <command>
export HONUA_BASE_URL=https://demo.honua.io   # anonymous reads on the public demo

honua services                    # list published services
honua layers maui-parcels         # list a service's layers
honua query maui-parcels/1 --count
honua query maui-parcels/1 --where "tmk_txt LIKE '2%'" --limit 5
honua query maui-parcels/1 --bbox -156.7,20.7,-156.3,21.0 --format geojson
honua explain maui-parcels/1 --bbox -156.7,20.7,-156.3,21.0 --json   # the plan, no server call
honua stac collections
honua geocode "1 Honolulu Pl, HI"
honua map export maui-parcels --bbox -156.7,20.7,-156.3,21.0 --size 800x600 -o maui.png
honua tiles maui-parcels 12/912/1809 -o tile.png

The CLI's --where takes the source-native filter grammar by design (SQL for GeoServices, CQL2 for OGC) — it is a command-line convenience, not the deprecated protocol-neutral Query.where; honua explain shows exactly what any query compiles to before it runs. Authentication resolves from --api-key, HONUA_API_KEY, or a saved honua login. Run honua --help for the full command surface. This is the recommended command surface for docs and demos.

For support-safe interoperability evidence, honua doctor emits a local, schema-validated diagnostic bundle with explicit classification/consent, credential and PII redaction, bounded previews, and original-byte SHA-256 metadata. honua doctor --replay permits only one bounded, abortable GET/HEAD and fails closed before network access for mutations, subscriptions, unsafe paths, credentials, malformed schemas, or hash drift. It never uploads. See docs/diagnostic-bundles.md.

What you can build

The versioned SDK sample catalog tracks all 35 executable examples: 4 qualified golden samples, 13 recipes, 15 labs, and 3 fixtures. Seven journey IDs are reserved; 3 remain explicitly planned candidates. The catalog is the source of truth for track, support, lifecycle, fixture/live evidence, quality profiles, and the honua.io projection.

Linking to Honua from a plugin directory or ecosystem list? Point at First Map (hosted walkthrough, source) — CI keeps its fixture lane externally network-blocked and its release smoke green across Chromium, Firefox, and WebKit. Reusable directory entries — and the ledger of which ecosystem submissions were filed and accepted — live in docs/listings/maplibre-plugin-directory.md.

Whether any of that actually makes the packages findable is measured, not assumed: docs/listings/npm-search-verification.md records where every published @honua/* package ranks in npm registry search for its declared discovery terms, including the queries where it does not rank at all.

Mental model: DatasetSourceQueryResult

Every Honua SDK — JavaScript, Python, .NET — speaks the same canonical vocabulary. A Dataset groups one or more Sources. Each Source accepts a protocol-neutral Query and returns a protocol-neutral Result. Operations the canonical surface does not cover stay reachable through the typed source.protocol(...) escape hatch. Method casing differs by language (queryAll() / query_all() / QueryAllAsync()), the semantics do not.

Capability misses throw HonuaCapabilityNotSupportedError (under the default strict policy) rather than silently returning empty results. See the 60-second quickstart above for the runnable shape; the cross-language semantics, protocol/capability identifiers, language-binding tables, and backwards-compatibility policy live in:

Documentation

Platform-wide documentation (server concepts, deployment, Esri migration) lives at honua.gitbook.io/honuaio.

Run npm run docs:learning:verify from a fresh checkout to build the SDK and validate learning-path metadata, internal links, generated Markdown, and runtime imports. CI reuses its existing build with the internal npm run docs:learning:check command, then separately compiles every selected example through npm run docs:learning:typecheck. Run npm run docs:snippets:verify to build the public declarations and validate all supported JavaScript and TypeScript documentation fences.

AI assistants

Coding agents (Claude Code, Cursor, and compatible assistants) can discover and correctly use this SDK:

Stability and versioning

Support and lifecycle

We publish a lifecycle because a library you build on should tell you what it promises. esri-leaflet never did, and "will this break under me?" is the question that decides adoption.

Long-form reference material now lives in docs/guide.md:

Protocol-specific deep dives also live alongside the guide: see docs/wfs.md, docs/ogc-api.md, docs/maplibre-runtime.md, docs/webmap-json-compatibility.md, docs/protocol-capability-matrix.md, docs/migration-punch-list.md, and docs/widget-survival-guide.md (every ArcGIS widget deprecated at 5.0 mapped to its Honua/MapLibre disposition ahead of the 6.0 removal — run npm run scan:arcgis:widgets -- ./src for a per-file readiness report).

Repo What it is
honua-server Flagship multi-protocol geospatial server (ELv2 open core)
honua-console Unified web console — Studio, Catalog, Operate, Share
honua-sdk-python Python SDK (same Dataset/Source/Query/Result contract)
honua-sdk-dotnet .NET SDKs (same contract)
honua-esri-assess Esri footprint assessment CLI for migration discovery
geospatial-mcp Open, vendor-neutral geospatial MCP standard

Contributing

This SDK ships from a single repository: the canonical package is @honua/sdk-js (all subpath entrypoints in INSTALL.md live under that name), with the standalone packages in the table above built from the same source tree. The MCP server lives in mcp/. See AGENTS.md for contributor instructions and the Specifica issue format used for backlog items.

Security

Report vulnerabilities to security@honua.io — see the org security policy. Please do not open public issues for security reports.

License

Apache 2.0