Notion Recipes
Use these recipes with easy-notion's MCP tools, or through the claude.ai connector when the equivalent tools are enabled. Recipe 2 (repair and find-replace) also works through the easy-notion CLI skill in skills/easy-notion-cli/. Recipe 1 does not: it needs create_database, source-block lookup with search_in_page, and structured database filters, and the current CLI surface does not expose that full workflow.
Recipe 1: Meeting Notes To Action Items
Turn a meeting-notes page or pasted notes into deduplicated rows in an Action Items database.
Safety boundary: Recipe 1 is re-run-safe and idempotent because Item Key stores the source line's stable Notion identity (<pageId>:<blockId>), not the action wording.
- Create the Action Items database once with
create_databaseunder the user-selected parent page:
{
"parent_page_id": "<parent page ID>",
"title": "Action Items",
"schema": [
{ "name": "Name", "type": "title" },
{ "name": "Item Key", "type": "rich_text" },
{ "name": "Owner", "type": "rich_text" },
{ "name": "Due", "type": "date" },
{ "name": "Status", "type": "status" },
{ "name": "Flags", "type": "multi_select" },
{ "name": "Source", "type": "rich_text" }
]
}
- Read or receive the meeting notes. Extract only discrete action items.
- For each item, derive these properties:
Name: the action text.Owner: the named assignee, or blank.Due: the stated date as ISOYYYY-MM-DD, or blank.Item Key: the source line's stable identity, formatted as<sourcePageId>:<sourceBlockId>.Source: the meeting title plus date. Do not stash flags here.Status:Not started.Flags: addneeds-ownerif no owner, andneeds-dueif no due date. These aremulti_selectvalues, not text inSource.
- Resolve
sourceBlockIdwithsearch_in_page.read_pagereturns markdown without block IDs. For a Notion-page source, callread_pageto extract items, then for each item callsearch_in_pagewith a verbatim, distinctive substring of that item's original source line. Use thematches[].block_idwhose text is that source line. If several blocks match, use a longer verbatim substring to isolate one block. For pasted notes, first save them as a Notion page withcreate_page, then proceed throughsearch_in_page; do not rely on block IDs fromcreate_page, which returns only{id,title,url}. If one source line contains multiple distinct actions, append a stable ordinal suffix in source order, such as:1or:2, to keep keys unique. - Dedupe before insert. For each item, call
query_databasewith this exact filter shape:
{
"filter": {
"property": "Item Key",
"rich_text": {
"equals": "<that item's key>"
}
}
}
If results is empty, insert the item. Otherwise skip it. Do not dedupe with free-text query_database text=...; text search also scans Source and can produce false matches for every row from the same meeting.
- Insert new rows with
add_database_entry, or batch them withadd_database_entries, using simple key-value properties such as:
{
"Name": "Draft the v1.1 release notes",
"Item Key": "38bbe876-242f-81f1-97b7-df935d050a24:38bbe876-242f-81c9-86c6-d9a792fc70b7",
"Owner": "James",
"Due": "2026-06-26",
"Status": "Not started",
"Flags": [],
"Source": "Q3 Planning Sync 2026-06-25"
}
- Verify by querying the database. The live smoke test produced 5 rows, with missing owners and dates represented in
Flags; running twice over the same notes left the count unchanged. An Item Keyrich_text equalsquery on a<pageId>:<blockId>key returned exactly 1 matching row.
Recipe 2: Bulk Edit, Find-Replace, And Repair
Use these procedures when Notion's built-in find-and-replace or bulk property edits hit native row caps. Iterate through the API results to continue past the native limit.
2A. Repair Inconsistent Database Property Values
- Call
get_databaseto see property names. For select or status properties created on the fly,get_databasemay returnoptions: []; read live values fromquery_databaseinstead. - Call
query_databaseto fetch rows. For large databases, page through all results in a loop. - Build a normalization map, for example:
{
"Eng": "Engineering",
"engineering": "Engineering"
}
- For each row whose value needs fixing, call
update_database_entrywith the row page ID and a simple key-value map:
{
"Team": "Engineering"
}
- Re-query to verify. The live smoke test normalized 4 rows with mixed
Engandengineeringvalues to one consistent option, while unrelated rows stayed unchanged.
Caveat: select and status option matching is case-insensitive, and writes snap to the earliest-existing option's casing. If a lowercase variant already exists, writing a capitalized version reuses the existing lowercase option. To force a specific casing, rename the option in Notion's UI rather than writing the new casing.
2B. Find-Replace Text Across A Page Body
- Call
find_replacewithdry_run: trueandreplace_all: trueto previewmatch_countwithout mutating. - Call
find_replacewithreplace_all: trueto apply the replacement. - Call
read_pageto verify. The live smoke test replaced 4 occurrences across paragraphs and a heading body.