Skip to content
Development
Command

/portaljs-add-map

Render a GeoJSON dataset on an interactive Leaflet map in the Views section of a dataset's showcase. Installs react-leaflet and a Map component, then renders the map for the chosen dataset.

From plugin
portaljs
2.3k25 skills25 commands
Install
> /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-add-map

Context preview

What this command does when you run it.

Render a GeoJSON dataset on an interactive Leaflet map in the Views section of a dataset's showcase. Installs react-leaflet and a Map component, then renders the map for the chosen dataset.

Command definition

portaljs-add-map.md
description: Render a GeoJSON dataset on an interactive Leaflet map in the Views section of a dataset's showcase. Installs react-leaflet and a Map component, then renders the map for the chosen dataset.
allowed-tools: Read, Write, Edit, Bash, WebFetch

/portaljs-add-map

Add an interactive Leaflet map as a **view on a dataset's showcase** in a `portaljs-catalog` portal. Installs `react-leaflet`/`leaflet` (once), generates a reusable `Map` component, and renders the map into the **Views** section of the showcase route `pages/[owner]/[slug].tsx` for the chosen GeoJSON dataset.

Use this when a dataset's data is geographic (points, lines, polygons) and you want a map view in addition to the showcase's default metadata + download. The dataset should already be registered in `datasets.json` (e.g. via `/portaljs-add-dataset`) with `format: "geojson"`; if it isn't yet, this skill can copy the file and add the entry first.

Required input — ask, don't error

  • **Dataset** — which dataset to map, by **slug**. It should be a `geojson` entry in

`datasets.json`. If a source file/URL is given for a not-yet-registered dataset, the skill copies it into `/public/data/` and appends a manifest entry.

  • **Portal directory** — path to the portal project (defaults to current directory).
  • **Source** (only if the dataset isn't registered yet) — a local file path

(`./data/file.geojson`) or public URL. Must be **GeoJSON** (a `Feature`, `FeatureCollection`, or geometry object).

**If the target dataset isn't specified, ask which one (by name/slug) — never dead-end with a missing-input error.**

Steps

1. Gather input from `$ARGUMENTS` (interview if thin)

Extract:

  • `DATASET` — dataset slug (the map target)
  • `SOURCE` — file path or URL (only needed if the dataset isn't already in the manifest)
  • `PORTAL_DIR` — portal directory (default: `.`)
  • `MAP_SLUG` — slug for a new dataset (default: lowercase hyphenated filename)
  • `MAP_NAME` — human-readable name (default: derived from filename)
  • `NAMESPACE` — namespace for a new dataset (default: the catalog's existing namespace)
  • `DESCRIPTION` — optional one-line description

If neither a dataset nor a source is given, **ask** and wait. When the user doesn't know the slug, read `PORTAL_DIR/datasets.json` and list the GeoJSON datasets so they can pick:

To add a map I need either:
1. Which existing dataset to map? (slug — GeoJSON datasets in your catalog: <name (slug)>, …)
   …or a GeoJSON source to add and map:
2. Source: local file path or public URL to a GeoJSON file
3. Portal directory (Enter for current directory)

2. Validate the portal directory

The target must be a `portaljs-catalog` portal. Confirm `PORTAL_DIR/datasets.json`, `PORTAL_DIR/package.json`, and `PORTAL_DIR/pages/[owner]/[slug].tsx` exist. If they don't, tell the user this isn't the catalog template and ask how to proceed rather than failing silently.

3. Resolve (or register) the GeoJSON dataset

**If `DATASET` is already in `datasets.json`:** read its entry and capture `namespace`, `slug`, and `file` (must be served from `/public/data/<file>`). Confirm `format` is `geojson`; if it's tabular, tell the user `/portaljs-add-map` only renders GeoJSON and ask whether they meant a different dataset (or `/portaljs-add-chart`).

**If a `SOURCE` was given for a not-yet-registered dataset:** fetch/copy and validate it, then append a manifest entry so the showcase exists:

  • URL: fetch it; if the status isn't 200, tell the user (with the HTTP status) and ask

them to confirm the URL is publicly accessible / supports CORS.

  • Local path: if the file doesn't exist, tell the user and ask for a correct path.
  • **Validate it is GeoJSON:** parse as JSON and confirm `type` is one of

`FeatureCollection`, `Feature`, `GeometryCollection`, `Point`, `MultiPoint`, `LineString`, `MultiLineString`, `Polygon`, or `MultiPolygon`. If not, tell the user it isn't valid GeoJSON (for tabular data use `/portaljs-add-dataset`) and stop.

  • Copy and register:
  mkdir -p PORTAL_DIR/public/data
  cp SOURCE PORTAL_DIR/public/data/MAP_SLUG.geojson
  # or for URLs: curl -L SOURCE -o PORTAL_DIR/public/data/MAP_SLUG.geojson

Then append to `datasets.json` (matching the `Dataset` shape in `lib/datasets.ts`):

  {
    "slug": "MAP_SLUG",
    "namespace": "NAMESPACE",
    "name": "MAP_NAME",
    "description": "DESCRIPTION",
    "file": "MAP_SLUG.geojson",
    "format": "geojson"
  }

Capture the final `NAMESPACE`, `SLUG`, and `FILE` for use when rendering the map.

4. Install map dependencies (once)

The template does not bundle a map component. Install Leaflet directly. Check whether `react-leaflet` is already in `PORTAL_DIR/package.json` — if so, skip this step.

cd PORTAL_DIR && npm install react-leaflet@^5 leaflet@^1.9 && npm install -D @types/leaflet

Tell the user first: `Installing map dependencies (react-leaflet, leaflet)...`

If install fails, tell the user (check Node.js >=22 and network access) and retry.

> Why `react-leaflet@^5`: v5 targets React 19, which the catalog template uses. (If a > portal is still on React 18, install `react-leaflet@^4` instead — its peer dep requires > React 18.)

5. Generate the `Map` component (once)

Leaflet touches `window` at module load, so it **must not** be server-rendered. The component is split in two: `MapView.tsx` holds the Leaflet code, and `Map.tsx` is a thin wrapper that loads it with `dynamic(..., { ssr: false })`. Only the type is imported across the boundary (erased at build), so Leaflet never reaches the server bundle.

Skip this step if `PORTAL_DIR/components/Map.tsx` already exists.

Write `PORTAL_DIR/components/MapView.tsx`:

import { useEffect, useState } from 'react'
import { MapContainer, TileLayer, GeoJSON, useMap } from 'react-leaflet'
import L from 'leaflet'
import type { GeoJsonObject } from 'geojson'
import 'leaflet/dist/leaflet.css'

// Fits the viewport to the data
Read more
Ships withportaljs

🌀 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).

Get the whole plugin