Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Read this skill when you need to use Notion. Don't have to open a browser tab.
.claude/skills/kunanonj-aside-notion/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | -20% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 46% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 25% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 138% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 12% | 0% |
Use the notion global in the REPL tool. It extracts token_v2 from the logged-in Notion browser session — no tab navigation needed.
js// If multiple Notion accounts/workspaces may be logged in, inspect and select explicitly. console.log(await notion.listAccounts()); // Get an initialized NotionClient (cached per Chrome profile). // IMPORTANT: Use const so you can reuse the client across REPL calls. const _notion = await notion.getClient({ email: 'you@example.com', workspaceName: 'Corca', }); // Current user info console.log(_notion.currentUser.email, _notion.currentUser.fullName); console.log('Space:', _notion.currentSpace.get('name')); console.log('Plan:', _notion.currentSpace.get('subscription_tier')); // Search pages const results = await _notion.search({ query: 'meeting notes', isNavigableOnly: true, limit: 10 }); for (const block of results) { console.log(block.id, block.get('type'), block.title); } // Get a page by URL or ID const page = await _notion.getBlock('https://www.notion.so/myorg/My-Page-abc123'); console.log(page.title); // Read page as markdown (fast, local conversion, no API call) console.log(blockToMarkdown(page)); // Append markdown to a page await page.children.addFromMarkdown(` ## Agent update - [x] searched the workspace - [x] appended a section `); // Create a child page const child = await page.children.addNew('page', { title: 'New Sub-page' }); await child.children.addFromMarkdown('# Hello\n\nContent here.'); // Update page title await page.set('properties.title', [['Updated Title']]);
js// Always assign to a const for reuse across REPL calls const _notion = await notion.getClient();
The returned client is NotionClient from @aside/notion — a full-featured Notion internal API client. All operations below use this client.
If the token expires or you switch accounts:
jsnotion.invalidateCache(); const refreshedNotion = await notion.getClient();
The client initializes with the first user/workspace found. If the task depends on a specific account or workspace, list accounts first and pass explicit selectors to getClient.
jsconst accounts = await notion.listAccounts(); console.log(accounts); const client = await notion.getClient({ email: 'other@email.com', workspaceName: 'Corca', });
js// Basic search const results = await _notion.search({ query: 'project plan', limit: 20 }); // Pages only (skip inline blocks) const pages = await _notion.search({ query: 'project', isNavigableOnly: true, excludeTemplates: true, sort: { field: 'lastEdited' }, // 'relevance' | 'lastEdited' | 'created' }); // Search within a parent page const childIds = await _notion.searchPagesWithParent(parentPageId, 'query');
Search results are Block[] — already cached, ready to mutate.
jsconst page = await _notion.getBlock(pageIdOrUrl); // Page metadata console.log(page.title); console.log(page.get('type')); // Read children for (const child of page.children) { console.log(child.get('type'), child.title); } // Export as markdown (fast local conversion, no API call) console.log(blockToMarkdown(page));
Before creating pages or uploading files, verify the target workspace:
jsconsole.log(_notion.currentSpace.get('name'), _notion.currentSpace.get('subscription_tier')); console.log(_notion.currentSpace.get('settings.reach_block_limit_time'));
If the current workspace is free or block-limited and the user asked for a subscribed/team workspace, switch to the correct workspace before writing.
jsawait page.children.addNew('text', { title: 'A paragraph' }); await page.children.addNew('to_do', { title: 'Ship it', checked: false }); await page.children.addNew('bulleted_list', { title: 'List item' });
jsawait page.children.addFromMarkdown(` # Summary - write docs - [x] port search API > keep the API minimal \`\`\`ts console.log('ship it') \`\`\` `);
Supported: headings, paragraphs, bullet/numbered lists, to-dos, quotes, code blocks, dividers, nested lists. Inline: bold, italic, ~~strike~~, code, links, $$equations$$.
jsconst parent = await _notion.getBlock(parentPageId); const child = await parent.children.addNew('page', { title: 'Design Doc' }); await child.children.addFromMarkdown('# Goals\n\n- keep scope tight');
jsawait page.set('properties.title', [['New Title']]);
jsawait _notion.runInTransaction(async () => { await page.set('properties.title', [['Updated']]); await page.children.addNew('text', { title: 'Note 1' }); await page.children.addNew('text', { title: 'Note 2' }); });
js// Get a database view by URL const view = await _notion.getCollectionView('https://www.notion.so/myorg/8511b9fc?v=8dee2a54'); const collection = view.collection; // List rows const rows = await collection.getRows(); for (const row of rows.toArray()) { console.log(await row.getProp('Name'), await row.getProp('Status')); } // Add a row const newRow = await collection.addRow({ Name: 'New task', Status: 'In Progress', 'Due Date': { start: new Date('2026-05-01') }, }); // Query with filters const query = view.buildQuery({ filter: { filters: [{ property: 'Status', filter: { operator: 'enum_is', value: { type: 'exact', value: 'Done' } }, }], operator: 'and', }, sort: [{ property: 'Due Date', direction: 'ascending' }], }); const result = await query.execute();
js// Soft-delete await page.remove(); // Hard-delete await page.remove(true); // Move await myBlock.moveTo(targetBlock, 'after'); // 'before' | 'after' | 'first-child' | 'last-child'
When working with file/image uploads, never print signedPutUrl, signedGetUrl, upload plans, or temporary signed response files. Log only counts, booleans, block IDs, and final Notion page URLs.
jsawait page.set('format.block_locked', true); // lock await page.set('format.block_locked', false); // unlock
ts// Search returns Block[] — each has: block.id; // UUID block.get('type'); // 'page', 'text', 'to_do', etc. block.title; // markdown string (pages, text blocks) block.children; // child blocks // Database row properties via typed accessors: await row.getProp('Name'); // string await row.getProp('Status'); // string | null (select) await row.getProp('Tags'); // string[] (multi_select) await row.getProp('Done'); // boolean (checkbox) await row.getProp('Due Date'); // NotionDate | null await row.getProp('Owner'); // User[]
await on async methods — getClient(), getBlock(), search(), getProp(), set(), addNew(), remove(), and moveTo() are all async.markdownToNotion() for block trees — that's for inline rich text only. Use addFromMarkdown() for block content.page.title = '# Heading\nBody' is wrong. Set title separately, strip a matching leading # H1 from body markdown when needed, then append body via page.children.const _notion = ..., you re-initialize every REPL call.currentSpace.get('subscription_tier') and block-limit settings before writing.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 5,796 | 5,861 | +1% | 1 | 1 | 0% | 1,005 | 2,460 | +145% | 0 | 0 | — |
case-02 | fail→fail | 7,249 | 5,552 | -23% | 1 | 1 | 0% | 1,660 | 2,479 | +49% | 0 | 0 | — |
case-03 | fail→fail | 7,032 | 5,770 | -18% | 1 | 1 | 0% | 1,071 | 2,526 | +136% | 0 | 0 | — |
case-04 | pass→pass | 12,058 | 8,306 | -31% | 1 | 1 | 0% | 2,142 | 3,799 | +77% | 0 | 0 | — |
case-05 | pass→pass | 12,060 | 8,233 | -32% | 1 | 1 | 0% | 2,195 | 3,755 | +71% | 0 | 0 | — |
case-06 | pass→pass | 10,617 | 13,563 | +28% | 1 | 1 | 0% | 2,147 | 4,038 | +88% | 0 | 0 | — |
case-07 | fail→pass | 20,414 | 8,543 | -58% | 1 | 1 | 0% | 3,930 | 3,131 | -20% | 0 | 0 | — |
case-08 | fail→pass | 9,658 | 2,159 | -78% | 1 | 1 | 0% | 1,752 | 2,556 | +46% | 0 | 0 | — |
case-09 | fail→pass | 14,952 | 7,950 | -47% | 1 | 1 | 0% | 2,355 | 2,955 | +25% | 0 | 0 | — |
case-10 | fail→pass | 4,802 | 2,823 | -41% | 1 | 1 | 0% | 1,102 | 2,628 | +138% | 0 | 0 | — |
case-11 | pass→pass | 9,451 | 2,258 | -76% | 1 | 1 | 0% | 1,615 | 2,575 | +59% | 0 | 0 | — |
case-12 | fail→pass | 12,712 | 1,968 | -85% | 1 | 1 | 0% | 2,312 | 2,578 | +12% | 0 | 0 | — |
case-13 | pass→pass | 10,297 | 2,300 | -78% | 1 | 1 | 0% | 1,790 | 2,667 | +49% | 0 | 0 | — |
case-14 | fail→pass | 8,363 | 2,664 | -68% | 1 | 1 | 0% | 1,675 | 2,788 | +66% | 0 | 0 | — |
case-15 | fail→pass | 11,076 | 2,436 | -78% | 1 | 1 | 0% | 2,262 | 2,603 | +15% | 0 | 0 | — |
case-16 | pass→pass | 12,000 | 1,896 | -84% | 1 | 1 | 0% | 2,239 | 2,550 | +14% | 0 | 0 | — |
case-17 | pass→pass | 10,019 | 2,279 | -77% | 1 | 1 | 0% | 1,845 | 2,543 | +38% | 0 | 0 | — |
case-18 | fail→pass | 11,743 | 2,904 | -75% | 1 | 1 | 0% | 2,310 | 2,778 | +20% | 0 | 0 | — |
case-19 | fail→pass | 8,614 | 2,641 | -69% | 1 | 1 | 0% | 1,534 | 2,689 | +75% | 0 | 0 | — |
case-20 | fail→pass | 10,814 | 1,816 | -83% | 1 | 1 | 0% | 1,922 | 2,528 | +32% | 0 | 0 | — |
case-21 | fail→pass | 10,868 | 2,349 | -78% | 1 | 1 | 0% | 2,303 | 2,614 | +14% | 0 | 0 | — |
case-22 | pass→pass | 11,177 | 2,819 | -75% | 1 | 1 | 0% | 1,771 | 2,716 | +53% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 19 counted toward the lift figure. The other 3 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +50 percentage points is the difference between those two pass rates over the 19 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.