Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Migrate (harvest) datasets between open-data platforms. Reads CKAN, a DCAT-US /data.json catalog (DKAN, ArcGIS Hub, data.gov), a DCAT / DCAT-AP RDF feed (JSON-LD, Turtle, or RDF/XML), Socrata, OpenDataSoft, or an ArcGIS FeatureServer, and writes them to a static PortalJS catalog or pushes them into a CKAN instance over its API. Use when moving datasets from an external open-data platform into a PortalJS portal, or bridging one CKAN instance to another.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | -24% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 5% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 2% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 40% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 41% | 0% |
Harvest datasets from an external open-data platform into a PortalJS portal, or push them into a CKAN instance. Every source reads into one canonical shape — { slug, namespace, name, description, resources[] } — then writes to the target from that shape, so adding a source covers every target automatically.
Supported sources: CKAN, a DCAT-US /data.json catalog (DKAN, ArcGIS Hub, data.gov), a DCAT / DCAT-AP RDF feed, Socrata, OpenDataSoft, or an ArcGIS FeatureServer/MapServer. Supported targets: a static PortalJS catalog (datasets.json, resources linked by URL or downloaded into Cloudflare R2) or a CKAN instance over its write API.
This is the copy-into-the-portal path, the inverse of /portaljs-connect-ckan (which reads the source live at build time): a one-time, re-runnable snapshot so the portal stands alone and needs no backend.
datasets.json,package.json, and pages/[owner]/[slug].tsx must exist (the portaljs-catalog template).
npm install).CKAN_API_KEY — never hardcoded.download copy mode: git-lfs and a PortalJS Arc account (or aself-hosted Giftless endpoint) to push resource files to R2.
dcat-rdf sources: lib/metadata/dcat-harvest.ts and dcat-profiles.ts(copy from examples/portaljs-catalog/lib/metadata/ if missing), run via npx tsx.
Full step-by-step workflow: .claude/commands/portaljs-migrate.md.
static/ckan), portaldirectory, copy mode (link/download), filters. Interview instead of erroring.
URL and API key authenticate.
--source) and verify reachability.the reference doc, applying the metadata hygiene rules (license sentinel — never fabricate CC-BY; description sanitizer + fallback chain; source dates preserved with migratedAt separate; cleanTitle display-name cleanup with slugs from the raw title; category from a meaningful theme).
link keeps sourceURLs; download copies files into the repo under Git LFS, pushed to R2.
--dry-run was passed.datasets.json (upsert or --replace), or push packages/resources intoCKAN over its action API.
npm run build (static) or re-query package_search (CKAN).Static target: an updated datasets.json (upserted on (namespace, slug)), optionally with resource files in Git LFS served from Cloudflare R2, plus a passing npm run build. CKAN target: created/updated packages and resources; per-dataset failures are logged and skipped, not fatal.
| Symptom | Cause | Fix | | --- | --- | --- | | "not a portaljs-catalog template" | datasets.json missing | Confirm the portal directory, or run /portaljs-new-portal | | CKAN target rejects every write (403) | Key missing, expired, or lacks org permission | Set a valid CKAN_API_KEY; stop rather than partially migrating | | Source URL matches no known format | Auto-detection failed | Pass --source explicitly (ckan, dcat, dcat-rdf, socrata, ods, arcgis) | | DCAT-RDF fetch returns HTML, not a feed | URL is a portal page, not the feed | Follow its <link rel="alternate"> href, or use the direct feed URL | | Build fails after write | Malformed entry (missing slug/namespace/name) | Inspect /tmp/portaljs-migrate-build.log, fix, rebuild | | download mode push fails | git-lfs missing, or no Arc/Giftless token | Install git-lfs; mint a token via the Arc API or self-hosted Giftless | | Duplicate datasets after re-running | --replace used unintentionally, or slug collisions | Omit --replace to upsert; keep slugs unique per namespace |
bash/portaljs-migrate --source ckan --source-url https://demo.dev.datopian.com \ --target static --portal-dir . --copy-mode link
Upserts all packages from the CKAN demo instance into datasets.json, referencing source resource URLs directly.
bash/portaljs-migrate --source dcat --source-url https://catalog.data.gov/data.json \ --target static --copy-mode download --org-filter epa-gov
Harvests one publisher and copies every resource file into the repo under Git LFS, served from Cloudflare R2.
bash/portaljs-migrate --source dcat-rdf \ --source-url https://data.europa.eu/api/hub/search/catalog.jsonld \ --target static --dry-run
Fetches the JSON-LD feed through the RDF harvester and previews the canonical datasets without writing anything.
bash/portaljs-migrate --source ckan --source-url https://old-portal.example.org \ --target ckan --target-url https://new-portal.example.org --owner-org research
Pushes every package from the old CKAN instance into the new one, filed under research.
.claude/commands/portaljs-migrate.mdreferences/reference.md/portaljs-connect-ckan, /portaljs-add-dcat,/portaljs-check-data-quality, /portaljs-define-schema
Other measured skills in the registry, with their headline benchmark lift.