Agent Native

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…

by datopianCommunityNot reviewed

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.”
The author's description, from SKILL.md

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-portaljs

Installs 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@d5c096aa6a4c24c19f28d112400d0489b18aeaf8

Installs 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.

Skill folder on GitHub at d5c096a

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 d5c096a

Files

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 d5c096a
  • 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

The skills listed here are third-party software, collected from public GitHub repositories as a catalog of recent skills and provided as is. Agent Native does not write or control them and is not responsible for any harm, loss or damage caused by installing or using them. Labels and checks describe what we saw at one commit and are not a guarantee. Read a skill's files before you run it. Terms of use