arcgis-to-portaljs
Migrate an entire ArcGIS Hub open-data portal to PortalJS by harvesting the DCAT-US catalog, exporting each FeatureService layer via REST API, converting to…
Community author
- Agents
- Claude Code
- License
- MIT (permissive)
- Pin
- d5c096apinned Oct 9, 2026
- Context cost
- about 1,700 tokenswhen the skill loads
- GitHub stars
- 2,358on datopian/portaljs
- Scripts
- None found
What it does
“Migrate a whole ArcGIS Hub site into a PortalJS Arc portal end-to-end. Harvests the Hub /data.json (DCAT-US) inventory, exports every FeatureService layer through the ArcGIS REST query API with resultOffset paging, converts each to the serverless dual tier (PMTiles render + GeoParquet query) with tabular items to Parquet, pushes everything to Cloudflare R2 via Git LFS, appends dual-tier datasets.json entries, and writes a source-vs-derived parity report. Use to move a City or sector ArcGIS Hub open-data portal onto PortalJS with no server-side compute.”
Generated summary from the skill's own text
Migrate an entire ArcGIS Hub open-data portal to PortalJS by harvesting the DCAT-US catalog, exporting each FeatureService layer via REST API, converting to dual-tier serverless format (PMTiles and GeoParquet), and publishing to Cloudflare R2. The skill orchestrates the full pipeline on the operator's machine and generates a parity report comparing source and derived datasets.
When to use it
- Moving a city or sector ArcGIS Hub to a serverless PortalJS portal
- Preserving geographic data layers with no server-side compute required
- Converting ArcGIS REST FeatureServices to static spatial formats
- Migrating entire open-data catalogs with multiple vector and tabular datasets
When not to
- When target PortalJS portal lacks geo components like MapPreview and GeoQuery
- If native tools like GDAL, tippecanoe, duckdb, or jq are unavailable on your machine
- For incremental dataset updates rather than full site migration
Install
These commands install from the publisher's repository and need no account.
skills CLI
npx skills add datopian/portaljs --skill arcgis-to-portaljsInstalls from the repository's default branch, which may have changed since the commit we checked.
GitHub CLI 2.90 or later
gh skill install datopian/portaljs arcgis-to-portaljs@d5c096aa6a4c24c19f28d112400d0489b18aeaf8Installs the commit we checked (d5c096a).
Copy by hand
Download the folder at d5c096a and put it in .claude/skills/arcgis-to-portaljs for Claude Code and in .agents/skills/arcgis-to-portaljs for Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode.
Copy for your agent
A prompt for any coding agent. It installs this skill from GitHub at the pinned commit, copies the files as they are, and runs nothing unless you ask.
Prompt
Install the agent skill "arcgis-to-portaljs" by datopian. Source, pinned to commit d5c096aa6a4c24c19f28d112400d0489b18aeaf8: https://github.com/datopian/portaljs/tree/d5c096aa6a4c24c19f28d112400d0489b18aeaf8/skills/arcgis-to-portaljs 1. Download that folder at exactly this commit. Do not use the default branch. 2. Copy every file unchanged, keeping the folder structure, into .claude/skills/arcgis-to-portaljs/ for Claude Code and .agents/skills/arcgis-to-portaljs/ for Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode. Create the folders if they do not exist. 3. Do not edit the files, and do not run any script in the folder unless I ask you to. 4. When you are done, list the files you copied and where you put them. Listing and checks: https://www.agentnative.dev/skills/datopian/arcgis-to-portaljs
SKILL.md at the pin
---
name: arcgis-to-portaljs
description: Migrate a whole ArcGIS Hub site into a PortalJS Arc portal end-to-end. Harvests the Hub /data.json (DCAT-US) inventory, exports every FeatureService layer through the ArcGIS REST query API with resultOffset paging, converts each to the serverless dual tier (PMTiles render + GeoParquet query) with tabular items to Parquet, pushes everything to Cloudflare R2 via Git LFS, appends dual-tier datasets.json entries, and writes a source-vs-derived parity report. Use to move a City or sector ArcGIS Hub open-data portal onto PortalJS with no server-side compute.
allowed-tools: Read, Write, Edit, Bash(ogr2ogr:*), Bash(ogrinfo:*), Bash(tippecanoe:*), Bash(duckdb:*), Bash(git:*), Bash(curl:*), Bash(jq:*), Bash(npx:*), Bash(node:*), Bash(mkdir:*), Bash(cp:*), Bash(wc:*), Bash(command:*), WebFetch
version: 0.1.0
author: Datopian <hello@datopian.com>
license: MIT
compatibility: Claude Code with PortalJS portals (Next.js 14, React 18/19, Node 18+). Requires native GDAL, tippecanoe, duckdb, and jq on the operator's machine (macOS or Linux/WSL). Runs from any project via the plugin, a personal install, or a portaljs clone.
tags:
- portaljs
- data-portal
- migration
- arcgis
- geospatial
- pmtiles
---
# ArcGIS Hub → PortalJS
## Overview
Migrate an entire **ArcGIS Hub** open-data site into a **PortalJS Arc** portal in one pass.
Every Hub site is machine-readable — a DCAT-US catalog at `/data.json`, with every dataset
backed by an ArcGIS REST FeatureService — so migration is a **harvest → export → convert →
publish → verify** pipeline that runs almost fully automated on the operator's machine, no
server-side compute. The tooling is the reusable `arcgis-to-portaljs` migrator: input is one
Hub URL, output is a ready-to-deploy PortalJS catalog plus a parity report.
The skill is an orchestrator: it reuses the DCAT-US harvest from `portaljs-migrate`, the
`ogr2ogr`/`tippecanoe`/`duckdb` dual-tier conversion from `portaljs-add-geo`, and the bulk
Git-LFS → R2 push from `portaljs-migrate`. Its novel parts are the FeatureService REST export
loop (paged features, not just a link) and the source-vs-derived parity report.
## Prerequisites
- A scaffolded PortalJS portal whose template ships `components/MapPreview.tsx` and
`components/GeoQuery.tsx` (PR #1647 or later). Run `portaljs-new-portal` first if none.
- Native CLIs: **GDAL** (`ogr2ogr`, `ogrinfo`), **tippecanoe**, **duckdb** (with `spatial`),
and **jq**. macOS: `brew install gdal tippecanoe duckdb jq`; Debian/Ubuntu:
`apt-get install gdal-bin duckdb jq` plus tippecanoe (apt or build from source); Windows via
WSL. The skill hard-stops with the install hint if any is missing.
- Arc credentials for the Git-LFS → R2 push (the token `portaljs-deploy` resolves), or an OSS
self-hosted Giftless.
## Instructions
The canonical, full step-by-step workflow is
[`.claude/commands/arcgis-to-portaljs.md`](https://github.com/datopian/portaljs/blob/main/.claude/commands/arcgis-to-portaljs.md)
— the single source of truth. Read and follow it when executing. Summary:
1. Gather input — Hub URL, portal directory, project slug, optional flags (`--limit`,
`--only`, `--dry-run`, `--namespace-mode`). Interview if missing; never dead-end.
2. Check native tools (`ogr2ogr`, `tippecanoe`, `duckdb` + `spatial`, `jq`). Any missing →
print the per-OS install and stop.
3. Validate the portal directory and confirm the geo showcase components exist.
4. Harvest the Hub `/data.json` (reuse the `portaljs-migrate` DCAT-US map) and classify each
item: vector (FeatureService), table, or non-data (web map / 3D / imagery → skipped).
Under `--namespace-mode owner`, resolve namespaces through a publisher-normalization
table with title-prefix fallback for broken `{{source}}` publishers (multi-publisher
Hubs ship dirty publisher labels). Dedup near-duplicate hosted-`view` layers — but only
after a mandatory live record-count check on BOTH twins: equal ⇒ dedup (keep the source
layer, log the pair); different ⇒ keep both as distinct datasets. Consolidate per-year
dataset series into one year-partitioned Parquet with legacy per-year view entries. Enrich
from the AGOL item: sanitized metadata (license/description/dates), cleaned display title
(`cleanTitle` — raw title still drives the slug), `category` (item categories → meaningful
theme → keyword mapping), and a `thumbnail` snapshot into `public/thumbnails/`.
5. Export each vector layer through the ArcGIS REST `query` API with `resultOffset` paging
(`f=geojson`, `outSR=4326`); fall back to keyset paging on transfer limits; accept a
customer File Geodatabase dump for very large layers.
6. Convert each layer to the dual tier via the `portaljs-add-geo` recipe (PMTiles +
GeoParquet); tabular items to Parquet. Preserve the native-CRS original.
7. Publish — bulk Git-LFS track + one push to R2 through Giftless, then append dual-tier
`datasets.json` entries (upsert on `(namespace, slug)`).
8. Write `arcgis-parity-report.md` — record count, extent, attribute schema, and geometry
validity, source vs derived, per dataset, plus the migrated/skipped/failed accounting.
9. Report the inventory, migrated datasets, R2 push, and parity summary.
## Output
- **Created:** `data/<namespace>/<slug>.pmtiles`, `.parquet`, and the original per vector
dataset (all LFS-tracked → R2); Parquet + original per table; `arcgis-parity-report.md`.
- **Modified:** `datasets.json` (one dual-tier entry per vector dataset, one resource entry
per table); `.gitattributes` (LFS tracking).
- **Verified:** the parity report compares each derived artifact to the live FeatureService.
- **Result:** `/@<namespace>/<slug>` renders `<MapPreview>` + `<GeoQuery>` for each vector
dataset with no page edits; the catalog lists everything migrated.
## Error Handling
| Symptom | Cause | Fix |
| --- | --- | --- |
| `MISSING_INPUT` | No Hub URL provided | Pass the site root (e.g. `https://hub-lewisville.opendata.arcgis.com`) and retry. |
| `MISSING_TOOLS` | `ogr2ogr`/`tippecanoe`/`duckdb`/`jq` (or duckdb `spatial`) absent | Print the per-OS install line and stop; re-run after installing. |
| `NOT_A_PORTAL` | Target dir has no `datasets.json` / geo components | Run `portaljs-new-portal` first, then re-run. |
| `HARVEST_FAILED` | `/data.json` unreachable or not DCAT-US | Confirm the site is an ArcGIS Hub and the feed loads in a browser. |
| `EXPORT_FAILED` | One FeatureService layer errored or hit a hard transfer cap | Logged and skipped; try keyset paging or a customer FGDB dump for that layer. |
| `LFS_PUSH_FAILED` | Missing/expired Arc token or unset `lfs.url` | Re-mint the JWT (see `portaljs-deploy`); confirm `git config lfs.url`. |
## Examples
### Example 1 — Migrate a City Hub (Lewisville)
```
/arcgis-to-portaljs https://hub-lewisville.opendata.arcgis.com slug=lewisville
```
### Example 2 — Dry-run inventory + plan only (no writes)
```
/arcgis-to-portaljs https://streamwaterdata.co.uk --dry-run
```
### Example 3 — Migrate a subset, one namespace per publisher
```
/arcgis-to-portaljs https://streamwaterdata.co.uk --only sewer-catchments,water-boundaries --namespace-mode owner
```
## Resources
- Full workflow: [`.claude/commands/arcgis-to-portaljs.md`](https://github.com/datopian/portaljs/blob/main/.claude/commands/arcgis-to-portaljs.md)
- REST export, classification, parity, and phasing details: [`references/reference.md`](references/reference.md)
- Ongoing sync, parity dashboard, and cutover (Phase 3): [`references/sync-and-cutover.md`](references/sync-and-cutover.md)
- Related skills: `portaljs-migrate`, `portaljs-add-geo`, `portaljs-add-dataset`, `portaljs-deploy`
- ArcGIS REST query API: <https://developers.arcgis.com/rest/services-reference/enterprise/query-feature-service-layer/> · tippecanoe: <https://github.com/felt/tippecanoe> · DuckDB spatial: <https://duckdb.org/docs/extensions/spatial>
From datopian/portaljs at d5c096a, shown as plain text under its license: MIT (permissive). The license text is in skills/arcgis-to-portaljs/SKILL.md.
View SKILL.md on GitHub at d5c096aFiles
- SKILL.md8.0 KB · 3034dc54ee0a
- references/
- reference.md5.5 KB · d6a9592fd57e
- sync-and-cutover.md15 KB · 9e4ed6a9be69
3 files, 28 KB in total. Each file has a sha256 in the pin's manifest; the first 12 characters are shown.
Skill folder on GitHub at d5c096aRelated in Data and databases
basin
This skill helps agents build and troubleshoot Cloudflare Basin analytics workflows, which ingest events, transform data, manage Apache Iceberg tables in R2…
by CloudflareOfficial publisher
Claude Code, Codex, Cursor +4 · about 600 tokens · pinned 6 days ago
data-table-manager
This skill manages n8n Data Tables through creation, import, querying, and mutation operations.
by n8nOfficial publisher
Claude Code, Codex, Cursor +3 · about 2.1K tokens · pinned 6 days ago
algolia-automation
This skill automates Algolia search and indexing operations through the Rube MCP interface.
by ComposioOfficial publisher
Claude Code, Codex, Cursor +3 · about 690 tokens · pinned 6 days ago
expo-sqlite
This skill enables SQLite database operations in Expo and React Native apps for structured data storage, offline queues, and persistent caches.
by ExpoOfficial publisher
Claude Code, Codex, Cursor +4 · about 2.6K tokens · pinned 6 days ago
prisma-8
This skill helps agents work with Prisma 8, a contract-first data layer for TypeScript projects.
by PrismaOfficial publisher
Contains scripts
Claude Code, Codex, Cursor +3 · about 3.5K tokens · pinned 6 days ago
analyzing-experiment-session-replays
This skill enables agents to analyze session recordings from experiment variants to identify behavioral differences between control and test groups.
by PostHogOfficial publisher
Claude Code, Codex, Cursor +4 · about 2.3K tokens · pinned 6 days ago