/portaljs-architect
Recommend a data-portal architecture (storage, compute, catalog, access, hosting, metadata) from your needs, then hand off to the build skills. The advisory entry point.
> /plugin marketplace add datopian/portaljs > /plugin install portaljs@datopian-portaljs
How it fires
How this command gets triggered: by you, by Claude, or both.
- Fires itselfClaude auto-loads it when your prompt matches the work.
- You can call itInvoke it directly when you want it.
- Slash command
/portaljs-architect
Context preview
What this command does when you run it.
Recommend a data-portal architecture (storage, compute, catalog, access, hosting, metadata) from your needs, then hand off to the build skills. The advisory entry point.
Command definition
portaljs-architect.mddescription: Recommend a data-portal architecture (storage, compute, catalog, access, hosting, metadata) from your needs, then hand off to the build skills. The advisory entry point.
allowed-tools: Read, Write, Bash
/portaljs-architect
The **advisory** skill. Before scaffolding anything, it works out *what* to build: given what you're building, what your data is, and what it's for, it recommends a concrete architecture and then hands off to the build skills (`/portaljs-new-portal`, `/portaljs-add-dataset`, `/portaljs-connect-ckan`, …). It **decides**; it does not build.
The skill is **interactive**: if the brief is thin it interviews you in short rounds, echoes an architecture brief for confirmation, and **never dead-ends on missing input** — every question has a sensible default; say **"use defaults"** to take them.
See [the decision framework](../../site/content/docs/architecture/decision-framework.md) and [ROADMAP.md](../../ROADMAP.md) for the full model this skill encodes.
What it recommends — six slots
Every recommendation fills six slots. The **bold** option is the default when nothing pulls you off it.
| Slot | Options (default in **bold**) | |------|-------------------------------| | **Storage** | repo files · **Git-LFS + R2** · Parquet on R2 · CKAN datastore / warehouse | | **Catalog** | `datasets.json` · git + Frictionless · **DuckLake** · backend-native | | **Compute** | papaparse preview · **DuckDB** (Wasm → server) · warehouse engine | | **Access** | **static (public)** · runtime + backend RBAC | | **Hosting** | **Cloudflare Pages** (static) · Cloudflare Workers (runtime) · any static host | | **Metadata** | **Frictionless** profile · extended · custom · multi-profile + DCAT |
Opinionated default stack: `git + giftless/R2 + Parquet + DuckLake + DuckDB`, static on Cloudflare Pages, Frictionless metadata. Storage stays **S3-compatible**, so R2 is the default but never a hard lock-in.
The two build-time knobs this skill sets
Six slots describe the architecture, but two of them resolve to concrete values the build skills read directly. Name both explicitly in the brief — they are what gets *built*:
- **Data tier** — where a dataset's *bytes* live: `inline | LFS | external`. This is the
Storage slot made concrete, and it is **per-dataset routing** `/portaljs-add-dataset` applies (see its source-then-size matrix):
- **inline** — bytes committed to `public/data/`. A fenced exception only: the template's
bundled sample data, or an OSS self-host with no R2 creds. Get-started-fast, zero creds.
- **LFS** — bytes stream to Cloudflare R2 via Giftless (Git LFS); a ~134 B pointer stays
in git. **The default for any data the user adds**, regardless of size or format.
- **external** — bytes already live at a URL (R2 or 3rd-party); the manifest records the
absolute URL and copies nothing (passthrough). Parquet-on-R2 queried in place lives here.
- **Query mode** — how the showcase *computes* over a dataset: the `DATA_QUERY` constant in
`lib/datasets.ts`, `flat | duckdb`. This is the Compute slot made concrete, and it is **portal-wide**:
- **duckdb** — load the file into in-browser DuckDB-Wasm and expose a SQL query view
(filter/aggregate/join, no server). **The template default** — every portal ships the SQL editor out of the box. A Parquet resource always renders the query view regardless; `duckdb` renders it for CSV/TSV too.
- **flat** — fetch + preview the file with papaparse. Lightest; a *downgrade* from the
default for a preview-only portal that never needs querying.
Steps
1. Interview the user (skip rounds already answered by `$ARGUMENTS`)
Parse `$ARGUMENTS` first and pre-fill anything it specifies. Then ask only for what's missing, **one round at a time** (wait for each answer). Tell the user they can reply "use defaults".
**Round 1 — What are you building?**
Round 1 of 4 — the portal.
1. What kind of portal? (open-data portal · internal data catalog · research/project
data site · public-sector portal with harvesting obligations · something else)
2. Who runs it — one team/publisher, or several?
**Round 2 — What is your data?**
Round 2 of 4 — the data.
1. Roughly how big? (KBs/MBs · GBs · TBs)
2. What shape? (tabular CSV · geospatial/GeoJSON · documents · mixed)
3. How often does it change? (rarely, by hand · regularly · continuously)
4. How many datasets? (a handful · dozens · hundreds+)
5. Any of it private / access-controlled? (no, all public · yes)
If the user points at actual files or a directory, inspect them to ground the answers instead of guessing — e.g. size and row counts:
# Replace PATHS with the files/dirs the user named.
du -sh PATHS 2>/dev/null
for f in PATHS; do [ -f "$f" ] && printf '%s: ' "$f" && { wc -l < "$f" 2>/dev/null || echo '?'; }; done**Round 3 — What is it for?**
Round 3 of 4 — the purpose (pick any that apply).
- publishing & discovery (people find and download datasets)
- analytics / querying (filter, aggregate, join the data)
- redistribution / machine-to-machine harvesting (e.g. DCAT to data.europa.eu)
- compliance with a metadata standard (DCAT-AP, a national or domain profile)
**Round 4 — Constraints.**
Round 4 of 4 — constraints (all optional; Enter to skip).
- team size / SQL comfort?
- budget sensitivity?
- cloud preference? (default: Cloudflare — R2/Workers/Pages, S3-compatible)
- any existing backend to keep? (e.g. a CKAN or OpenMetadata instance)
**Defaults if a round is skipped:** open-data portal · single publisher · MBs of tabular CSV that changes rarely · a few-to-dozens of datasets · all public · publishing & discovery · Cloudflare, no existing backend.
2. Derive the recommendation
Fill the five slots by applying these rules (first match wins per slot; otherwise use the default):
**Storage · Catalog · Compute** (by data volume + query needs): | Situation | Storage | Catalog | Compute | |
Read more
description: Recommend a data-portal architecture (storage, compute, catalog, access, hosting, metadata) from your needs, then hand off to the build skills. The advisory entry point. allowed-tools: Read, Write, Bash
/portaljs-architect
The **advisory** skill. Before scaffolding anything, it works out *what* to build: given what you're building, what your data is, and what it's for, it recommends a concrete architecture and then hands off to the build skills (`/portaljs-new-portal`, `/portaljs-add-dataset`, `/portaljs-connect-ckan`, …). It **decides**; it does not build.
The skill is **interactive**: if the brief is thin it interviews you in short rounds, echoes an architecture brief for confirmation, and **never dead-ends on missing input** — every question has a sensible default; say **"use defaults"** to take them.
See [the decision framework](../../site/content/docs/architecture/decision-framework.md) and [ROADMAP.md](../../ROADMAP.md) for the full model this skill encodes.
What it recommends — six slots
Every recommendation fills six slots. The **bold** option is the default when nothing pulls you off it.
| Slot | Options (default in **bold**) | |------|-------------------------------| | **Storage** | repo files · **Git-LFS + R2** · Parquet on R2 · CKAN datastore / warehouse | | **Catalog** | `datasets.json` · git + Frictionless · **DuckLake** · backend-native | | **Compute** | papaparse preview · **DuckDB** (Wasm → server) · warehouse engine | | **Access** | **static (public)** · runtime + backend RBAC | | **Hosting** | **Cloudflare Pages** (static) · Cloudflare Workers (runtime) · any static host | | **Metadata** | **Frictionless** profile · extended · custom · multi-profile + DCAT |
Opinionated default stack: `git + giftless/R2 + Parquet + DuckLake + DuckDB`, static on Cloudflare Pages, Frictionless metadata. Storage stays **S3-compatible**, so R2 is the default but never a hard lock-in.
The two build-time knobs this skill sets
Six slots describe the architecture, but two of them resolve to concrete values the build skills read directly. Name both explicitly in the brief — they are what gets *built*:
- **Data tier** — where a dataset's *bytes* live: `inline | LFS | external`. This is the
Storage slot made concrete, and it is **per-dataset routing** `/portaljs-add-dataset` applies (see its source-then-size matrix):
- **inline** — bytes committed to `public/data/`. A fenced exception only: the template's
bundled sample data, or an OSS self-host with no R2 creds. Get-started-fast, zero creds.
- **LFS** — bytes stream to Cloudflare R2 via Giftless (Git LFS); a ~134 B pointer stays
in git. **The default for any data the user adds**, regardless of size or format.
- **external** — bytes already live at a URL (R2 or 3rd-party); the manifest records the
absolute URL and copies nothing (passthrough). Parquet-on-R2 queried in place lives here.
- **Query mode** — how the showcase *computes* over a dataset: the `DATA_QUERY` constant in
`lib/datasets.ts`, `flat | duckdb`. This is the Compute slot made concrete, and it is **portal-wide**:
- **duckdb** — load the file into in-browser DuckDB-Wasm and expose a SQL query view
(filter/aggregate/join, no server). **The template default** — every portal ships the SQL editor out of the box. A Parquet resource always renders the query view regardless; `duckdb` renders it for CSV/TSV too.
- **flat** — fetch + preview the file with papaparse. Lightest; a *downgrade* from the
default for a preview-only portal that never needs querying.
Steps
1. Interview the user (skip rounds already answered by `$ARGUMENTS`)
Parse `$ARGUMENTS` first and pre-fill anything it specifies. Then ask only for what's missing, **one round at a time** (wait for each answer). Tell the user they can reply "use defaults".
**Round 1 — What are you building?**
Round 1 of 4 — the portal. 1. What kind of portal? (open-data portal · internal data catalog · research/project data site · public-sector portal with harvesting obligations · something else) 2. Who runs it — one team/publisher, or several?
**Round 2 — What is your data?**
Round 2 of 4 — the data. 1. Roughly how big? (KBs/MBs · GBs · TBs) 2. What shape? (tabular CSV · geospatial/GeoJSON · documents · mixed) 3. How often does it change? (rarely, by hand · regularly · continuously) 4. How many datasets? (a handful · dozens · hundreds+) 5. Any of it private / access-controlled? (no, all public · yes)
If the user points at actual files or a directory, inspect them to ground the answers instead of guessing — e.g. size and row counts:
# Replace PATHS with the files/dirs the user named.
du -sh PATHS 2>/dev/null
for f in PATHS; do [ -f "$f" ] && printf '%s: ' "$f" && { wc -l < "$f" 2>/dev/null || echo '?'; }; done**Round 3 — What is it for?**
Round 3 of 4 — the purpose (pick any that apply). - publishing & discovery (people find and download datasets) - analytics / querying (filter, aggregate, join the data) - redistribution / machine-to-machine harvesting (e.g. DCAT to data.europa.eu) - compliance with a metadata standard (DCAT-AP, a national or domain profile)
**Round 4 — Constraints.**
Round 4 of 4 — constraints (all optional; Enter to skip). - team size / SQL comfort? - budget sensitivity? - cloud preference? (default: Cloudflare — R2/Workers/Pages, S3-compatible) - any existing backend to keep? (e.g. a CKAN or OpenMetadata instance)
**Defaults if a round is skipped:** open-data portal · single publisher · MBs of tabular CSV that changes rarely · a few-to-dozens of datasets · all public · publishing & discovery · Cloudflare, no existing backend.
2. Derive the recommendation
Fill the five slots by applying these rules (first match wins per slot; otherwise use the default):
**Storage · Catalog · Compute** (by data volume + query needs): | Situation | Storage | Catalog | Compute | |
🌀 AI-native framework for building data portals. Scaffold a full portal from a brief and load datasets in minutes with agentic skills — any backend (CKAN, GitHub, Frictionless).
Repo: datopian/portaljs
Other commands on portaljs.
- /add-chart
Renamed → /portaljs-add-chart. This alias will be removed next minor release.
Open command - /add-dataset
Renamed → /portaljs-add-dataset. This alias will be removed next minor release.
Open command - /add-map
Renamed → /portaljs-add-map. This alias will be removed next minor release.
Open command - /add-resource
Renamed → /portaljs-add-resource. This alias will be removed next minor release.
Open command - /arcgis-to-portaljs
Migrate a whole ArcGIS Hub site (opendata.arcgis.com or a Hub Premium custom domain) 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 (resultOffset paging), converts each
Open command - /architect
Renamed → /portaljs-architect. This alias will be removed next minor release.
Open command

