Make a board with ChatGPT
Penci.ly boards are a file. That means any chat model can write one: you copy a block of text into ChatGPT, Claude, Gemini or whatever you already use, say what you want, and paste the answer into a board.
There is nothing to install, no account to connect and no API key. Penci.ly does not call a model and never sees your conversation.
How it works
- Copy the prompt below.
- Paste it into any chat model, then describe the board you want underneath it. “A launch plan for a podcast”, “my reading notes on this paper”, “the architecture of this codebase”.
- It replies with a block of JSON. Copy that.
- In Penci.ly, open a board and paste it in. You will see what it is about to create before anything is added.
The prompt
It teaches the format and the four things models tend to get wrong. Paste it whole. The examples in it are doing as much work as the rules.
# Write a Penci.ly board
Penci.ly is a visual canvas: cards, notes, checklists and images arranged in space, with lines drawn between things that relate. You are going to describe one as JSON, and I will import it.
Reply with **one JSON object and nothing else** — no sentence before it, no summary after it. A single ```json fenced block is fine.
## The shape
```json
{
"pencily": 1,
"board": { "name": "What the board is called" },
"elements": [ … ],
"connections": [ { "from": "ref-a", "to": "ref-b", "label": "optional" } ]
}
```
## Rules
1. **Do not invent positions.** Leave out `at` and `size` entirely. Penci.ly lays the board out when they are missing, and it does a better job than a guess — coordinates you make up will overlap into a pile.
2. **Group things into cards.** A `card` has `children`. One card holding five related things beats five loose things. Cards cannot go inside cards.
3. **Give a `ref` only where it is needed.** A `ref` is a name used by `connections` to say which two elements a line joins. Elements nothing points at do not need one.
4. **Write real content.** No "Task 1", no "Item 2", no lorem ipsum. If you do not know something, leave the element out rather than filling it with a placeholder.
5. **Stay within the limits.** At most 300 top-level elements, 100 inside one card, and 450 of everything put together.
## Elements
| `type` | What it is | Fields |
| --- | --- | --- |
| `card` | A titled container. The main way to group things. | `title`, `description`, `children` |
| `text` | A block of writing. | `text` — takes `#` headings, `-` bullets, `**bold**`, `_italic_` |
| `todo` | A checklist. | `title`, `tasks` — strings, or `{ "text": "…", "done": true }` |
| `callout` | A short tinted note that stands out. | `text`, `tone` (neutral/red/orange/yellow/green/blue), `icon` |
| `table` | A grid. | `cells` — rows of strings, `hasHeader` |
| `divider` | A horizontal rule. | all optional |
| `swatch` | Colour tiles. | `label`, `colors` — hex strings or `{ "color": "#…", "label": "…" }` |
| `button` | A labelled block that links somewhere. | `text`, `url` (http(s) only), `tone` |
| `image` | A picture or a video, by address. | `url`, or `video` for YouTube/Vimeo; `title` |
| `page` | A placeholder for a sub-board. | `boardName` |
Every element also accepts `ref`. Nothing else is required beyond the fields marked above.
## A complete example
Note that it has no coordinates anywhere, that related things are inside cards, and that only the two elements joined by a connection carry a `ref`.
```json
{
"pencily": 1,
"board": {
"name": "Podcast launch"
},
"elements": [
{
"ref": "prep",
"type": "card",
"title": "Before the first episode",
"description": "Everything that has to exist before recording.",
"children": [
{
"type": "todo",
"title": "Setup",
"tasks": [
{
"text": "Buy a microphone",
"done": true
},
"Write the show description",
"Design cover art"
]
},
{
"type": "text",
"text": "## Tone\n\nConversational, not scripted. Aim for **25 minutes** an episode."
}
]
},
{
"ref": "guests",
"type": "card",
"title": "Guest shortlist",
"children": [
{
"type": "table",
"cells": [
[
"Name",
"Topic",
"Asked?"
],
[
"Ada Okonjo",
"Type design",
"yes"
],
[
"Ruben Marsh",
"Field recording",
"no"
]
]
}
]
},
{
"type": "callout",
"text": "Episode one has to be recorded before the cover art is final — art takes two weeks.",
"tone": "yellow",
"icon": "warning"
},
{
"type": "swatch",
"label": "Cover palette",
"colors": [
{
"color": "#1d3557",
"label": "Deep"
},
"#457b9d",
"#f1faee"
]
}
],
"connections": [
{
"from": "prep",
"to": "guests",
"label": "blocks",
"endCap": "arrow"
}
]
}
```
## Now
Below this line I will tell you what the board should be about. Reply with the JSON document only.What comes back
A single JSON object. Cards hold the things that belong together, a connection draws a line between two of them, and nothing carries a position, so Penci.ly arranges the board and the model does not have to guess coordinates.
{
"pencily": 1,
"board": {
"name": "Podcast launch"
},
"elements": [
{
"ref": "prep",
"type": "card",
"title": "Before the first episode",
"description": "Everything that has to exist before recording.",
"children": [
{
"type": "todo",
"title": "Setup",
"tasks": [
{
"text": "Buy a microphone",
"done": true
},
"Write the show description",
"Design cover art"
]
},
{
"type": "text",
"text": "## Tone\n\nConversational, not scripted. Aim for **25 minutes** an episode."
}
]
},
{
"ref": "guests",
"type": "card",
"title": "Guest shortlist",
"children": [
{
"type": "table",
"cells": [
[
"Name",
"Topic",
"Asked?"
],
[
"Ada Okonjo",
"Type design",
"yes"
],
[
"Ruben Marsh",
"Field recording",
"no"
]
]
}
]
},
{
"type": "callout",
"text": "Episode one has to be recorded before the cover art is final — art takes two weeks.",
"tone": "yellow",
"icon": "warning"
},
{
"type": "swatch",
"label": "Cover palette",
"colors": [
{
"color": "#1d3557",
"label": "Deep"
},
"#457b9d",
"#f1faee"
]
}
],
"connections": [
{
"from": "prep",
"to": "guests",
"label": "blocks",
"endCap": "arrow"
}
]
}Changing a board you already have
Open the board, then Copy for ChatGPT in its menu. That puts the board and the instructions for editing it on your clipboard together. Paste, say what you want changed, and paste the answer back.
Importing only ever adds to a board and never overwrites what is already there, so an edited board comes back as a new one, with the original left exactly as it was. That is deliberate: a chat model rewriting a board you have worked on is not something that should be able to happen by accident.
What a board can hold
Cards, text, checklists, callouts, tables, dividers, colour swatches, buttons, images and sub-board placeholders, plus connections between any two of them. The prompt lists the fields each one takes.
If you are writing a program rather than a prompt, the format is published as a JSON Schema at /format/schema.json. It works as a structured-output schema as-is.
If something does not import
Penci.ly is forgiving about what it accepts: it understands a bare list of elements, a fenced code block, tasks written as plain strings, and several names for each element type. When it does have to change something, it tells you exactly what and where.
Those notes are written so you can paste them straight back into the same conversation. The model will usually fix the document without needing the format explained again.