Agent Skill · Carto

carto-create-builder-maps

Author, edit, publish, and validate CARTO Builder maps via the `carto maps` CLI. Use when the user wants to create a map from a natural-language request, edit an existing map (datasets, layers, styling, privacy, popups, widgets, SQL parameters), duplicate one, upload custom marker icons, or wire up an AI agent on a map. Covers the full `carto maps` subcommand surface — `list`, `get`, `create`, `update`, `delete`, `publish`, `validate`, `schema`, `agents`, `markers`, `screenshot`, `datasets update`.

Provider: Carto Path in repo: skills/carto-create-builder-maps/SKILL.md

Skill body

carto-create-builder-maps

CARTO Builder is a mapping tool that renders interactive maps from a JSON map configuration. This skill covers the full authoring lifecycle via the CLI: create from natural language, edit datasets / layers / widgets / popups / privacy, publish snapshots for shared viewers, validate offline, and operate via the carto maps commands. It also covers cross-profile copy (dev → prod promotion, customer-segregated org delivery via carto maps copy / maps clone) — see the Promote / copy across orgs references below.

For ad-hoc spatial SQL exploration, use carto-query-datawarehouse.

Field shapes, enum values, palette catalogues, and AI-tool catalogues are served by the CLI — never hardcode or assume them. Run carto maps schema [section] for JSON Schema (generated from the same Zod definitions Tier-1 validation uses), carto maps agents models / mcp-tools / core-tools for AI surfaces, and carto connections describe <conn> <table> for dataset metadata. When this doc disagrees with the CLI, the CLI wins.

References

Decision / orientation — read first

Per-component — consult on demand while authoring

Operate / unstick

Promote / copy across orgs — read when migrating maps between profiles


Authoring process

Follow these phases in order for every “create a map” request. Skipping a phase is the most common cause of “the map looks broken in Builder”.

Phase 1 — Gather context (intake gate)

This phase is a gate, not a suggestion. But the order matters: the data is the only fact that constrains what’s even askable. Asking the user abstract preferences (audience, mode, widgets, sharing) before knowing what’s in the table produces generic questions that often don’t apply, and makes the user do the agent’s job of mapping wishes to columns.

Sequence

  1. Goal — one line. “What’s the map about, and what’s the takeaway?” Don’t proceed without an answer; “just make a map of X” is fine if X is specific.
  2. Data hint — one line. “Where’s the data — a table you already have, a demo dataset, or a file to import?” Resolve to a concrete table FQN before moving on. Demo data: search carto-demo-data.demo_tables by topic. File: run carto imports create first.
  3. SILENT data inspection. Before asking anything else:
    • carto connections describe <conn> <table> → schema, row count, geom type (point / line / polygon / h3 / quadbin / raster).
    • carto sql query for: NULL ratios on candidate colorField columns, min/max/p50/p95/p99 on numeric columns relevant to the goal, COUNT(DISTINCT ...) on candidate categorical columns to detect cardinality traps, date range on temporal columns.
    • Cap inspection at one or two queries. Don’t audit every column. Inspect what’s relevant to the user’s goal.
  4. Questions, NOW data-contingent. Only ask what the data makes answerable. Examples of good data-grounded questions versus bad abstract ones:

    Bad (abstract, asked too early) Good (data-grounded, asked after inspect)
    “Want widgets?” “I see capacity_repd_mwp (sum to ~13 GW) and repd_status (5 categories) — want a capacity total + a status breakdown widget?”
    “Analytical or cartographic?” “The table has 23 columns including operational date, area, capacity. Strong analytical map territory — propose a histogram of capacity, or stay simple?”
    “Public or private?” (sharing is orthogonal to data; ask cleanly when relevant — never on first turn)
    “Which palette?” “With 265k installations, points will overlap heavily — propose low opacity + uniform colour, or color by capacity (heavy-tailed → log scale)?”

    If the data answers a question on its own, don’t ask — just decide:

    • Geom type → layer type, silently: polygon / line / point → tileset; pre-indexed h3 / quadbin → h3 / quadbin directly; raster → raster.
    • Viewport → bounding box of the data.
    • NULL ratio on a candidate colorField > 25% → switch column or filter WHERE col IS NOT NULL silently. See cartography.md §4.5a.
  5. Sharing / audience / agent — ask only when triggered. Don’t gate the first map render on these. Default to private. Surface them when the user says “share with the team”, “send to my CEO”, “add an AI agent”.

