/portaljs-new-portal
Scaffold a new PortalJS data portal from a brief. Copies the canonical template from examples/portaljs-catalog and substitutes project tokens.
> /plugin marketplace add datopian/portaljs> /plugin install portaljs@datopian-portaljs
Command definition
portaljs-new-portal.mddescription: Scaffold a new PortalJS data portal from a brief. Copies the canonical template from examples/portaljs-catalog and substitutes project tokens.
allowed-tools: Read, Write, Edit, Bash
/portaljs-new-portal
Scaffold a production-ready PortalJS data portal. 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`, substitutes placeholder tokens, sets the namespace mode and the UI language, seeds any datasets, installs dependencies, and verifies the build.
The template you are scaffolding
`examples/portaljs-catalog` is the canonical template. It has **three surfaces**:
| Surface | Route | File | What it is | |---|---|---|---| | Home | `/` | `pages/index.tsx` | Search-first landing: hero + search box + suggested-query chips (`__PROJECT_NAME__` / `__DESCRIPTION__` tokens) | | Catalog / search | `/search` | `pages/search.tsx` | Client-side full-text list over `datasets.json` | | Dataset showcase | `/@<namespace>/<slug>` | `pages/[owner]/[slug].tsx` | One dataset: metadata, `Table` data preview, Download & API, and a Views placeholder for charts/maps |
The catalog is driven by `datasets.json` (the single source of truth) — each entry is `{ slug, name, description, file, format, namespace }`. `lib/datasets.ts` exposes `getDatasets()`, `getDataset(slug)`, `getDatasetByNamespace(ns, slug)`, `datasetHref(d)` → `/@${namespace}/${slug}`, and a `NAMESPACE_TYPE: 'theme' | 'owner'` constant.
A portal uses **exactly one** namespace mode:
- **`'theme'`** — a single-publisher portal whose datasets are grouped by subject
(e.g. `@reference/country-codes`). The showcase labels the namespace "Theme".
- **`'owner'`** — a multi-publisher portal whose datasets are grouped by who published
them (e.g. `@worldbank/country-codes`). The showcase labels the namespace "Owner".
Search is a **static client-side list** for now. A live backend (e.g. CKAN, via `/portaljs-connect-ckan`) can replace `datasets.json` later without changing the URL structure.
Every UI string (navigation, search, showcase, SQL/map panels, errors) lives in `locales/en.json` and renders through `t()` from `lib/i18n.ts`. A portal has **one** UI language, committed as `DEFAULT_LOCALE` in `next.config.js` (default `'en'`); another language is a sibling `locales/<code>.json` with the same keys, registered in `lib/i18n.ts`. `npm run i18n:check` validates the locale files.
Required input — interview, don't error
You need a **project name** and **one-line description**. Datasets are optional at scaffold time, and the UI language defaults to English. **Never dead-end with a missing-input error.** If `$ARGUMENTS` is empty or thin, run the structured interview in Step 1 to gather what's missing. The user can say **"use defaults"** at any point to skip a round and accept the defaults below.
Steps
1. Interview the user (skip rounds already answered by `$ARGUMENTS`)
Parse `$ARGUMENTS` first and pre-fill anything it already specifies. Then ask only for what's still missing, **one focused round at a time** (wait for the answer before the next round). Each round maps to a template surface. Tell the user they can reply "use defaults" to take the defaults.
**Round 1 — Home / basics:**
Round 1 of 3 — the home page.
1. What's the portal called? (e.g. "Auckland Open Data Portal")
2. One-line description for the hero? (default: "An open data portal.")
3. Who is it for / what's it about? (helps pick suggested-search chips)
4. Which language should the interface be in? (default: English — a name or a code,
e.g. "Italian" or "it", "Brazilian Portuguese" or "pt-BR")
Infer the language when the brief already says it (e.g. "build an Italian data portal", or a name/description written in Italian) and confirm it in Step 2 instead of asking. Write the name and description in the portal's language — they are shown as-is.
**Round 2 — Catalog & discovery:**
Round 2 of 3 — the catalog (the /search list).
1. Which datasets should it start with? Give file paths or URLs, or "none yet".
2. Roughly how many datasets total — a handful, dozens, hundreds?
3. Single publisher or multiple?
- single → datasets grouped by SUBJECT (namespace mode "theme")
- multiple → datasets grouped by PUBLISHER (namespace mode "owner")
Then: what namespace value(s)? (e.g. "reference", or "worldbank, eurostat")
Note for the user when relevant: search is a static client-side list over `datasets.json` for now; a live backend (CKAN) can be wired in later with `/portaljs-connect-ckan` without changing URLs.
**Round 3 — Showcase / views:**
Round 3 of 3 — each dataset's showcase page (/@<namespace>/<slug>).
Every showcase already shows metadata + a data preview + a Download & API section.
For the dataset(s) above, do any need an extra view?
- a chart (line/bar/area/pie/scatter) → added later with /portaljs-add-chart
- a map (GeoJSON on Leaflet) → added later with /portaljs-add-map
(Press Enter / "use defaults" for metadata + preview + download only.)
**Defaults if a round is skipped:** description `"An open data portal."`; language English (`en`); no datasets; `NAMESPACE_TYPE = 'theme'` with namespace value `reference`; no extra views.
2. Confirm the brief before building
Echo a short brief back and wait for a yes:
Here's the plan — say "go" to build, or correct anything:
• Name: PROJECT_NAME (slug: PROJECT_SLUG)
• Description: DESCRIPTION
• Language: LANGUAGE (locale LOCALE_CODE, formats INTL_TAG — e.g. Italian: it, it-IT)
• Namespace: NAMESPACE_TYPE — value(s): NS_VALUES
• Datasets: <list, or "none — add later with /portaljs-add-dataset">
• Views to add after scaffold: <charts/maps per dataset, or "none">
Derive `PROJECT_SLUG` from `PROJECT_NAME` (lowercase, hyphenated, no special char
Read more
description: Scaffold a new PortalJS data portal from a brief. Copies the canonical template from examples/portaljs-catalog and substitutes project tokens. allowed-tools: Read, Write, Edit, Bash
/portaljs-new-portal
Scaffold a production-ready PortalJS data portal. 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`, substitutes placeholder tokens, sets the namespace mode and the UI language, seeds any datasets, installs dependencies, and verifies the build.
The template you are scaffolding
`examples/portaljs-catalog` is the canonical template. It has **three surfaces**:
| Surface | Route | File | What it is | |---|---|---|---| | Home | `/` | `pages/index.tsx` | Search-first landing: hero + search box + suggested-query chips (`__PROJECT_NAME__` / `__DESCRIPTION__` tokens) | | Catalog / search | `/search` | `pages/search.tsx` | Client-side full-text list over `datasets.json` | | Dataset showcase | `/@<namespace>/<slug>` | `pages/[owner]/[slug].tsx` | One dataset: metadata, `Table` data preview, Download & API, and a Views placeholder for charts/maps |
The catalog is driven by `datasets.json` (the single source of truth) — each entry is `{ slug, name, description, file, format, namespace }`. `lib/datasets.ts` exposes `getDatasets()`, `getDataset(slug)`, `getDatasetByNamespace(ns, slug)`, `datasetHref(d)` → `/@${namespace}/${slug}`, and a `NAMESPACE_TYPE: 'theme' | 'owner'` constant.
A portal uses **exactly one** namespace mode:
- **`'theme'`** — a single-publisher portal whose datasets are grouped by subject
(e.g. `@reference/country-codes`). The showcase labels the namespace "Theme".
- **`'owner'`** — a multi-publisher portal whose datasets are grouped by who published
them (e.g. `@worldbank/country-codes`). The showcase labels the namespace "Owner".
Search is a **static client-side list** for now. A live backend (e.g. CKAN, via `/portaljs-connect-ckan`) can replace `datasets.json` later without changing the URL structure.
Every UI string (navigation, search, showcase, SQL/map panels, errors) lives in `locales/en.json` and renders through `t()` from `lib/i18n.ts`. A portal has **one** UI language, committed as `DEFAULT_LOCALE` in `next.config.js` (default `'en'`); another language is a sibling `locales/<code>.json` with the same keys, registered in `lib/i18n.ts`. `npm run i18n:check` validates the locale files.
Required input — interview, don't error
You need a **project name** and **one-line description**. Datasets are optional at scaffold time, and the UI language defaults to English. **Never dead-end with a missing-input error.** If `$ARGUMENTS` is empty or thin, run the structured interview in Step 1 to gather what's missing. The user can say **"use defaults"** at any point to skip a round and accept the defaults below.
Steps
1. Interview the user (skip rounds already answered by `$ARGUMENTS`)
Parse `$ARGUMENTS` first and pre-fill anything it already specifies. Then ask only for what's still missing, **one focused round at a time** (wait for the answer before the next round). Each round maps to a template surface. Tell the user they can reply "use defaults" to take the defaults.
**Round 1 — Home / basics:**
Round 1 of 3 — the home page. 1. What's the portal called? (e.g. "Auckland Open Data Portal") 2. One-line description for the hero? (default: "An open data portal.") 3. Who is it for / what's it about? (helps pick suggested-search chips) 4. Which language should the interface be in? (default: English — a name or a code, e.g. "Italian" or "it", "Brazilian Portuguese" or "pt-BR")
Infer the language when the brief already says it (e.g. "build an Italian data portal", or a name/description written in Italian) and confirm it in Step 2 instead of asking. Write the name and description in the portal's language — they are shown as-is.
**Round 2 — Catalog & discovery:**
Round 2 of 3 — the catalog (the /search list). 1. Which datasets should it start with? Give file paths or URLs, or "none yet". 2. Roughly how many datasets total — a handful, dozens, hundreds? 3. Single publisher or multiple? - single → datasets grouped by SUBJECT (namespace mode "theme") - multiple → datasets grouped by PUBLISHER (namespace mode "owner") Then: what namespace value(s)? (e.g. "reference", or "worldbank, eurostat")
Note for the user when relevant: search is a static client-side list over `datasets.json` for now; a live backend (CKAN) can be wired in later with `/portaljs-connect-ckan` without changing URLs.
**Round 3 — Showcase / views:**
Round 3 of 3 — each dataset's showcase page (/@<namespace>/<slug>). Every showcase already shows metadata + a data preview + a Download & API section. For the dataset(s) above, do any need an extra view? - a chart (line/bar/area/pie/scatter) → added later with /portaljs-add-chart - a map (GeoJSON on Leaflet) → added later with /portaljs-add-map (Press Enter / "use defaults" for metadata + preview + download only.)
**Defaults if a round is skipped:** description `"An open data portal."`; language English (`en`); no datasets; `NAMESPACE_TYPE = 'theme'` with namespace value `reference`; no extra views.
2. Confirm the brief before building
Echo a short brief back and wait for a yes:
Here's the plan — say "go" to build, or correct anything: • Name: PROJECT_NAME (slug: PROJECT_SLUG) • Description: DESCRIPTION • Language: LANGUAGE (locale LOCALE_CODE, formats INTL_TAG — e.g. Italian: it, it-IT) • Namespace: NAMESPACE_TYPE — value(s): NS_VALUES • Datasets: <list, or "none — add later with /portaljs-add-dataset"> • Views to add after scaffold: <charts/maps per dataset, or "none">
Derive `PROJECT_SLUG` from `PROJECT_NAME` (lowercase, hyphenated, no special char
🌀 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).
Other commands on portaljs.
More commands from other plugins.
build
CommandImplement tasks incrementally — build, test, verify, commit. Add "auto" to run the whole plan in one approved pass.

