Skip to main content
The Notion VFS exposes a Notion workspace as a virtual filesystem mounted at a prefix such as /notion/. For API key setup, see Notion Setup.

Config

Fields marked secret are redacted in snapshots.

VFS.md

The VFS.md an agent reads with Notion mounted at /<path>.

Virtual Filesystem

VFS Layout

/<path>
  • page.json holds the page’s metadata, its body rendered as Markdown and its raw blocks (nested blocks under children).
  • A database is a container plus one or more data sources. database.json holds the container’s identity and its data_sources stubs, data_source.json the typed property schema, and rows.jsonl the rows, one JSON line each: the row page’s page.json without the body, plus path, that page.json’s path below the data source directory. Row directories are not listed; open one by its path.
  • Titles are sanitized. Don’t construct paths: ls the parent directory, or take a row’s path from rows.jsonl.
  • ntn takes ids as positional operands, not flags: ntn pages get <page-id>, ntn datasources query <data-source-id>, ntn datasources resolve <database-id>.

Supported Unix Commands for VFS

Unix commands work on each path as on a local filesystem, as far as the path supports them:

Limitations of Unix Commands for VFS

Where a path differs from a local filesystem:Run man <cmd> or <cmd> --help for one command’s usage.
The pages/ hierarchy mirrors Notion’s standalone page tree. Each page directory contains a page.json with the page metadata and content, and child pages appear as nested directories. The databases/ hierarchy is one level deeper than the page tree, because the 2025-09-03 API generation split a database into a container plus one or more data sources. The column schema and the rows both live on the data source: data_source.json and rows.jsonl. The name stutters for a single-source database because Notion names the auto-created data source after its database; that disappears the moment a database holds two. A data source lists its rows as one file, not as a directory each. A query answers a hundred rows’ cells in one call, while every row directory costs calls of its own to enter, so a grep -r or find over a large database would cost calls per row. A row is still a page with a page.json, at <data-source-dir>/<row-title>__<page-id>/page.json: it is not listed, and its path is on the row’s line in rows.jsonl.

database.json

The container’s identity. It carries database_id, title, url, timestamps, parent, archived, is_inline, and the data_sources stubs that name the directories beneath it. It does not carry properties: at this API version the column schema lives on the data source, and GET /v1/databases/{id} no longer returns one.

data_source.json

The typed column schema (Notion’s own property objects), with no rows inline:
A data source id is not its database id. Turn one into the other with ntn datasources resolve <database-id>.

rows.jsonl

Every row of the data source, one JSON object per line, in the order the query returns them. A line is the row’s page.json without the body, which a query does not carry, plus path, where that page.json is below the data source directory:
Reading it costs one query call per hundred rows, however many rows match.

page.json

A page, including a database row. Notion blocks render to markdown and stay available raw under blocks; a row’s cell values are under properties, as Notion’s own property objects, answering to the schema in the data_source.json one level up:
So a table’s cells are readable with the ordinary tools, and a row’s body by the path its line carries:
Blocks with children embed them recursively under a children key, and the Markdown renders nested blocks with indentation. A standalone page’s properties holds only its title, which is what the API returns for one.

Cache

Uses IndexCacheStore for page metadata. File bytes go through the workspace file cache.

Integration

What Notion answers through its own API, so fewer files are read. Integration explains each command.

Example

Shell Commands

Standard commands available on the mounted Notion tree: Acting on Notion (creating, editing and trashing pages, querying data sources, and every route that has no typed verb) goes through the ntn CLI when installed. Ids are positional: use the <page-id> / <database-id> / <data-source-id> from a path segment as the operand.