---
title: Notion
icon: /images/notion-logo.svg
description: Mount Notion pages and databases as a Mirage filesystem.
---

`NotionResource` exposes a Notion workspace as a filesystem. The Node SDK talks to the Notion REST API with an integration token; the Browser SDK talks to Notion's MCP server with an OAuth client provider so secrets stay off the page.

## Node

```bash
pnpm add @struktoai/mirage-node
```

```ts
import { MountMode, NotionResource, Workspace } from '@struktoai/mirage-node'

const notion = new NotionResource({ apiKey: process.env.NOTION_API_KEY! })

const ws = new Workspace({ '/notion': notion }, { mode: MountMode.READ })
await ws.execute('ls /notion/pages/')
```

| Field | Default | Notes |
| --- | --- | --- |
| `apiKey` | required | Notion internal integration token. Redacted in snapshots. |
| `baseUrl` | `https://api.notion.com/v1` | Override for testing against a mock server. |

## Browser

```bash
pnpm add @struktoai/mirage-browser @modelcontextprotocol/sdk
```

```ts
import { MountMode, NotionResource, Workspace } from '@struktoai/mirage-browser'

const notion = new NotionResource({
  authProvider, // OAuthClientProvider from @modelcontextprotocol/sdk
})

const ws = new Workspace({ '/notion': notion }, { mode: MountMode.READ })
await ws.execute('ls /notion/')
```

| Field | Default | Notes |
| --- | --- | --- |
| `authProvider` | required | `OAuthClientProvider` from `@modelcontextprotocol/sdk`. Redacted in snapshots. |
| `serverUrl` | Notion's hosted MCP endpoint | Override if you proxy MCP through your own URL. |

## Mount mode

`read`, `write` (page edits via the MCP server in the browser SDK).

## Acting on Notion

Acting on Notion (creating, editing and trashing pages, querying data
sources, and every route that has no typed verb) goes through the
[ntn CLI](/typescript/cli/ntn) when installed.

## Layout

```text
/notion/
  pages/
    <page-title>__<page-id>/
      page.json
      <child-page-title>__<child-id>/
        page.json
        ...
  databases/
    <database-title>__<database-id>/
      database.json
      <data-source-name>__<data-source-id>/
        data_source.json
        <row-page-title>__<page-id>/
          page.json
          ...
```

Since the `2025-09-03` API generation a database is a container of **data
sources**, and both the column schema and the rows live on the data
source, so a row page sits one level deeper than the page tree.
`database.json` is the container's identity plus the `data_sources` stubs
that name the directories beneath it, and carries **no** `properties`.
The shape is identical to the Python connector:

```json
{
  "database_id": "eeee1111-2222-3333-4444-555566667777",
  "title": "Tasks",
  "url": "https://www.notion.so/eeee1111222233334444555566667777",
  "created_time": "2026-01-01T00:00:00.000Z",
  "last_edited_time": "2026-01-02T00:00:00.000Z",
  "parent": { "type": "workspace", "workspace": true },
  "archived": false,
  "is_inline": false,
  "data_sources": [
    { "id": "d5000000-2222-3333-4444-555566667777", "name": "Tasks" }
  ]
}
```

`data_source.json` holds the typed column schema under `properties`; `ls`
the data source directory to enumerate row pages.

A database row is itself a page: its `page.json` reports `parent_type` of
`data_source_id`, and its cell values ride in the same file under
`properties`. For full `page.json` field details and supported edits see
the [Python Notion docs](/python/resource/notion).
