John KuehJohn Kueh
All skills

Claude Code skill

notion-page

Read any Notion page into clean markdown.

Install all skills in one command:

claude mcp add-plugin johnkueh/claude-skills

Why it exists

Notion's API returns blocks as JSON. Claude needs them as markdown. Copy-pasting from the Notion web UI strips inline structure (toggles, callouts, code blocks). This skill takes a Notion URL or page ID and returns title, metadata and body as clean markdown ready to feed into another prompt.

In practice

Fetch a page
Input
notion-page https://notion.so/Project-Spec-abc123
Output
# Project Spec\n_Last edited: 2026-05-20_\n\nMarkdown body with headings, lists, callouts and code blocks preserved.
skills/notion-page/SKILL.mdRaw
---
name: notion-page
description: Read the full content of a Notion page from a URL or page ID. Returns title, metadata, and the page body as markdown. Use when the user pastes a Notion link or asks to read, summarize, or extract content from a Notion page. Triggers on notion.so URLs, "read this notion page", "fetch from notion", "what's in this notion doc".
---

# Notion Page

Fetch a Notion page's title and body content as markdown via the Notion API.

## Setup

Requires a Notion internal integration token in `.env` (in CWD or in this skill's directory):

```
NOTION_TOKEN=ntn_...
```

The page must be **shared with the integration**: open the page in Notion → `Share` → `Connections` → add the integration. Without sharing, the API returns 404 even though the page exists.

Dependencies install on first run via `uv` (notion-client, python-dotenv).

## Commands

Run from this skill's base directory.

### Read a page (JSON output, default)

```bash
uv run python cli.py "https://www.notion.so/Magic-Tags-Product-spec-31a4cb32d18780bbafeaf1e0d7660594?source=copy_link"
```

### Read a page (markdown output)

```bash
uv run python cli.py "<url-or-id>" --format md
```

### Truncate large pages

```bash
uv run python cli.py "<url-or-id>" --max-chars 20000
```

Accepts: full URL, dashed page ID (`31a4cb32-d187-80bb-afea-f1e0d7660594`), or bare 32-char hex (`31a4cb32d18780bbafeaf1e0d7660594`).

### Image handling

By default the page's signed S3 image URLs are kept inline (each is several KB of query-string noise — bad for context). Pick a different mode:

```bash
# Download images to results/<page>/images/ and rewrite markdown to relative paths.
# Best mode for agent use: agent gets local paths, can `Read` any image on demand.
uv run python cli.py "<url-or-id>" --images download

# Inline images as data: URLs. Self-contained but bloats context massively
# (a 7-image page becomes ~600K tokens). Use only if you need a single artifact.
uv run python cli.py "<url-or-id>" --images base64

# Replace every image with `[image]` placeholder. Smallest output.
uv run python cli.py "<url-or-id>" --images strip

# Custom output directory for --images=download
uv run python cli.py "<url-or-id>" --images download --out-dir /tmp/magic-tags
```

When `--images=download`, output also includes `page.md` and `images/img_<hash>.<ext>` next to each other, so the markdown's relative paths resolve.

## JSON output shape

```json
{
  "url": "https://www.notion.so/...",
  "page_id": "31a4cb32d18780bbafeaf1e0d7660594",
  "title": "Magic Tags — Product spec",
  "created_time": "2026-...",
  "last_edited_time": "2026-...",
  "content": "## Heading\n\nBody as markdown...",
  "content_length": 4823,
  "truncated": false,
  "images_mode": "download",
  "output_dir": "results/31a4cb32d187_2026-04-28_150833",
  "markdown_path": "results/.../page.md",
  "images": [
    {"path": ".../img_aa209471db1f.png", "relative": "images/img_aa209471db1f.png", "size": 223152, "mime": "image/png", "source_url": "..."}
  ]
}
```

The `images` array and output paths only appear when `--images=download`.

## Block coverage

Headings (1–3), paragraphs, bulleted/numbered lists, to-dos, toggles, quotes, callouts, code (with language), dividers, bookmarks/embeds, images/video/files/pdfs, tables, equations, child pages, child databases. Nested children render with indentation. Unsupported block types emit `<!-- unsupported block: <type> -->` so nothing disappears silently.

## Troubleshooting

- **404 / "object_not_found"** — the integration isn't added to the page. Share → Connections → add it.
- **"NOTION_TOKEN not set"** — drop a `.env` next to `cli.py` or in the directory you're running from.