Technical preconditions (silent — don’t surface unless they fail)

Time budget

First-version target: ~30s wall time from the user’s “go” to a working URL. Achievable when the only intake is goal + data hint and the agent inspects silently. The first map still has to look good — cartographic defaults from cartography.md (palette family, scale, basemap pairing, multi-layer hue separation) apply on the first shot; refinement (custom domains, palette swaps, widget tuning) lands on subsequent turns with the user looking at the result.

Phase 2 — Make cartographic decisions

Read references/cartography.md ahead of writing JSON when styling is in scope. State explicit choices before emitting:

Skip cartography ahead-of-time only on purely structural work (rename, dataset swap, privacy change, agent-config edit, mapSettings tweaks).

Phase 3 — Compose the configuration

Reference references/configuration-shape.md for the skeleton. Fill in:

Phase 4 — Validate offline

carto maps validate map.json

Tier-1 catches shape, types, enum values, cross-references, agent fields, aggregationExp coherence, privacy coercion, and the dozen-or-so cross-field rules (canonical visualChannels path, custom-marker pairings, popup hover cap, etc.) — all with zero backend calls. Iterate until clean.

Phase 5 — Create + verify

carto maps create < map.json

The CLI runs Tier-1 + a SELECT … WHERE 1=0 source-accessibility probe per dataset BEFORE POST /maps, so broken sources never create orphan maps. The probe automatically excludes synthetic _carto_* columns and post-aggregation aliases parsed from aggregationExp, so legitimate h3 / quadbin / heatmapTile / clusterTile authoring won’t trip it. After create, decide whether to run carto maps screenshot <id> for visual verification — see the “Visual verification” always-on rule below for the decision rubric.

Phase 6 — Publish (when ready for viewers)

maps create writes a private draft. To make a map visible to the user’s intended audience:

Tell the user “it’s live for viewers” after a successful publish; otherwise make clear the edits are visible only to them.


Always-on rules

These apply on every task, not just the create flow.

Lead with intent — hide the plumbing

When asking the Phase 1 intake questions (and on every follow-up turn), stay in plain language. Do NOT surface dataId, geoColumn, tilejson, keplerMapConfig, connectionId, FQN syntax, or layer-type taxonomy on turn 1 — that reads as a spec dump and makes the user do your job. Frame every question in terms the user already has: “what’s the map about”, “who reads it”, “should viewers be able to filter”, “how should it be shared”. Translate to the JSON in your head; don’t ask the user to.

Do silently, don’t ask

Opt-in blocks — emit ONLY when the user has explicitly asked

Don’t offer them proactively, don’t list them in “what else can I do?” unless the user is clearly exploring:

Block Emit when…
agent User asks for an Agent on the map. Run carto maps agents status first; drop if disabled.
privacy (non-private) User asks to share. Default stays private.
tags / description User supplies them, or the map is being published externally.
collaborative User asks for other org members to edit, not just view.
Custom palette / 3D / custom markers User asks for specific styling, or the default looks wrong.

Validate before you write

When you’ve assembled a map configuration and want an offline sanity check before burning an API call, run carto maps validate <map.json>. Same Tier-1 checks as create with zero backend calls. Useful when iterating in a loop or handing the JSON to the user.

Reload Builder after a write

Every write returns as soon as the server accepts the change. Builder loads the map into its in-memory client state once and does not subscribe to server events, so an open https://<org>/builder/<id> tab keeps showing stale state until the tab reloads. For remote / external agents (Claude in claude.ai, ChatGPT, MCP clients, anything without local browser access): tell the user. “Map updated. Reload the Builder tab (Cmd/Ctrl+R) to see changes.”

Visual verification — carto maps screenshot, decided by map shape (no need to ask)

carto maps screenshot <id> renders the map to a PNG. Two engines:

Don’t ask the user “want a screenshot?” — they can’t tell whether it’s worth the latency. Decide based on the map’s shape, run it, then embed the resulting PNG inline in the conversation so the user sees what landed without leaving chat. The latency cost is real (~5–20 s on top of the create), so use it deliberately, not reflexively.

Run a screenshot when:

Skip the screenshot when:

