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.
npx skills add https://github.com/datopian/portaljs --skill arcgis-to-portaljs
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.
components/MapPreview.tsx andcomponents/GeoQuery.tsx (PR #1647 or later). Run portaljs-new-portal first if none.
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.
portaljs-deploy resolves), or an OSSself-hosted Giftless.
The canonical, full step-by-step workflow is
.claude/commands/arcgis-to-portaljs.md
— the single source of truth. Read and follow it when executing. Summary:
--limit,--only, --dry-run, --namespace-mode). Interview if missing; never dead-end.
ogr2ogr, tippecanoe, duckdb + spatial, jq). Any missing →print the per-OS install and stop.
/data.json (reuse the portaljs-migrate DCAT-US map) and classify eachitem: 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/.
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.
portaljs-add-geo recipe (PMTiles +GeoParquet); tabular items to Parquet. Preserve the native-CRS original.
datasets.json entries (upsert on (namespace, slug)).
arcgis-parity-report.md — record count, extent, attribute schema, and geometryvalidity, source vs derived, per dataset, plus the migrated/skipped/failed accounting.
data/<namespace>/<slug>.pmtiles, .parquet, and the original per vectordataset (all LFS-tracked → R2); Parquet + original per table; arcgis-parity-report.md.
datasets.json (one dual-tier entry per vector dataset, one resource entryper table); .gitattributes (LFS tracking).
/@<namespace>/<slug> renders <MapPreview> + <GeoQuery> for each vectordataset with no page edits; the catalog lists everything migrated.
| 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. |
/arcgis-to-portaljs https://hub-lewisville.opendata.arcgis.com slug=lewisville
/arcgis-to-portaljs https://streamwaterdata.co.uk --dry-run
/arcgis-to-portaljs https://streamwaterdata.co.uk --only sewer-catchments,water-boundaries --namespace-mode owner
.claude/commands/arcgis-to-portaljs.mdreferences/reference.mdreferences/sync-and-cutover.mdportaljs-migrate, portaljs-add-geo, portaljs-add-dataset, portaljs-deployRun Python code in the cloud with serverless containers, GPUs, and autoscaling. Use when deploying ML models, running batch processing jobs, scheduling compute-intensive tasks, or serving APIs that require GPU acceleration or dynamic scaling.
Advanced GitHub Actions workflow automation with AI swarm coordination, intelligent CI/CD pipelines, and comprehensive repository management
Google Cloud Platform CLI - manage GCP resources including Compute Engine, Cloud Run, GKE, Cloud Functions, Storage, BigQuery, and more.
Expert backend architect specializing in scalable API design, microservices architecture, and distributed systems. Masters REST/GraphQL/gRPC APIs, event-driven architectures, service mesh patterns, and modern backend frameworks. Handles service boundary definition, inter-service communication, resilience patterns, and observability. Use PROACTIVELY when creating new backend services or APIs.
Run Python code in the cloud with serverless containers, GPUs, and autoscaling. Use when deploying ML models, running batch processing jobs, scheduling compute-intensive tasks, or serving APIs that require GPU acceleration or dynamic scaling.
Aspire skill covering the Aspire CLI, AppHost orchestration, service discovery, integrations, MCP server, VS Code extension, Dev Containers, GitHub Codespaces, templates, dashboard, and deployment. Use when the user asks to create, run, debug, configure, deploy, or troubleshoot an Aspire distributed application.
Audits Python + BigQuery pipelines for cost safety, idempotency, and production readiness. Returns a structured report with exact patch locations.
Microsoft Store Developer CLI (msstore) for publishing Windows applications to the Microsoft Store. Use when asked to configure Store credentials, list Store apps, check submission status, publish submissions, manage package flights, set up CI/CD for Store publishing, or integrate with Partner Center. Supports Windows App SDK/WinUI, UWP, .NET MAUI, Flutter, Electron, React Native, and PWA applications.
Take datopian/arcgis-to-portaljs from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference brew, apt.
Without those the skill loads but fails at the first command.