portaljs
Agent skills that build data portals — scaffold a portal, add datasets, charts and maps, connect CKAN, and generate DCAT/Croissant metadata
Installation
Open Claude Code and run this command:
/plugin install portaljs@claude-code-plugins-plus
Use --global to install for all projects, or --project for current project only.
What It Does
PortalJS
The AI-native framework for building data portals.
Describe the portal you want — your agent helps you choose an architecture, scaffolds it, and loads your data.
·
·
Skills (14)
Migrate a whole ArcGIS Hub site into a PortalJS Arc portal end-to-end.
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.tsxand
components/GeoQuery.tsx (PR #1647 or later). Run portaljs-new-portal first if none.
- Native CLIs: GDAL (
ogr2ogr,ogrinfo), tippecanoe, duckdb (withspatial),
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-deployresolves), or an OSS
self-hosted Giftless.
Instructions
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:
- Gather input — Hub URL, portal directory, project slug, optional flags (
--limit,
--only, --dry-run, --namespace-mode). Interview if missing; never dead-end.
- Check native tools (
ogr2ogr,tippecanoe,duckdb+spatial,jq). Any missing →
print the per-OS install and stop.
- Validate the portal directory and confirm the geo showcase components exist.
- Harvest the Hub
/data.json(reuse theportaljs-migrateDCAT-US map) and classify each
item: vector (FeatureSe
Add a chart (line, bar, area, pie, or scatter) to a dataset's showcase in a PortalJS portal.
PortalJS — Add Chart
Overview
Add a visualization to a dataset's showcase in a PortalJS portal. The skill installs
recharts (added directly — never @portaljs/components), writes a reusable client-side
Chart component into components/, and renders a into the Views section
of the showcase route pages/[owner]/[slug].tsx for one chosen dataset. The chart reads the
same /public/data/ the showcase already uses, so no data is duplicated.
Five chart types are supported: line, bar, area, pie, and scatter.
Prerequisites
- A scaffolded PortalJS portal (see
portaljs-new-portal). - The target dataset already registered in
datasets.json(seeportaljs-add-dataset). components/ui/parseCsv.tspresent (ships with the template).- Node 18+ and npm available in the portal directory.
Instructions
The canonical, full step-by-step workflow is
.claude/commands/portaljs-add-chart.md —
the single source of truth. Read and follow it when executing. Summary:
- Gather input — dataset slug, X column, Y column(s), chart type (default
line), portal
directory. If any is missing, interview the user; never dead-end on a missing value.
- Resolve the dataset from
datasets.json(namespace,file,format). - Validate that the X and every Y column exist in the data file's header; warn on
non-numeric Y columns (values are coerced with Number()).
- Install the chart library:
npm install recharts@^2.15.0. - Write
components/Chart.tsx(idempotent — do not overwrite a customized component). - Render
into the Views section, gated on the dataset's(namespace, slug)so
other showcases are unaffected. Extend an existing view-dispatch block, do not overwrite.
- Verify with
npx tsc --noEmit. - Report the component, route, and dependency added.
Output
- Created:
components/Chart.tsx(recharts wrapper, if absent). - Modified:
pages/[owner]/[slug].tsx(import + gatedin Views);
package.json (recharts@^2.15.0).
- Verified:
npx tsc --noEmitpasses. - Result: the chart renders at
/@under a "Views" heading./
Error Handling<
Add a dataset (CSV, TSV, JSON, or GeoJSON) to an existing PortalJS portal.
PortalJS — Add Dataset
Overview
Register a dataset in a PortalJS (portaljs-catalog) portal. The skill appends one entry to
datasets.json — the single source of truth for the catalog — and routes the underlying
bytes by source first, then size: a local file defaults to R2 via Git LFS, a remote URL
defaults to passthrough (no copy). No per-dataset page is created; the catalog at /search
lists the new entry and the dynamic showcase route pages/[owner]/[slug].tsx renders it
automatically at /@. Supported formats for the showcase preview: CSV,
TSV, JSON (array), and GeoJSON.
Prerequisites
- A scaffolded PortalJS portal (see
portaljs-new-portal) withdatasets.json,
package.json, and pages/[owner]/[slug].tsx present.
- The source data: a local file path or a publicly reachable URL.
- For the R2/Git LFS default route:
gitandgit-lfsinstalled, and an Arc account token
(or an OSS Giftless key) to mint a push-scoped LFS credential.
- Node 18+ and npm available in the portal directory.
Instructions
The canonical, full step-by-step workflow is
.claude/commands/portaljs-add-dataset.md —
the single source of truth. Read and follow it when executing. Summary:
- Gather input from
$ARGUMENTS— source (file path or URL), portal directory (default
.), dataset name/slug, description, namespace. If the source is missing, interview the
user; never dead-end.
- Validate the portal directory: confirm
datasets.json,package.json, and
pages/[owner]/[slug].tsx exist.
- Detect the format from the file extension, URL extension, or
Content-Typeheader
(CSV, TSV, JSON array, or GeoJSON); reject anything else and ask for a conversion.
- Route the data by source: remote URL → passthrough (default) or adopt into R2 (opt-in);
local file → R2 via Git LFS (default) or inline into public/data/ (fenced exception for
bundled samples or an OSS-no-R2 fallback).
- Append one entry to
datasets.json—slug,namespace,name,description,file
(the routed path/URL), format — keeping (namespace, slug) unique.
- Verify the build with
npx next build; fix errors (commonly malformed JSON) before
reporting success.
- Report the route taken, the manifest change,
Make a PortalJS portal harvestable by national/EU/US open-data portals — emit standards-compliant DCAT catalog feeds (DCAT 2/3, DCAT-AP, DCAT-US, national profiles) in JSON-LD, Turtle, and RDF/XML at build, with autodiscovery and per-profile conformance checking.
PortalJS — Add DCAT
Overview
Turn an existing PortalJS (portaljs-catalog) portal into a harvestable data catalog.
PortalJS is Frictionless-native — a dataset is a Data Package (see
portaljs-define-schema) — and DCAT is the serialization + harvest layer on top
(lib/metadata/dcat.ts + lib/metadata/dcat-profiles.ts). This skill selects one or
more DCAT application profiles, maps every dataset's metadata to them, and writes
static feed files at build time — JSON-LD, Turtle, and RDF/XML — so external catalogs
(data.europa.eu, data.gov, national portals) can harvest the datasets automatically,
on any static host, with no runtime.
Prerequisites
- A scaffolded PortalJS portal with
datasets.json,package.json, and
lib/metadata/ (the metadata-profile contract) present.
lib/metadata/dcat.ts(the DCAT-3 core) already in place — profiles augment it.- Node 18+ and npm available in the portal directory.
- For DCAT-AP / DCAT-US: a publishing organization (name + homepage) and a contact
(name + email) — both profiles require dct:publisher and dcat:contactPoint.
- Optional but recommended: network access to run SHACL conformance checks against the
official EU ITB validator or pyshacl.
Instructions
The canonical, full step-by-step workflow is
.claude/commands/portaljs-add-dcat.md —
the single source of truth. Read and follow it when executing. Summary:
- Gather input from
$ARGUMENTS(interview if thin): portal directory (default.),
profiles (default ["dcat-3"]), site URL, publisher, contact, license, themes,
languages, access level.
- Validate the portal directory: confirm
datasets.json,package.json, and
lib/metadata/ exist; stop with an ERROR: if the metadata contract is missing.
- Ensure the DCAT profile layer is present —
dcat-profiles.ts,dcat-rdf.ts,
dcat-validate.ts — copying canonical versions from examples/portaljs-catalog if
the portal predates this skill.
- Ensure
scripts/generate-dcat.tsis wired topredev/prebuildand emits
per-profile x serialization feeds from dcat.config.json.
- Write
dcat.config.jsonwith the gathered profiles, publisher, contact, license,
themes, and access level.
- Add feed autodiscovery: a portaljs-add-geo View full skill →
Auto-ingest a geospatial file (GeoJSON, Shapefile, GeoPackage, KML/KMZ, FlatGeobuf, CSV-with-geometry) into a PortalJS portal on the user's own machine, with no server.
ReadWriteEditBash(ogr2ogr:*)Bash(ogrinfo:*)Bash(tippecanoe:*)Bash(duckdb:*)Bash(git:*)Bash(curl:*)Bash(npx:*)Bash(node:*)Bash(mkdir:*)Bash(cp:*)Bash(wc:*)Bash(command:*)WebFetchPortalJS — Add Geo
Overview
Turn one geospatial upload into a dual-tier PortalJS dataset entirely on the user's
machine — no server-side container compute, keeping the zero-backend bet end-to-end.
From a single source the skill derives two serverless tiers plus the preserved original:
- PMTiles (render tier) — a vector-tile archive
renders with MapLibre GL
over HTTP range requests; any dataset size pans and zooms with no tile server.
- GeoParquet (query tier) — a GeoParquet 1.1 file (covering
bboxcolumn, Hilbert-sorted)
runs spatial SQL over in place via DuckDB-Wasm.- Original — the untouched upload in its native CRS, kept downloadable.
All three land on Cloudflare R2 (Git LFS → Giftless), and the skill appends one
datasets.jsonentry whoseresources[]match the shape the showcase auto-renders (the@reference/world-boundariesdemo, PR #1647):pmtiles→,geoparquet→, original → download. No page edits are needed. It automates the manualtippecanoe/duckdbrecipes in the template README.Prerequisites
- A scaffolded
portaljs-catalogportal whose template shipscomponents/MapPreview.tsxand
components/GeoQuery.tsx(PR #1647 or later).- Native CLIs on the user's machine: GDAL (
ogr2ogr,ogrinfo), tippecanoe, and
duckdb (with the
spatialextension). macOS:brew install gdal tippecanoe duckdb;Debian/Ubuntu:
apt-get install gdal-bin duckdb+ tippecanoe (apt or build from source);Windows: use WSL. The skill hard-stops with the install hint if any is missing.
- Arc credentials for the Git-LFS → R2 push (the same token
portaljs-deployresolves), or
an OSS self-hosted Giftless.
Instructions
The canonical, full step-by-step workflow is
.claude/commands/portaljs-add-geo.md— the single source of truth. Read and follow it when executing. Summary:
- Gather input — source (geo file path or URL), portal directory, namespace. Interview if
missing; never dead-end.
- Detect the native tools (
ogr2ogr,tippecanoe,duckdb+spatial). Any missing →
print the per-OS install and stop.
- Validate the portal directory and co
- PMTiles (render tier) — a vector-tile archive
Render a GeoJSON dataset on an interactive Leaflet map in the Views section of a dataset's showcase.
PortalJS — Add Map
Overview
Add an interactive Leaflet map as a view on a dataset's showcase in a
portaljs-catalog portal. The skill installs react-leaflet/leaflet (added directly —
never @portaljs/components), writes a reusable Map component split across
MapView.tsx (the Leaflet code) and Map.tsx (a dynamic(..., { ssr: false }) wrapper,
since Leaflet touches window at module load), and renders into the Views
section of the showcase route pages/[owner]/[slug].tsx for one chosen GeoJSON dataset.
The dataset should already carry format: "geojson" in datasets.json; if it doesn't
exist yet, the skill can copy the file and register it first.
Prerequisites
- A scaffolded PortalJS portal (see
portaljs-new-portal). - The target dataset registered in
datasets.jsonwithformat: "geojson"(see
portaljs-add-dataset), or a GeoJSON source file/URL to register.
- Node 18+ and npm available in the portal directory.
Instructions
The canonical, full step-by-step workflow is
.claude/commands/portaljs-add-map.md — the
single source of truth. Read and follow it when executing. Summary:
- Gather input — dataset slug (or a GeoJSON source to register), portal directory. If any
is missing, interview the user; never dead-end on a missing value.
- Validate the portal directory (
datasets.json,package.json,
pages/[owner]/[slug].tsx must exist).
- Resolve the dataset from
datasets.json, or validate and register a new GeoJSON
source (copy to /public/data/, append a manifest entry).
- Install map dependencies:
npm install react-leaflet@^5 leaflet@^1.9and
npm install -D @types/leaflet (skip if already present).
- Write
components/MapView.tsxandcomponents/Map.tsx(idempotent — skip if
Map.tsx already exists).
- Render
into the Views section, gated on the dataset's(namespace, slug)so
other showcases are unaffected. Extend an existing view-dispatch block, do not
overwrite it.
- Verify with
npx tsc --noEmit(nevernext buildagainst a live dev server). - Report the component, route, and dependency added.
Output
- Created:
components/MapView.tsx
Add another file (resource) to an EXISTING dataset in a PortalJS portal — a data dictionary, methodology, or an additional data file.
PortalJS — Add Resource
Overview
Add a resource — an additional file — to a dataset that already exists in a
portaljs-catalog portal. Where /portaljs-add-dataset creates a new dataset
(one file), this skill adds a file to an existing one: a data dictionary, a
methodology document, or another data file (e.g. a second year's figures).
Mirrors the Frictionless Data Package model: a dataset holds a resources[] array,
and the showcase at /@ renders one section per resource (preview,
schema, download). A single-file dataset migrates to resources[] automatically the
first time a second file is added — no data is lost.
Prerequisites
- An existing PortalJS portal (
portaljs-catalogtemplate) withdatasets.json,
package.json, and pages/[owner]/[slug].tsx present.
- The target dataset already registered in
datasets.json. - The new resource's source: a local file path or a public URL, in CSV, TSV, JSON
(array), or GeoJSON format.
- Node 18+ installed to run
npx next buildfor verification.
Instructions
The canonical, full step-by-step workflow lives in
.claude/commands/portaljs-add-resource.md
in this repository — that file is the single source of truth. Read and follow it. Summary:
- Gather input (interview if thin):
DATASET(slug ornamespace/slug),SOURCE
(path or URL), PORTALDIR (default .), RESOURCENAME, RESOURCE_TITLE,
DESCRIPTION. If DATASET or SOURCE is missing, list datasets from
datasets.json and ask.
- Validate the portal and locate the dataset entry by slug (and namespace, if
given). If the dataset does not exist, offer /portaljs-add-dataset instead.
- Detect the format from extension/Content-Type, fetch (check HTTP status) or
confirm the local path exists, then copy into PORTAL_DIR/public/data/ under a
non-colliding filename.
- Update
datasets.json: if the dataset has noresourcesyet, migrate its
top-level file/format/schema into the first resource (lossless), then
append the new resource; if resources already exists, just append, keeping
name unique within the array.
- Verify the build with
npx next build, capturing output to a log file; f
Recommend a data-portal architecture (storage, compute, catalog, access, hosting, metadata) from stated needs, then hand off to the build skills.
PortalJS — Architect
Overview
The advisory entry point for a PortalJS project. Before anything gets scaffolded, this
skill works out what to build: given the kind of portal, the shape of the data, and its
purpose, it fills six architecture slots (storage, catalog, compute, access, hosting,
metadata), resolves two build-time knobs (per-dataset data tier and the portal-wide
DATA_QUERY mode), and hands off to the concrete build skills. It decides; it does not
build. When the brief is thin it interviews in short rounds and never dead-ends — every
question has a sensible default, reachable by replying "use defaults."
Prerequisites
- A rough idea of the portal's purpose and data (exact numbers are not required — the
interview supplies defaults for anything missing).
- Optional: local files or a directory of sample data to inspect for size and shape.
- No PortalJS project needs to exist yet; this skill runs before scaffolding.
Instructions
The canonical, full step-by-step workflow lives in
.claude/commands/portaljs-architect.md —
that file is the single source of truth. Follow it when executing this skill:
- Parse
$ARGUMENTSfor anything already specified, then interview for what's
missing, one round at a time: (1) what's being built, (2) what the data is,
(3) what it's for, (4) constraints. Accept "use defaults" at any point. Inspect
named files/directories with du -sh and line counts to ground size guesses.
- Derive the recommendation by matching the answers against the decision tables —
Storage/Catalog/Compute by data volume and query needs, Access/Hosting by public
vs. private, Metadata by standards-compliance needs — then resolve the two
build-time knobs: per-dataset data tier (inline | LFS | external) and the
portal-wide DATA_QUERY mode (flat | duckdb).
- Echo the architecture brief (stack, reasoning per slot, deviations from default,
deferred items) and wait for confirmation ("go") or corrections.
- On confirmation, persist the brief to
./ARCHITECTURE.mdin the working directory. - Hand off to the build skills —
/portaljs-new-portal,/portaljs-add-dataset,
/portaljs-connect-ckan, /portaljs-define-schema, /portaljs-deploy — mapped
from the brief, and offer to run the first one.
Output
- Created:
./ARCHITECTURE.mddocumenting the six slots, the two build-time
knobs, the reasoning, and anything
Audit a local or remote tabular file (CSV/TSV) for common data quality issues — schema, nulls, types, duplicates.
PortalJS — Check Data Quality
Overview
Run a read-only quality audit of one CSV or TSV file, local or remote, and return a
structured JSON report. The audit profiles every column — null/blank counts, inferred
value types, numeric ranges, likely year/date fields — and flags duplicate rows,
duplicate values in identifier-like columns, ambiguous overlapping year columns (e.g.
calendar year vs fiscal year), and mixed-type columns. It never edits the source
file, datasets.json, or any other project file; it only reads the target file (a
remote URL is downloaded to a temp file that is deleted before the run ends) and
prints a report. Use it before publishing a dataset with portaljs-add-dataset, or to
diagnose why a showcase renders wrong.
Prerequisites
python3onPATH— the audit logic runs as an embedded Python script; nothing is
installed.
- One CSV or TSV file, given as a local path or an
http/httpsURL. Only one file
per run.
Instructions
The canonical, full step-by-step workflow is
.claude/commands/portaljs-check-data-quality.md —
the single source of truth. Read and follow it when executing. Summary:
- Gather input — the file path or URL to audit. If missing, ask for it; never dead-end.
- Resolve the source: if it's an
http/httpsURL, download it to a temp file first;
otherwise use the local path as given.
- Validate the extension is
.csvor.tsv. If not, or the file is missing, or the
header row is empty, stop and surface the error JSON as-is — do not guess a fix.
- Profile every column: null/blank counts, distinct values, sample values, inferred
per-value type (boolean/integer/float/date/string), numeric min/max, and year
range for columns whose name looks year-like.
- Derive findings from the profiles — duplicate rows, missing-value ratios, invalid
year values, mixed types, suspect negative values, duplicate identifier values, and
ambiguous overlapping year columns — each tagged critical, warning, or info.
- Assemble the JSON report (
status, file metadata,findings,recommendations,
column_profiles), print it, and clean up the temp file if one was created.
- Relay the report to the user as-is; do not modify the source file,
datasets.json,
or any other project file based on the findings — that's a separate, explicit step.
Output<
Wire a scaffolded PortalJS portal to a CKAN backend over its API.
PortalJS — Connect CKAN
Overview
Connect an existing portaljs-catalog portal to a live CKAN backend for the "decoupled /
any backend" path. The portal stops reading the static datasets.json manifest (and files
in /public/data/) and instead feeds its two data surfaces — the /search catalog and
the /@namespace/slug showcases — straight from a CKAN instance's REST API
(packagesearch / packageshow) through a generated fetch client. Output is plain,
editable Next.js code with no runtime dependency — never @portaljs/ckan, whose bundle
wires React UI components to React 18 internals and crashes at import under the template's
React 19. Pages fetch CKAN server-side in getStaticProps/getStaticPaths, so the catalog
is pre-rendered at build time and the site can still be statically deployed. Run this right
after portaljs-new-portal to swap a freshly scaffolded portal's sample data over to CKAN.
Prerequisites
- A scaffolded PortalJS portal (see
portaljs-new-portal) withpackage.json,pages/,
datasets.json, pages/search.tsx, and pages/[owner]/[slug].tsx present.
- A CKAN base URL that is publicly reachable, e.g.
https://demo.dev.datopian.com. - Node 18+ and npm available in the portal directory (no new packages are installed).
Instructions
The canonical, full step-by-step workflow is
.claude/commands/portaljs-connect-ckan.md —
the single source of truth. Read and follow it when executing. Summary:
- Gather input from
$ARGUMENTS— CKAN base URL (required), org filter (optional), group
filter (optional), portal directory (default .). If the URL is missing, interview the
user; never dead-end with a missing-input error.
- Validate the target directory is a
portaljs-catalogportal; if not, suggest
portaljs-new-portal instead of failing silently.
- Verify the CKAN backend is reachable via
package_search?rows=1, and validate each org
filter via organization_show; on failure, explain and re-prompt rather than dead-ending.
- Generate
lib/ckan.ts— a self-contained server-side fetch client wrapping
packagesearch and packageshow, with DMS, ORGFILTER, GROUPFILTER, and
MAX_DATASETS as editable constants.
Define a dataset's metadata profile — infer a Frictionless Table Schema from its data, add Data Package metadata (license, sources, keywords), and write it into datasets.
PortalJS — Define Schema
Overview
Define a dataset's metadata profile — the authoring skill for the metadata-profile
contract (lib/metadata). Where portaljs-add-dataset registers that a dataset exists,
this skill describes what its data means: infer a Frictionless Table Schema (fields,
types, constraints) from sampled data, add the Data Package fields a catalog surfaces
(title, licenses, sources, keywords), and write them onto the dataset's entry in
datasets.json. The showcase at /@ then renders a typed field table
instead of a bare preview. The model is Frictionless-native; DCAT is a serialization layer
built on top later, not authored here.
The skill runs on a profile ladder — reach for higher levels only when needed:
| Level | What it is | When |
|---|---|---|
| L0 | Default frictionless-tabular profile; declare schema + metadata. |
Default. Standard tabular CSV/TSV. |
| L1 | L0 plus extra descriptive package fields. | Extra metadata, standard validation is fine. |
| L2 | Fully custom profile (own schema template + validate()). |
A dataset type needing custom validation rules. |
| L3 | Multiple registered profiles, resolved per dataset. | A portal mixing dataset types. |
The skill is interactive and never dead-ends: if input is thin it interviews in short
rounds, infers defaults from the data, echoes the schema for confirmation, and accepts
"use defaults" to proceed with the inferred schema as-is.
Prerequisites
- A scaffolded PortalJS portal with the metadata contract (
lib/metadata/types.ts,
pages/[owner]/[slug].tsx); see portaljs-new-portal.
- The target dataset already registered in
datasets.json(seeportaljs-add-dataset). - For tabular schema inference, the dataset's CSV/TSV file present under
PORTAL_DIR/public/data/. JSON/GeoJSON datasets get package metadata only — no fields.
- Node 18+;
tsxoptional, used for the schema-validation check.
Instructions
The canonical, full step-by-step workflow is
.claude/commands/portaljs-define-schema.md —
the single source of truth. Read and follow it when executing. Summary:
- Gather
PORTAL_DIR,DATASET(slug ornamespace/slug), andLEVEL(defaultL0)
Deploy a PortalJS portal to PortalJS Arc — Datopian-managed static hosting on Cloudflare.
PortalJS — Deploy
Overview
Publish an existing PortalJS portal to PortalJS Arc — Datopian's managed static
hosting on Cloudflare. Build a static export, upload it to the Arc API, and print a live
https://SLUG.arc.portaljs.com URL. Re-running redeploys the same portal (idempotent on
the slug). This is a single-target skill — it deploys to Arc only. For self-hosting, run
npm run build and upload out/ to any static host; no skill required for that path.
Arc serves static exports only — SSR is not hosted on Arc yet.
Prerequisites
- A PortalJS portal directory with a
package.jsonthat listsnextas a dependency. - Node 18+ and npm on PATH (the Arc device-login flow uses Node's global
fetch). curlandtaravailable for packaging and upload.- A PortalJS Arc token — read from
PORTALJS_TOKEN, or~/.portaljs/credentials
({"token":"…"}). If neither exists, the skill signs in on demand via a device-code
flow; no manual token copying required.
- If the portal stores large data in Git LFS/R2, do not run
git lfs pullbefore
deploying — large datasets are served from Cloudflare R2 via absolute URLs in
datasets.json, not copied into the export.
Instructions
The canonical, full step-by-step workflow is
.claude/commands/portaljs-deploy.md — the
single source of truth. Read and follow it when executing. Summary:
- Gather input — portal directory (default
.) and slug (default frompackage.json
name or directory name, slugified). Confirm the directory is a Next.js project; reject
reserved slugs (www, api, admin, staging, arc).
- Resolve the Arc token: read
PORTALJS_TOKEN, else~/.portaljs/credentials; if
missing, run the device-authorization sign-in flow and save the returned token.
- Ensure
next.config.jssetsoutput: 'export'andimages: { unoptimized: true },
then run npm run build; stop if the build fails.
- Verify the export carries no dataset bytes — run
npm run check-export(or
scripts/check-export.mjs) to catch Git LFS pointer leaks and oversized data files.
- Tar the
out/directory andPOSTit to$PORTALJSARCAPI/v1/deploy?slug=
with the bearer token; handle 200/401/409/400/413 responses distinctly.
- Repo
Migrate (harvest) datasets between open-data platforms.
PortalJS — Migrate
Overview
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.
Prerequisites
- An existing PortalJS portal for the static target:
datasets.json,
package.json, and pages/[owner]/[slug].tsx must exist (the portaljs-catalog
template).
- Node 18+ and npm, with portal dependencies installed (
npm install). - Network access to the source platform's API or feed URL.
- For a CKAN target: a write API key in
CKANAPIKEY— never hardcoded. - For
downloadcopy mode:git-lfsand a PortalJS Arc account (or a
self-hosted Giftless endpoint) to push resource files to R2.
- For
dcat-rdfsources:lib/metadata/dcat-harvest.tsanddcat-profiles.ts
(copy from examples/portaljs-catalog/lib/metadata/ if missing), run via npx tsx.
Instructions
Full step-by-step workflow:
.claude/commands/portaljs-migrate.md.
- Gather input — source type, source URL, target (
static/ckan), portal
directory, copy mode (link/download), filters. Interview instead of erroring.
- Validate the target — confirm the static catalog files exist, or that the CKAN
URL and API key authenticate.
- Detect the source type from the URL (or
--source) and verify reachability. - Read the source into canonical dataset entries, per the source's field table in
the reference doc.
- For a static target, resolve resource paths by copy mode:
linkkeeps source
URLs; download copies files into the repo under Git LFS, pushed to R2.
- Print a dry-run preview; st
Scaffold a new PortalJS data portal from a brief.
PortalJS — New Portal
Overview
Scaffold a production-ready PortalJS data portal from a brief. The skill is
interactive: if the brief is thin it interviews the user in three short rounds
(mapped to the template's three surfaces — Home, Catalog, Showcase), echoes a brief
back for confirmation, then copies examples/portaljs-catalog (locally or via a
remote tiged fetch), substitutes placeholder tokens, sets the namespace mode, seeds
any datasets named in the interview, installs dependencies, and verifies the scaffold
with a type check.
Prerequisites
- Node.js >=22 and npm available on
PATH. - Network access, unless a current local checkout of the portaljs repo is available
(the resolver defaults to a remote fetch of the template).
- A destination directory name that does not already contain files (or user consent
to overwrite one that does).
Instructions
The canonical, full step-by-step workflow is
.claude/commands/portaljs-new-portal.md —
the single source of truth. Read and follow it when executing. Summary:
- Interview the user in up to three rounds — Home/basics, Catalog & discovery
(datasets, namespace mode theme vs owner), Showcase/views — skipping any round
already answered by the input brief. Accept "use defaults" at any point.
- Confirm a short brief (name, slug, description, namespace, datasets, views) before
building.
- Resolve the template source: prefer a remote
tigedfetch of
examples/portaljs-catalog at main (or PORTALJSTEMPLATEREF); use a local
checkout only when it is current (has pages/[owner]/[slug].tsx) and the
destination is outside that repo.
- Materialize the template into
./PROJECT_SLUG, asking first if the destination
already exists and is non-empty.
- Substitute
_PROJECTNAME,PROJECTSLUG,DESCRIPTION_tokens
across all files with perl -pi, escaping /, \, and & in the values.
- Set
NAMESPACE_TYPE('theme'or'owner') inlib/datasets.tsper the
interview.
- Seed datasets captured in Round 2 (via
/portaljs-add-datasetor by hand), or
clear datasets.json to [] if none were named.
- Run
npm installinside the scaffolded portal. - Verify with
Use Cases
Reference implementations live in examples/:
| Example | Backend |
|---|---|
portaljs-catalog |
Canonical template — Home + Catalog + Showcase over a static manifest |
portaljs-template |
Minimal single-page starter |
ckan · ckan-ssg |
CKAN |
github-backed-catalog |
GitHub |
dataset-frictionless |
Frictionless Data Package |
fivethirtyeight · openspending · turing |
Real-world portals |
Ready to use portaljs?
Related Plugins
fairdb-ops-manager
Database operations management for FairDB PostgreSQL clusters
/plugin install fairdb-ops-manager@claude-code-plugins-plus
jeremy-firestore
Firestore database specialist for schema design, queries, and real-time sync
/plugin install jeremy-firestore@claude-code-plugins-plus
claude-never-forgets
Persistent memory plugin for Claude Code - remembers preferences, decisions, and corrections across sessions and context limits
/plugin install claude-never-forgets@claude-code-plugins-plus
geepers-agents
Multi-agent orchestration system with 51 specialized agents for development workflows, code quality, deployment, research, games, and more. Includes orchestrators for checkpoint, deploy, quality, fullstack, research, games, corpus, web, and python workflows.
/plugin install geepers-agents@claude-code-plugins-plus
sprint
Autonomous multi-agent development framework with spec-driven sprints. Write specs, run /sprint, and let coordinated agents (backend, frontend, QA, UI testing) handle implementation through self-iterating convergent loops.
/plugin install sprint@claude-code-plugins-plus
geepers-agents
Multi-agent orchestration system with 51 specialized agents for development workflows, code quality, deployment, research, and more. Built with MCP tools and Claude Code plugin agents.
/plugin install geepers-agents@claude-code-plugins-plus