Engine pick: default to light for speed; switch to full when the verification depends on widgets or legends (those don’t render in light — they only show on the Builder/viewer surface that full captures). Popups (hover / click / info-panel / custom HTML templates) don’t render on screenshots at all — neither engine captures them, so don’t pick full to verify popup output. When in doubt with high stakes (public publish, complex multi-layer): pay the full-engine cost.

See references/troubleshooting.md for full screenshot flag reference (--render-engine, --width, --height, --lat/--lng/--zoom, --hide-overlays, etc.).

keplerMapConfig is wholesale-replace, not partial-merge

Most top-level fields on maps update accept partial patches: title, description, tags, collaborative, privacy, agent, datasets. keplerMapConfig does not. Sending {keplerMapConfig: {config: {basemapConfig: {...}}}} as a “partial update” wipes layers / widgets / sqlParameters / viewport. To change anything inside keplerMapConfig, use the read-modify-write cycle: carto maps get <id> --json > /tmp/m.json, edit, carto maps update <id> /tmp/m.json. The CLI rejects wipe-causing partial updates pre-flight; see updates.md for the full merge matrix.

Don’t fabricate a map id from a title

If the user refers to a map by name / title rather than UUID:

  1. carto maps list --mine --search "<hint>" — narrows to the user’s own maps matching the hint.
  2. Exactly one match → use its id and confirm before writing.
  3. Multiple matches → list them with ids + titles, ask which.
  4. Zero matches → ask if they meant to create a new map.

Never pick a match and write without the user confirming, and never invent a UUID from a title alone.

Reserve the spec for when the user asks for it

If they say “show me the JSON” / “I’ll write it myself” / “what’s the schema”, open carto maps schema + configuration-shape.md. Otherwise keep the conversation about their map, not about ours.


Cheat sheet

The work is almost always one of three shapes:

I want to… Do this
Create a map from a natural-language request (the common path) Elicit the required inputs from the user (Phase 1), then emit a configuration with just those, carto maps create < map.json
Edit an existing map — add a dataset, update a layer’s style, change privacy, rename, swap basemap, etc. carto maps update <id> < partial.json (partial PATCH; unmentioned fields are preserved — except keplerMapConfig which is wholesale-replaced)
Duplicate an existing map carto maps get <id> --json > map.json, edit, carto maps create < map.json

Render + sources + agent checks run automatically on every create / update and surface as warnings — no separate “validate it will render” step needed.

Commands you reach for most

carto maps list --mine                            # browse what's already there
carto maps get <id> --json                        # read a configuration; pipe back to create/update
carto maps validate [map.json]                    # Tier-1 sanity check, no API calls
carto maps create [map.json]                      # new map from a configuration file
carto maps update <id> [patch.json] [--publish]   # partial update, optional auto-publish
carto maps publish <id>                           # freeze a snapshot for shared/public viewers
carto maps schema [section]                       # JSON Schema reference
carto maps agents status                          # is CARTO AI enabled on this organization?
carto maps screenshot <id>                        # PNG render for visual verification
carto --commands --json                           # full CLI command catalogue (machine-readable)

Inline recipes

Duplicate an existing map — most reliable way to produce a working map; the source configuration is already Builder-shaped, so the partial-layer pitfall doesn’t apply.

carto maps get <source-map-id> --json \
  | jq '.title = "My copy" | del(.id, .privacy)' \
  > new-map.json
carto maps create < new-map.json

Update only the title (partial PATCH):

echo '{"title":"Better title"}' | carto maps update <map-id> --json

Add one dataset to an existing map (merge mode keeps existing datasets):

carto maps get <map-id> --json > current.json
jq '.datasets += [{"$ref":"extra","type":"table","source":"proj.ds.new_table","connectionId":"<conn-id>","geoColumn":"geom","columns":["geom"],"format":"tilejson","label":"New source"}]' current.json > updated.json
carto maps update <map-id> --json < updated.json

Replace all datasets (destructive — datasets not in the input are deleted):

carto maps update <map-id> --datasets-mode replace --json < map.json

Update a single dataset’s source SQL (no keplerMapConfig wipe risk):

carto maps datasets update <map-id> <dataset-id> --source "SELECT geom, revenue FROM stores WHERE country = 'ES'"

For longer recipes (full JSON bundles), see references/examples.md.


When in doubt

Skill frontmatter

license: MIT