by datopian
🌀 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).
# Add to your Claude Code skills
git clone https://github.com/datopian/portaljsLast scanned: 7/8/2026
{
"issues": [
{
"type": "npm-audit",
"message": "@adobe/css-tools: @adobe/css-tools Regular Expression Denial of Service (ReDOS) while Parsing CSS",
"severity": "medium"
},
{
"type": "npm-audit",
"message": "@babel/core: @babel/core: Arbitrary File Read via sourceMappingURL Comment",
"severity": "low"
},
{
"type": "npm-audit",
"message": "@babel/helpers: Babel has inefficient RegExp complexity in generated code with .replace when transpiling named capturing groups",
"severity": "medium"
},
{
"type": "npm-audit",
"message": "@babel/runtime: Babel has inefficient RegExp complexity in generated code with .replace when transpiling named capturing groups",
"severity": "medium"
},
{
"type": "npm-audit",
"message": "@babel/traverse: Babel vulnerable to arbitrary code execution when compiling specifically crafted malicious code",
"severity": "critical"
},
{
"type": "npm-audit",
"message": "@changesets/cli: Vulnerability found",
"severity": "low"
},
{
"type": "npm-audit",
"message": "@microsoft/api-extractor: Vulnerability found",
"severity": "high"
},
{
"type": "npm-audit",
"message": "@microsoft/api-extractor-model: Vulnerability found",
"severity": "high"
},
{
"type": "npm-audit",
"message": "@microsoft/tsdoc-config: Vulnerability found",
"severity": "medium"
},
{
"type": "npm-audit",
"message": "@rushstack/node-core-library: Vulnerability found",
"severity": "high"
},
{
"type": "npm-audit",
"message": "ajv: ajv has ReDoS when using `$data` option",
"severity": "medium"
},
{
"type": "npm-audit",
"message": "brace-expansion: brace-expansion Regular Expression Denial of Service vulnerability",
"severity": "medium"
},
{
"type": "npm-audit",
"message": "diff: jsdiff has a Denial of Service vulnerability in parsePatch and applyPatch",
"severity": "low"
},
{
"type": "npm-audit",
"message": "dompurify: DOMPurify allows tampering by prototype pollution",
"severity": "high"
},
{
"type": "npm-audit",
"message": "esbuild: esbuild enables any website to send any requests to the development server and read the response",
"severity": "medium"
},
{
"type": "npm-audit",
"message": "external-editor: Vulnerability found",
"severity": "low"
},
{
"type": "npm-audit",
"message": "flatted: flatted vulnerable to unbounded recursion DoS in parse() revive phase",
"severity": "high"
},
{
"type": "npm-audit",
"message": "get-func-name: Chaijs/get-func-name vulnerable to ReDoS",
"severity": "high"
},
{
"type": "npm-audit",
"message": "giget: Vulnerability found",
"severity": "high"
},
{
"type": "npm-audit",
"message": "glob: glob CLI: Command injection via -c/--cmd executes matches with shell:true",
"severity": "high"
},
{
"type": "npm-audit",
"message": "immutable: Immutable is vulnerable to Prototype Pollution",
"severity": "high"
},
{
"type": "npm-audit",
"message": "js-yaml: js-yaml has prototype pollution in merge (<<)",
"severity": "medium"
},
{
"type": "npm-audit",
"message": "lodash: lodash vulnerable to Code Injection via `_.template` imports key names",
"severity": "high"
},
{
"type": "npm-audit",
"message": "lodash-es: lodash vulnerable to Code Injection via `_.template` imports key names",
"severity": "high"
},
{
"type": "npm-audit",
"message": "mermaid: Prototype pollution vulnerability found in Mermaid's bundled version of DOMPurify",
"severity": "high"
},
{
"type": "npm-audit",
"message": "minimatch: minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern",
"severity": "high"
},
{
"type": "npm-audit",
"message": "mocha: Vulnerability found",
"severity": "medium"
},
{
"type": "npm-audit",
"message": "nanoid: Predictable results in nanoid generation when given non-integer values",
"severity": "medium"
},
{
"type": "npm-audit",
"message": "next: Next.js missing cache-control header may lead to CDN caching empty reply",
"severity": "critical"
},
{
"type": "npm-audit",
"message": "picomatch: Picomatch: Method Injection in POSIX Character Classes causes incorrect Glob Matching",
"severity": "high"
},
{
"type": "npm-audit",
"message": "postcss: PostCSS line return parsing error",
"severity": "medium"
},
{
"type": "npm-audit",
"message": "rollup: DOM Clobbering Gadget found in rollup bundled scripts that leads to XSS",
"severity": "high"
},
{
"type": "npm-audit",
"message": "semver: semver vulnerable to Regular Expression Denial of Service",
"severity": "high"
},
{
"type": "npm-audit",
"message": "serialize-javascript: Serialize JavaScript is Vulnerable to RCE via RegExp.flags and Date.prototype.toISOString()",
"severity": "high"
},
{
"type": "npm-audit",
"message": "tar: node-tar Vulnerable to Arbitrary File Creation/Overwrite via Hardlink Path Traversal",
"severity": "high"
},
{
"type": "npm-audit",
"message": "tmp: tmp allows arbitrary temporary file / directory write via symbolic link `dir` parameter",
"severity": "high"
},
{
"type": "npm-audit",
"message": "uuid: uuid: Missing buffer bounds check in v3/v5/v6 when buf is provided",
"severity": "medium"
},
{
"type": "npm-audit",
"message": "validator: validator.js has a URL validation bypass vulnerability in its isURL function",
"severity": "high"
},
{
"type": "npm-audit",
"message": "vite: Vite dev server option `server.fs.deny` can be bypassed when hosted on case-insensitive filesystem",
"severity": "high"
},
{
"type": "npm-audit",
"message": "word-wrap: word-wrap vulnerable to Regular Expression Denial of Service",
"severity": "medium"
},
{
"type": "npm-audit",
"message": "yaml: yaml is vulnerable to Stack Overflow via deeply nested YAML collections",
"severity": "medium"
},
{
"file": "README.md",
"line": 44,
"type": "remote-install",
"message": "Install command (remote install script piped to a shell — review the source before running): \"curl -fsSL https://raw.githubusercontent.com/datopian/portaljs/main/scripts/inst\"",
"severity": "low"
}
],
"status": "FAILED",
"scannedAt": "2026-07-08T06:27:34.292Z",
"npmAuditRan": true,
"pipAuditRan": true,
"promptInjectionRan": true
}portaljs is an open-source ai agents skill for AI coding assistants such as Claude Code, Codex CLI, and ChatGPT, built by datopian. 🌀 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). It has 2,304 GitHub stars.
portaljs failed SkillsLLM's automated security scan, which flagged one or more high-severity issues. Review the Security Report section carefully before using it.
Clone the repository with "git clone https://github.com/datopian/portaljs" and add it to your Claude Code skills directory (see the Installation section above).
portaljs is primarily written in TypeScript. It is open-source under datopian on GitHub, so you can review or fork the full source.
Yes. SkillsLLM lists many other AI Agents skills you can browse and compare side by side. Open the AI Agents category from the badge at the top of this page, or use the Related Skills and comparison links further down to weigh portaljs against similar tools.
No comments yet. Be the first to share your thoughts!
Requires a passing catalog security scan. Resolve the flagged issues and resubmit to enable featuring.
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-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](.claude/commands/portaljs-check-data-quali |