๐ 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).
> /plugin marketplace add datopian/portaljs> /plugin install portaljs@datopian-portaljs
Repo: datopian/portaljs
What's inside
Create a portal โ one command, nothing to install beyond Node 22+:
npm create portaljs@latest my-portal
cd my-portal
npm run dev # โ http://localhost:3000
You get the three surfaces โ Home, a Catalog (/search), and a dataset Showcase
(/@<namespace>/<slug>) โ over sample data. Plain, editable Next.js, no lock-in. Add your
own CSV/JSON to datasets.json and it renders automatically.
Build it with your AI assistant โ PortalJS ships Claude Code
skills that do the assembly. Install them once (into ~/.claude/commands):
curl -fsSL https://raw.githubusercontent.com/datopian/portaljs/main/scripts/install-portaljs-skills.sh | bash
Then, in a Claude Code session from any directory:
/portaljs-architect not sure what stack you need? start here
/portaljs-new-portal "Auckland Council open data portal"
/portaljs-add-dataset ./data/air-quality.csv
/portaljs-new-portal scaffolds the three surfaces; /portaljs-add-dataset (or /portaljs-add-resource) loads data;
/portaljs-connect-ckan points it at a CKAN backend; /portaljs-deploy ships it. (All skills + install โ)
Prefer the bare template โ plain Next.js, no AI, no lock-in:
npx tiged datopian/portaljs/examples/portaljs-catalog my-portal
cd my-portal && npm install && npm run dev # โ http://localhost:3000
You get Home, a Catalog (/search), and a dataset Showcase (/@<namespace>/<slug>) over
sample data. Add your own CSV/JSON to datasets.json and it renders automatically.
โญ If it's useful, a star helps others find it.
Building a data portal has always meant more than a website. You have to decide where the data lives, how it's versioned, how people search it, how it's served, and how it's governed โ and then wire a frontend on top. Teams either over-build on a heavy data warehouse they don't need, or under-build on a pile of scripts that doesn't scale.
PortalJS is an open-source, agentic skills framework that helps data teams build, develop, and ship data portals โ and the data infrastructure underneath them. It isn't only a frontend. The skills do two jobs:
It is opinionated but open: the recommended modern path is git + object storage (Cloudflare R2) + Parquet, queried with DuckDB โ an open lakehouse instead of a classic warehouse. For living, incremental tables you can layer on DuckLake, and a traditional datastore (CKAN, a warehouse) stays a first-class option when you need it. You always own plain code.
Built and maintained in the open by Datopian and the PortalJS community.
๐ง you describe what you want to build
โ
โผ
โญโ ๐ค AGENTIC SKILLS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ decide + build
โ /portaljs-architect ยท /portaljs-new-portal ยท /portaljs-add-dataset ยท /portaljs-add-chart ยท /portaljs-add-map โฆ
โฐโ generates plain, editable Next.js code โ no lock-in
โ
โผ
โญโ ๐ฅ๏ธ SURFACES โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ what users see
โ ๐ Home / ๐ Catalog /search ๐ Showcase /@ns/slug
โฐโ read data through one DataProvider contract
โ
โผ
โญโ ๐ PROVIDERS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ pluggable backends
โ ๐ staticยทgit ๐ CKAN ๐ญ OpenMetadata ๐๏ธ git-LFS + R2
โฐโ swap the source without touching a page
โ
โผ
๐ฆ STORAGE + COMPUTE โ choose your point on the spectrum:
flat files โโถ Git-LFS + R2 โโถ Parquet on R2 + ๐ฆ DuckDB โโถ warehouse / CKAN
simplest โญ open lakehouse (default) heaviest
(+ DuckLake for living tables)
โ๏ธ Substrate โ Cloudflare R2 (storage) ยท Workers (runtime) ยท D1 (catalog) ยท Pages (static)
object storage stays S3-compatible โ R2 is the default, never a lock-in
Three surfaces. Every data portal is built from three: a Home page that explains
it and offers search, a Catalog (/search) to discover datasets, and a Showcase
(/@<namespace>/<slug>) to explore one dataset โ metadata, preview, download/API, and
charts/maps. (Core concepts โ)
One seam. The surfaces read data only through a DataProvider, so the source โ static
files today, a CKAN or lakehouse backend tomorrow โ can change without touching a page.
See ROADMAP.md for the full model and the
architecture decision framework
for how /portaljs-architect turns your needs into a stack.
PortalJS ships Claude Code skills that turn a brief into a working portal.
Install the skills once into your personal scope so they're available from any directory:
curl -fsSL https://raw.githubusercontent.com/datopian/portaljs/main/scripts/install-portaljs-skills.sh | bash
Restart Claude Code (or open a new session) and type / to see them. See
.claude/INSTALL.md for other install options (versioned plugin, or
running straight from a clone of this repo).
If you're not sure how to set up your portal, start with the advisor, then build:
/portaljs-architect we have ~200 public CSVs, updated quarterly, and must publish DCAT-AP
/portaljs-new-portal "Auckland Council open data portal"
/portaljs-add-dataset ./data/air-quality.csv
/portaljs-add-dataset https://example.com/parks.geojson
The skills are interactive โ if your brief is thin, they interview you in short rounds
rather than erroring. /portaljs-architect recommends a stack and hands off; /portaljs-new-portal
scaffolds the three surfaces; /portaljs-add-dataset appends to the datasets.json manifest and
the showcase renders automatically at /@<namespace>/<slug>. Run npm run dev and you
have a portal.
Prefer to build by hand? The skills are a convenience, not a requirement โ scaffold the template directly with the CLI:
npm create portaljs@latest my-portal
(Or grab the bare template with no prompts: npx tiged datopian/portaljs/examples/portaljs-catalog my-portal.)
| Skill | What it does |
|---|---|
/portaljs-architect | Advisory โ turns your needs (data, scale, governance) into a recommended architecture before you build. Start here if you're unsure of the stack. |
/portaljs-new-portal | Scaffold a new portal (Home + Catalog + Showcase) from a brief โ copies the template, substitutes your project name and description, installs deps, verifies the build. |
/portaljs-add-dataset | Add a CSV, TSV, JSON, or GeoJSON dataset โ registers it in the catalog and renders its showcase automatically; large local files are pushed to Cloudflare R2 via Git LFS for you. |
/portaljs-add-resource | Attach another file (data dictionary, methodology, extra data) to an existing dataset โ it becomes multi-resource and the showcase renders a section per file. |
/portaljs-add-chart | Add a line, bar, area, pie, or scatter chart to a dataset's showcase. |
/portaljs-add-map | Render a GeoJSON dataset on an interactive map and register it on the home page. |
/portaljs-add-geo | Auto-ingest a geospatial file (GeoJSON, Shapefile, GeoPackage, KML/KMZ, FlatGeobuf, CSV-with-geometry) on your own machine โ no server: normalizes CRS to EPSG:4326, derives a PMTiles render tier and a GeoParquet query tier, pushes both plus the original to R2, and registers one dual-tier dataset the showcase maps and queries in place. |
/portaljs-define-schema | Infer a Frictionless Table Schema from a dataset's data, add license/source/keyword metadata, and surface a typed field table on its showcase. |
/portaljs-add-dcat | Make the portal harvestable โ emit standards-compliant DCAT feeds (DCAT 2/3, DCAT-AP, DCAT-US, national profiles) in JSON-LD, Turtle, and RDF/XML so national/EU/US open-data portals can harvest its datasets. |
/portaljs-connect-ckan | Wire the portal to a CKAN backend over its API instead of static files. |
/portaljs-check-data-quality | Validate a dataset against its schema and flag quality issues (type mismatches, missing values, constraint violations). |
/portaljs-migrate | Harvest or migrate a whole catalog into the portal from CKAN, Socrata, OpenDataSoft, ArcGIS, or DCAT-US, over a canonical Frictionless/DCAT model. |
/arcgis-to-portaljs | Migrate a whole ArcGIS Hub site into the portal end-to-end โ harvest its /data.json, export every FeatureService layer via the ArcGIS REST API, convert to the serverless dual tier (PMTiles + GeoParquet), push to R2, and write a source-vs-derived parity report. |
/portaljs-deploy | Build a static export and publish it to PortalJS Arc โ Datopian-managed hosting on Cloudflare โ with a live <slug>.arc.portaljs.com URL. |
Large-data scaling โ big files pushed to Cloudflare R2 via Git LFS โ already ships in
/portaljs-add-dataset. More skill families โ metadata schemas (Frictionless/DCAT), more
backends (OpenMetadata), a browser DuckDB query layer, and access control โ are on the
roadmap. Write your own โ see .claude/AUTHORING.md.
.claude/commands/ the agentic skills (slash commands)
examples/ reference portals โ portaljs-catalog is the canonical template
packages/
core/ layout/UI components (@portaljs/core)
ckan/ CKAN catalog UI + React (@portaljs/ckan)
ckan-api-client-js/ pure CKAN API client (@portaljs/ckan-api-client-js)
site/ portaljs.com โ the marketing site + docs
ROADMAP.md direction, the four contracts, sequencing
The canonical template, examples/portaljs-catalog, is where
the three surfaces and the DataProvider seam live โ read it before building.
/portaljs-architect helps you decide the infrastructure, not only scaffold a UI.DataProvider contract in front of CKAN, DKAN, OpenMetadata, DataHub, GitHub, Frictionless, plain files โ or your own.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 |
PortalJS is built in the open and we welcome contributions of all sizes โ new skills, examples, docs, and fixes. See CONTRIBUTING.md to get started, and read ROADMAP.md and VISION.md for where the project is headed.
.changeset/
config.json
README.md
.claude/
.claude-plugin/
marketplace.json
plugin.json
AUTHORING.md
commands/
add-chart.md
add-dataset.md
add-map.md
add-resource.md
arcgis-to-portaljs.md
architect.md
check-data-quality.md
connect-ckan.md
define-schema.md
deploy.md
migrate.md
new-portal.md
portaljs-add-chart.md
portaljs-add-dataset.md
portaljs-add-dcat.md
portaljs-add-geo.md
portaljs-add-map.md
portaljs-add-resource.md
portaljs-architect.md
portaljs-check-data-quality.md
portaljs-connect-ckan.md
portaljs-define-schema.md
portaljs-deploy.md
portaljs-migrate.md
portaljs-new-portal.md
INSTALL.md
.editorconfig
.eslintignore
.eslintrc.json
.gastown/
README.md
settings.json
.github/
workflows/
ci.yml
release.yml
skills-sync.yml
.gitignore
.prettierignore
.prettierrc
AGENTS.md
assets/
portaljs-logo-spin.svg
portaljs-logo.svg
babel.config.json
CLAUDE.md
cloud/
api/
.gitignore
migrations/
0001_init.sql
0002_device_codes.sql
0003_email_auth.sql
0004_email_rate_limit.sql
0005_ph_distinct_id.sql
package-lock.json
package.json
src/
db.ts
index.ts
lfs.ts
untar.ts
test/
deploy.test.ts
helpers.ts
lfs.test.ts
untar.test.ts
whoami.test.ts
tsconfig.json
wrangler.toml
auth/
.gitignore
package-lock.json
package.json
src/
analytics.ts
device.ts
email.ts
html.ts
index.ts
ratelimit.ts
session.ts
tokens.ts
util.ts
test/
analytics.test.ts
cors.test.ts
csrf.test.ts
device.test.ts
email.test.ts
errors.test.ts
ratelimit.test.ts
session.test.ts
tokens.test.ts
tsconfig.json
wrangler.toml
deploy.sh
GO-LIVE.md
INFRA.md
SPEC.md
worker/
.gitignore
package-lock.json
package.json
src/
index.ts
test/
router.test.ts
tsconfig.json
wrangler.toml
CONTRIBUTING.md
DESIGN.md
docs/
analytics-bot-filtering.md
skill-naming-audit.md
examples/
.gitkeep
ckan/
ckan-ssg/
.eslintrc.json
index.d.ts
next-env.d.ts
next.config.js
package-lock.json
package.json
pages/
_app.tsx
@org/
[org]/
[dataset]/
index.tsx
index.tsx
styles.css
postcss.config.js
public/
.gitkeep
README.md
specs/
index.spec.tsx
tailwind.config.js
tsconfig.json
tsconfig.spec.json
.eslintrc.json
.gitignore
components/
DataRichDocument.tsx
content/
test.md
lib/
markdown.js
mddb.ts
next.config.js
package-lock.json
package.json
pages/
_app.tsx
_document.tsx
[org]/
[dataset]/
index.tsx
api/
cors.ts
index.tsx
stories/
[[...path]].tsx
postcss.config.js
public/
favicon.ico
next.svg
vercel.svg
README.md
styles/
globals.css
tailwind.config.js
tsconfig.json
dataset-frictionless/
.babelrc
.gitignore
assets/
demo.gif
lib/
dataset.js
utils.js
package.json
pages/
_app.js
index.js
postcss.config.js
public/
country-codes/
archive/
country-codes.csv
data/
country-codes_csv.csv
datapackage.json
README.md
dataset/
data/
vix-daily.csv
datapackage.json
README.md
favicon.ico
README.md
styles/
globals.css
tailwind.css
tailwind.config.js
yarn.lock
fivethirtyeight/
.eslintrc.json
.gitignore
components/
Breadcrumbs.tsx
Layout.tsx
datasets.json
index.csv
lib/
octokit.ts
next.config.js
package-lock.json
package.json
pages/
_app.tsx
_document.tsx
datasets/
[datasetName].tsx
index.tsx
postcss.config.js
public/
favicon.ico
next.svg
share_image.png
vercel.svg
README.md
scripts/
build-datasets.js
README.md
styles/
globals.css
tailwind.config.js
tsconfig.json
github-backed-catalog/
.eslintrc.json
.prettierignore
.prettierrc.json
components/
_shared/
Breadcrumbs.tsx
DataRichDocument.tsx
icons/
ExternalLinkIcon.tsx
HomeIcon.tsx
datasets.json
index.d.ts
lib/
markdown.js
mddb.ts
octokit.ts
next-env.d.ts
next.config.js
package-lock.json
package.json
pages/
_app.tsx
@org/
[org]/
[...path].tsx
index.tsx
styles.css
postcss.config.js
public/
.gitkeep
README.md
tailwind.config.js
tsconfig.json
learn/
.gitignore
components/
DataRichDocument.tsx
content/
index.md
lib/
markdown.js
mddb.ts
next.config.js
package-lock.json
package.json
pages/
_app.tsx
_document.tsx
[[...path]].tsx
postcss.config.js
public/
data.csv
favicon.png
README.md
styles/
globals.css
tailwind.config.js
tsconfig.json
openspending/
__tests__/
os-data.test.ts
.eslintrc.json
.gitignore
components/
_shared/
Breadcrumbs.tsx
Footer.tsx
Layout.tsx
TopBanner.tsx
Button.tsx
Container.tsx
DataRichDocument.tsx
DatasetCard.tsx
DatasetsGrid.tsx
DatasetsSearch.tsx
FlatUiTable.tsx
Header.tsx
Hero.tsx
icons/
ExternalLinkIcon.tsx
HomeIcon.tsx
content/
about/
contact.md
contributors.md
exemplars.md
faq.md
fiscal-spec.md
fiscaldatapackage.md
funders.md
index.md
meta/
changes/
index.md
contribute/
add-resource.html
adding-terms.html
adding.html
editing.html
index.md
markdown-examples.md
translate-glossary.html
translate-guide.html
index.md
media/
index.md
openspending-first-steps.md
presentation-introduction.md
presentations.md
steering-group/
index.md
tools.md
blog/
2011-03-01-where-does-my-money-go-goes-international-welcome-to-openspending.md
2011-03-20-uploading-data-to-openspending.md
2011-10-27-thoughts-from-the-global-investigative-journalism-conference.md
2011-11-11-new-translation-documentation-for-openspending.md
2011-11-16-openspending-v0-11-released.md
2011-11-28-openspending-visualisations-featured-in-the-guardian.md
2011-12-05-how-spending-stories-spots-errors-in-public-spending.md
2011-12-12-data-seized-sanitised-and-sanity-checked-open-data-day-2011.md
2012-01-12-civil-society-and-spending-data-who-is-mapping-the-money.md
2012-01-17-open-bookkeeping-what-role-can-accountants-play-in-open-spending-budget-projects.md
2012-01-23-transparency-and-technology-in-brazil-linking-politicians-to-bad-entrepreneurs.md
2012-01-23-updates-from-the-openspending-dev-team.md
2012-01-30-hakuna-my-data-nbo-data-bootcamp.md
2012-02-15-the-czech-budget-on-line-the-half-success-story.md
2012-02-16-thekit.md
2012-02-24-how-spending-stories-fact-checks-big-brother-the-wiretappers-ball.md
2012-02-29-open-meeting-software-for-participatory-budgeting.md
2012-03-06-calculating-portugal's-taxes.md
2012-03-06-participatory-budgeting-and-technology.md
2012-03-08-uk-25k-spending-data.md
2012-03-12-technology-for-fiscal-transparency-where-next.md
2012-03-13-voting-systems-for-e-participatory-budgeting-upcoming-call.md
2012-05-29-GIFT-report-released.md
2012-06-05-IATI-on-OpenSpending.md
2012-06-11-opendata-ch-workshop.md
2012-06-11-upload-videos.md
2012-06-28-OKFest-announce.mdown
2012-07-02-tester.mdown
2012-07-05-OSI.md
2012-07-11-Greece.mdown
2012-07-16-Greece-2.md
2012-07-16-Greece-3.md
2012-07-19-Caring-for-your-neighbourhood.mdown
2012-07-20-Romania.md
2012-07-27-budapest-institute.md
2012-08-05-introduction-to-the-taxman.md
... 1600 moreShowing a partial view of a very large repo.
FAQ
portaljs is a Claude Code plugin with 14 hand-picked skills for development work, indexed on Flowy. Install it with the command on its page. It includes arcgis-to-portaljs, portaljs-add-chart, portaljs-add-dataset. Its skills do not fire on their own yet. Request auto-invocation to have Flowy route them as you prompt. Free and open source.