# Show HN: PortalJS – AI-native, open-source framework for data portals

> Source: <https://github.com/datopian/portaljs>
> Published: 2026-07-15 12:34:32+00:00

**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.

[Docs](https://www.portaljs.com/docs)
·
[Discussions](https://github.com/datopian/portaljs/discussions)
·
[Report a bug](https://github.com/datopian/portaljs/issues/new)

[
](https://www.npmjs.com/package/create-portaljs)[
](https://github.com/datopian/portaljs/stargazers)[
](https://discord.gg/krmj5HM6He)[
](/datopian/portaljs/blob/main/license)

**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](https://claude.com/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 →](/datopian/portaljs/blob/main/.claude/INSTALL.md))

**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:

**Advise**— given*what you're building*,*what your data is*, and*what it's for*, they recommend an architecture: storage, compute, catalog, access, hosting, metadata.**Build**— they scaffold that stack as plain, editable Next.js code with no lock-in.

It is **opinionated but open**: the recommended modern path is git + object storage
(Cloudflare R2) + Parquet, queried with [DuckDB](https://duckdb.org/) — an open lakehouse
instead of a classic warehouse. For living, incremental tables you can layer on
[DuckLake](https://ducklake.select/), 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](https://www.datopian.com/) 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 →](https://www.portaljs.com/docs/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](/datopian/portaljs/blob/main/ROADMAP.md) for the full model and the

[architecture decision framework](https://www.portaljs.com/docs/architecture/decision-framework)for how

`/portaljs-architect`

turns your needs into a stack.PortalJS ships [Claude Code](https://claude.com/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](/datopian/portaljs/blob/main/.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` |

`/portaljs-new-portal`

`/portaljs-add-dataset`

`/portaljs-add-resource`

`/portaljs-add-chart`

`/portaljs-add-map`

`/portaljs-add-geo`

`/portaljs-define-schema`

`/portaljs-add-dcat`

`/portaljs-connect-ckan`

`/portaljs-check-data-quality`

`/portaljs-migrate`

`/arcgis-to-portaljs`

`/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`

`<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](/datopian/portaljs/blob/main/ROADMAP.md). Write your own — see [ .claude/AUTHORING.md](/datopian/portaljs/blob/main/.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](/datopian/portaljs/blob/main/examples/portaljs-catalog), is where
the three surfaces and the

`DataProvider`

seam live — read it before building.- 🌱
**Open source, MIT, no lock-in**— every skill emits plain Next.js you can fork and own. - 🧭
**Advisory, not just generative**—`/portaljs-architect`

helps you*decide*the infrastructure, not only scaffold a UI. - 🦆
**Open lakehouse by default**— git + R2 + Parquet queried with DuckDB, over a heavy warehouse; add DuckLake for living/incremental tables. A datastore/warehouse stays a supported choice. - ☁️
**Cloudflare-first, portable**— R2 / Workers / D1 / Pages as the default substrate, but object storage stays S3-compatible. - 🧩
**Decoupled, any backend**— one`DataProvider`

contract in front of[CKAN](https://ckan.org/),[DKAN](https://getdkan.org/),[OpenMetadata](https://open-metadata.org/),[DataHub](https://datahubproject.io/), GitHub,[Frictionless](https://frictionlessdata.io/), plain files — or your own. - 🎨
**Bring your own stack**— adopt the template or lift the skills and the three-surface model into an app you already have.

Reference implementations live in [ examples/](/datopian/portaljs/blob/main/examples):

| Example | Backend |
|---|---|
`portaljs-catalog` |

**Canonical template**— Home + Catalog + Showcase over a static manifest`portaljs-template`

[·](/datopian/portaljs/blob/main/examples/ckan)`ckan`

`ckan-ssg`

`github-backed-catalog`

`dataset-frictionless`

[·](/datopian/portaljs/blob/main/examples/fivethirtyeight)`fivethirtyeight`

[·](/datopian/portaljs/blob/main/examples/openspending)`openspending`

`turing`

- 💬
**Discord**— live chat and help:[join the server](https://discord.gg/krmj5HM6He) - 🗣️
**Discussions**— questions, ideas, show-and-tell:[github.com/datopian/portaljs/discussions](https://github.com/datopian/portaljs/discussions) - 🐛
**Issues**— bugs and feature requests:[open an issue](https://github.com/datopian/portaljs/issues/new) - 📖
**Docs**—[portaljs.com/docs](https://www.portaljs.com/docs)

PortalJS is built in the open and we welcome contributions of all sizes — new skills,
examples, docs, and fixes. See [CONTRIBUTING.md](/datopian/portaljs/blob/main/CONTRIBUTING.md) to get started, and read
[ROADMAP.md](/datopian/portaljs/blob/main/ROADMAP.md) and [VISION.md](/datopian/portaljs/blob/main/VISION.md) for where the project is headed.
