carto-develop-app
Generate a working CARTO + deck.gl app from a prompt. Four decisions — app type, framework, auth model, data shape — then assemble layers, widgets, filters, inputs, a legend, and (optionally) an embedded map agent.
Apps follow the CartoDB/deck.gl-examples blueprint. Layers from @deck.gl/carto; data sources, widgets, filters, fetchMap, query() from @carto/api-client (sources left @deck.gl/carto in v0.4.0 — don't import them from there).
Access path. This is a CLI-path skill: scaffolding and running an app needs a real shell and dev environment (npm, Vite), and no MCP tool generates an app — so this can't run on sandboxed chat hosts (Claude.ai, ChatGPT). Only the credential/discovery steps have MCP equivalents: over an OAuth MCP session, mint tokens with manage_api_access_tokens, OAuth/M2M clients with manage_oauth_clients, and discover map/workflow IDs with read_maps / read_workflows. Everything else needs the shell. Path detection: carto-basics/references/access-paths.md.
When to use
- "deck.gl app", "CARTO app", "map app", "spatial dashboard" — use this skill.
- Builder map ID supplied →
fetchmap.md.
- Storymap / scrollytelling / narrative map / guided tour →
storymap.md (composes on top of the React scaffold below).
- Chat-with-map / embedded AI agent →
agentic-variant.md.
- Authoring maps in Builder →
carto-create-builder-maps. Migrating a Builder map across orgs / profiles → cross-profile-copy.md.
Operating mode — autonomous by default
Generate the env via the CARTO CLI. Don't interview the user. Pass --json and parse.
Run, then write .env:
carto auth status --json # → apiBaseUrl (from tenant.domain region)
carto connections list --json # → connectionName (default: carto_dw)
# One token, ONE grant per source — repeat --connection alongside every --source.
carto credentials create token --json \
--connection <connectionName> --source <fully.qualified.table.A> \
--connection <connectionName> --source <fully.qualified.table.B> \
--apis sql,maps \
--referers 'http://localhost:5173*,<production-origin>*'
One token, many grants — not one token per table. --connection and --source pair positionally, so repeat --connection for every --source. Use --referers (plural CSV); --referer (singular) overwrites if repeated. Referers are wildcard-matched against the browser's full page URL, so end each pattern with * (a bare http://localhost:5173 won't match the http://localhost:5173/ browsers actually send). --apis and grants are independent: maps covers sources/layers, sql covers query(), and a *QuerySource needs its query in the grant — see auth-public-token.md.
Private apps swap the token command for carto credentials create spa --json (OAuth) or carto credentials create m2m --json (M2M).
If carto isn't on PATH or auth status fails: say so once and stop — print the missing command. Don't fall back to interviewing. Assumes a working CLI (carto-basics).
Only ask what the CLI can't answer: table name (if not obvious) and ambiguous app-shape choices below.
Decision flow — only ask when truly ambiguous
- Builder map ID supplied? Yes →
fetchmap.md. Done.
1b. Storymap shape? Cues: "story map", "scrollytelling", "guided tour", "scenes/chapters/slides", "fly through". → storymap.md. Still pick the React scaffold + sources/layers below; storymap is a state pattern layered on top, not a separate scaffold.
- Demo or production? Default to vanilla TS + Vite + MapLibre (
scaffold-vanilla.md) unless the prompt says "production", "auth", "deploy", "team", "multi-screen" → React (scaffold-react.md). Vue / Angular only if explicitly named (scaffold-vue-angular.md).
- Auth model? Pick from prompt cues; ask only if cues conflict.
- "public" / "share" / "embed" / no login → API access token (
auth-public-token.md)
- "private" / "users log in" / "CARTO login" → OAuth SPA (
auth-private-oauth.md)
- "SSO" / "Okta" / "Azure AD" / "corporate IdP" → OAuth + SSO (
auth-private-sso.md)
- "backend" / "ETL" / "CI" / "scheduled" → M2M (
auth-m2m.md)
- Data shape? Source/layer pair from
data-sources.md + layers.md: points/lines/polygons → vector; H3 → H3; quadbin → quadbin; surfaces → raster. Wire the basemap and view-state sync via basemap-and-view.md.
Then layer in only what was asked for: widgets, filters, inputs, legend, SQL/workflows, agentic chat. Recipes in recipes/.
- Visual style? No style cues → Meridian-inspired default (clean, professional, CARTO-native). Any style cue ("futuristic", "corporate", a named design system) → custom theme matching the user's description. Either way, apply the UX layout principles. See
design-and-theming.md.
Always-on guidance
apiBaseUrl comes from carto auth status --json (tenant.domain region). Never hard-code.
connectionName defaults to carto_dw; confirm via carto connections list --json.
- Public tokens must always pass
--source and limit --apis to sql,maps. Never imports / lds in a public bundle.
- Complex analysis is a workflow, not app SQL. Predicting revenue, composite/suitability scores, segmentation, multi-step joins — build it with
carto-create-workflow; the app then reads its output table or CALLs its procedure via the Workflows API (or, if an embedded agent drives it, invokes it as a published MCP tool). Heavy logic stays server-side; the app passes parameters. See workflows-and-sql.md.
- One
filters object is shared by source helpers and widget methods. Mutating it triggers re-fetch on both.
- Layer z-order is the
layers array, last = on top — not CSS z-index. Restack by changing array position and passing a fresh array (never mutate in place); z-index orders DOM panels, never layers on the shared canvas. See layers.md. (Opaque fills hiding basemap labels is a separate, basemap-side question — basemap-and-view.md.)
- Debounce viewport spatial filters ~300 ms on
onViewStateChange.
- Design — apply the Meridian-inspired default theme unless the user specifies a different aesthetic. UX layout principles (map as hero, panel discipline, progressive disclosure) always apply. See
design-and-theming.md.
- Never invent logos. Only render a logo (CARTO, customer, partner, third-party brand) when the user supplied a verified PNG / JPG / SVG asset or pointed to an official source. No verified asset → ask the user, or use a text wordmark / omit the logo entirely. Do not generate SVG marks, fetch from unverified URLs, or substitute a similar-looking brand. Applies even to well-known brands.
- End by running the app. Run
npm install && npm run dev and report the URL.
1---2name: carto-develop-app3description: Generate a working geospatial app powered by CARTO and deck.gl — basemap, layers (vector / H3 / quadbin / raster), widgets, filters, legend, inputs, optional chat-with-map agent, and the right auth strategy (public token, OAuth, SSO, or M2M).4license: MIT5---67# carto-develop-app89Generate a working CARTO + [deck.gl](https://deck.gl) app from a prompt. Four decisions — **app type**, **framework**, **auth model**, **data shape** — then assemble layers, widgets, filters, inputs, a legend, and (optionally) an embedded map agent.1011Apps follow the [CartoDB/deck.gl-examples](https://github.com/CartoDB/deck.gl-examples) blueprint. Layers from `@deck.gl/carto`; data sources, widgets, filters, `fetchMap`, `query()` from `@carto/api-client` (sources left `@deck.gl/carto` in v0.4.0 — don't import them from there).1213> **Access path.** This is a **CLI-path skill**: scaffolding and running an app needs a real shell and dev environment (npm, Vite), and no MCP tool generates an app — so this can't run on sandboxed chat hosts (Claude.ai, ChatGPT). Only the credential/discovery steps have MCP equivalents: over an **OAuth** MCP session, mint tokens with `manage_api_access_tokens`, OAuth/M2M clients with `manage_oauth_clients`, and discover map/workflow IDs with `read_maps` / `read_workflows`. Everything else needs the shell. Path detection: [`carto-basics/references/access-paths.md`](../carto-basics/references/access-paths.md).1415## When to use1617- "deck.gl app", "CARTO app", "map app", "spatial dashboard" — use this skill.18- Builder map ID supplied → [`fetchmap.md`](references/fetchmap.md).19- Storymap / scrollytelling / narrative map / guided tour → [`storymap.md`](references/storymap.md) (composes on top of the React scaffold below).20- Chat-with-map / embedded AI agent → [`agentic-variant.md`](references/agentic-variant.md).21- *Authoring* maps in Builder → [`carto-create-builder-maps`](../carto-create-builder-maps). *Migrating* a Builder map across orgs / profiles → [`cross-profile-copy.md`](../carto-create-builder-maps/references/cross-profile-copy.md).2223## Operating mode — autonomous by default2425**Generate the env via the CARTO CLI. Don't interview the user.** Pass `--json` and parse.2627Run, then write `.env`:2829```bash30carto auth status --json # → apiBaseUrl (from tenant.domain region)31carto connections list --json # → connectionName (default: carto_dw)3233# One token, ONE grant per source — repeat --connection alongside every --source.34carto credentials create token --json \35 --connection <connectionName> --source <fully.qualified.table.A> \36 --connection <connectionName> --source <fully.qualified.table.B> \37 --apis sql,maps \38 --referers 'http://localhost:5173*,<production-origin>*'39```4041**One token, many grants** — not one token per table. `--connection` and `--source` pair positionally, so repeat `--connection` for every `--source`. Use `--referers` (plural CSV); `--referer` (singular) overwrites if repeated. Referers are wildcard-matched against the browser's full page URL, so end each pattern with `*` (a bare `http://localhost:5173` won't match the `http://localhost:5173/` browsers actually send). `--apis` and grants are independent: `maps` covers sources/layers, `sql` covers `query()`, and a `*QuerySource` needs its query in the grant — see [`auth-public-token.md`](references/auth-public-token.md).4243Private apps swap the token command for `carto credentials create spa --json` (OAuth) or `carto credentials create m2m --json` (M2M).4445If `carto` isn't on `PATH` or `auth status` fails: say so once and stop — print the missing command. Don't fall back to interviewing. Assumes a working CLI ([`carto-basics`](../carto-basics)).4647Only ask what the CLI can't answer: **table name** (if not obvious) and ambiguous app-shape choices below.4849## Decision flow — only ask when truly ambiguous50511. **Builder map ID supplied?** Yes → [`fetchmap.md`](references/fetchmap.md). Done.521b. **Storymap shape?** Cues: "story map", "scrollytelling", "guided tour", "scenes/chapters/slides", "fly through". → [`storymap.md`](references/storymap.md). Still pick the React scaffold + sources/layers below; storymap is a state pattern layered on top, not a separate scaffold.532. **Demo or production?** Default to **vanilla TS + Vite + MapLibre** ([`scaffold-vanilla.md`](references/scaffold-vanilla.md)) unless the prompt says "production", "auth", "deploy", "team", "multi-screen" → React ([`scaffold-react.md`](references/scaffold-react.md)). Vue / Angular only if explicitly named ([`scaffold-vue-angular.md`](references/scaffold-vue-angular.md)).543. **Auth model?** Pick from prompt cues; ask only if cues conflict.55 - "public" / "share" / "embed" / no login → API access token ([`auth-public-token.md`](references/auth-public-token.md))56 - "private" / "users log in" / "CARTO login" → OAuth SPA ([`auth-private-oauth.md`](references/auth-private-oauth.md))57 - "SSO" / "Okta" / "Azure AD" / "corporate IdP" → OAuth + SSO ([`auth-private-sso.md`](references/auth-private-sso.md))58 - "backend" / "ETL" / "CI" / "scheduled" → M2M ([`auth-m2m.md`](references/auth-m2m.md))594. **Data shape?** Source/layer pair from [`data-sources.md`](references/data-sources.md) + [`layers.md`](references/layers.md): points/lines/polygons → vector; H3 → H3; quadbin → quadbin; surfaces → raster. Wire the basemap and view-state sync via [`basemap-and-view.md`](references/basemap-and-view.md).6061Then layer in only what was asked for: [widgets](references/widgets.md), [filters](references/filters.md), [inputs](references/inputs-and-parameters.md), [legend](references/legend.md), [SQL/workflows](references/workflows-and-sql.md), [agentic chat](references/agentic-variant.md). Recipes in [`recipes/`](references/recipes/).62635. **Visual style?** No style cues → Meridian-inspired default (clean, professional, CARTO-native). Any style cue ("futuristic", "corporate", a named design system) → custom theme matching the user's description. Either way, apply the UX layout principles. See [`design-and-theming.md`](references/design-and-theming.md).6465## Always-on guidance6667- **`apiBaseUrl`** comes from `carto auth status --json` (`tenant.domain` region). Never hard-code.68- **`connectionName`** defaults to `carto_dw`; confirm via `carto connections list --json`.69- **Public tokens** must always pass `--source` and limit `--apis` to `sql,maps`. Never `imports` / `lds` in a public bundle.70- **Complex analysis is a workflow, not app SQL.** Predicting revenue, composite/suitability scores, segmentation, multi-step joins — build it with [`carto-create-workflow`](../carto-create-workflow); the app then reads its output table or `CALL`s its procedure via the Workflows API (or, if an embedded agent drives it, invokes it as a published **MCP tool**). Heavy logic stays server-side; the app passes parameters. See [`workflows-and-sql.md`](references/workflows-and-sql.md).71- **One `filters` object** is shared by source helpers *and* widget methods. Mutating it triggers re-fetch on both.72- **Layer z-order is the `layers` array, last = on top — not CSS `z-index`.** Restack by changing array position and passing a *fresh* array (never mutate in place); `z-index` orders DOM panels, never layers on the shared canvas. See [`layers.md`](references/layers.md). (Opaque fills hiding basemap labels is a separate, basemap-side question — [`basemap-and-view.md`](references/basemap-and-view.md).)73- **Debounce viewport spatial filters ~300 ms** on `onViewStateChange`.74- **Design** — apply the Meridian-inspired default theme unless the user specifies a different aesthetic. UX layout principles (map as hero, panel discipline, progressive disclosure) always apply. See [`design-and-theming.md`](references/design-and-theming.md).75- **Never invent logos.** Only render a logo (CARTO, customer, partner, third-party brand) when the user supplied a verified PNG / JPG / SVG asset or pointed to an official source. No verified asset → ask the user, or use a text wordmark / omit the logo entirely. Do not generate SVG marks, fetch from unverified URLs, or substitute a similar-looking brand. Applies even to well-known brands.76- **End by running the app.** Run `npm install && npm run dev` and report the URL.