Install
openclaw skills install notion-publisherPublish articles to Notion using cached local copies of the target database's default Notion template when available. Use this skill when the user types /notion-publisher, asks to publish an article to Notion, create a Notion article page, draft a post into a Notion database, use an existing Notion article template, refresh the local template cache, or infer article metadata such as slug, summary, tags, category, status, cover, and template before creating a Notion page.
openclaw skills install notion-publisherUse this skill when the user types /notion-publisher or asks to publish, draft, or create an article page in a Notion database.
~/.notion_publish/notion-publisher-config.json
Only store reusable publishing preferences:
{
"default_status": "Draft",
"template_strategy": "ask_each_time"
}
Do not store database IDs, data source IDs, tags, category choices, cover choices, article content, Notion tokens, or private workspace URLs in the config file. Ask for the target database every time.
Before choosing a runtime, detect whether the current client is OpenClaw by running:
which openclaw 2>/dev/null && echo "PLATFORM=openclaw" || echo "PLATFORM=other"
If PLATFORM=openclaw, assume Notion MCP is not available and prefer the bundled CLI runtime. If PLATFORM=other, use MCP when available and fall back to the CLI runtime when MCP is missing.
This skill supports MCP clients and command-only clients such as OpenClaw.
Preferred order:
scripts/notion_publisher.pyThe CLI runtime uses the official Notion API and requires NOTION_TOKEN in the environment or in:
~/.notion_publish/.env
Example:
NOTION_TOKEN=secret_xxx
The target Notion database must be shared with that Notion integration.
To get a Notion token:
~/.notion_publish/.env as NOTION_TOKEN=secret_xxx. Never commit it to a repository.Use whichever Notion MCP tools are available in the current client.
Codex tool names:
mcp__notion__notion_searchmcp__notion__notion_fetchmcp__notion__notion_create_pagesmcp__notion__notion_update_pageClaude Code / Claude plugin tool names:
mcp__plugin_Notion_notion__notion-search or notion-searchnotion-fetchnotion-create-pagesnotion-update-pageIf no Notion MCP tools are available, do not stop automatically. Use the CLI runtime if shell commands are available.
CLI examples:
python3 scripts/notion_publisher.py publish \
--database-id "NOTION_DATABASE_ID_OR_URL" \
--title "Article title" \
--body-file article.md \
--status Draft \
--type Post
The CLI also accepts a Notion data source directly:
python3 scripts/notion_publisher.py publish \
--data-source-id "collection://DATA_SOURCE_ID" \
--title "Article title" \
--body-file article.md
If both --database-id and --data-source-id are omitted, the CLI asks the user for a Notion database/data source ID or URL. By default, it does not save database IDs. Use --save-database only if the user explicitly wants the database remembered as a local prompt default.
CLI update examples:
python3 scripts/notion_publisher.py update \
--page-id "NOTION_PAGE_ID_OR_URL" \
--mode replace \
--body-file article.md \
--summary "Updated summary"
python3 scripts/notion_publisher.py update \
--page-id "NOTION_PAGE_ID_OR_URL" \
--mode append \
--body-file appendix.md
CLI search example:
python3 scripts/notion_publisher.py search \
--data-source-id "collection://DATA_SOURCE_ID" \
--query "keyword" \
--limit 10
CLI search returns JSON rows with id, title, status, type, category, slug, date, and url.
Important search behavior:
update.Read:
cat ~/.notion_publish/notion-publisher-config.json 2>/dev/null
If the file does not exist, create it immediately:
mkdir -p ~/.notion_publish
cat > ~/.notion_publish/notion-publisher-config.json <<'EOF'
{
"default_status": "Draft",
"template_strategy": "ask_each_time"
}
EOF
Then continue. During the article confirmation step, show default_status and template_strategy to the user and let them override values for that article.
Do not save cover preferences. Cover is selected every time.
Always ask the user to choose a target database. Do not reuse a cached database.
Offer these input modes:
Please choose a target Notion database:
1. Paste a Notion database URL
2. Enter a database ID
3. Enter keywords to search
4. Press Enter to search for likely article databases
After the user chooses, fetch the database schema with the current environment's Notion fetch tool. Read the data source ID, writable properties, select/multi-select options, and available Notion database templates.
In CLI mode, pass the database ID or URL to scripts/notion_publisher.py. If the user does not provide one, let the CLI prompt for it. Do not save the database unless the user explicitly chooses --save-database.
Before fetching a Notion template page, read:
templates/notion-defaults/catalog.md
If a local cached template matches the target database or template ID, use the local Markdown file as the article structure source.
Fetch the Notion template page only when:
When refreshing, update the cache file under templates/notion-defaults/ and update cached_at. Do not include private database IDs or workspace names if the skill will be shared publicly.
When using a cached or bundled template, preserve its visual structure:
一、... and 二、...; avoid generic subheadings like 观点一 or 观点二.Ask:
Title:
Body: paste content, type "generate", or leave blank to draft later.
Infer and show a confirmation block:
slug:
summary:
category:
tags:
status:
cover:
inline images:
template strategy:
Rules:
default_status from the shared config as the suggested status, but show it for confirmation each time.slug as English kebab-case.summary to one or two concise sentences.Cover is not saved in config. Always ask:
Choose cover:
1. Search a recommended cover by article topic
2. Search with custom keywords
3. Custom image URL
4. No cover
Cover search rules:
.jpg, .jpeg, .png, or .webp URLs. Avoid URLs with complex crop/query parameters when a clean direct image URL is available.Do not use plain-color or mostly solid-color covers by default. Avoid gradient covers unless the user explicitly asks for a simple gradient.
Preferred cover source strategy:
.jpg, .jpeg, .png, or .webp URLs.image_id, then build:
https://www.artic.edu/iiif/2/{image_id}/full/843,/0/default.jpghttps://api.artic.edu/api/v1/artworks/search?q={keywords}&query[term][is_public_domain]=true&fields=id,title,artist_display,image_id,is_public_domainprimaryImage or primaryImageSmall JPEG URL when isPublicDomain is true.https://iiif.micr.io/{id}/full/max/0/default.png when an image id is available.cover.Ask whether to insert images inside the article body:
Insert inline images?
1. No inline images
2. Search by article title and summary
3. Search by custom keywords
4. Use custom image URL
If the user chooses search:
When generating a complete article body, include at least one relevant inline image unless the user explicitly chooses "No inline images".
<empty-block/> before and after the image block so Notion App has the best chance of rendering them as native image blocks, not plain Markdown text.<empty-block/> blocks.Insert confirmed images with Notion-flavored Markdown:
<empty-block/>

<empty-block/>
Place images near the relevant section. Do not insert images that are unrelated, low quality, broken, or likely to violate usage rights.
Use template_strategy from config as the default suggestion:
ask_each_time: ask every time.use_cached_notion_template: default to creating with the Notion template ID.generate_from_cached_template: default to generating complete content from the cached local template.Still let the user override.
Show:
Choose template strategy:
1. Use the Notion database template to create a page
2. Generate complete content from the cached local template
3. Use a bundled fallback template
4. No template
Important:
template_id, do not pass content.template_id; read the local template and pass content.If no database template cache exists, read templates/catalog.md and offer:
If using MCP, call the current environment's Notion create pages tool with:
{
"parent": { "type": "data_source_id", "data_source_id": "..." },
"pages": [{
"properties": {},
"cover": "optional cover URL",
"template_id": "only when using a Notion database template",
"content": "only when writing generated Markdown content"
}]
}
Use expanded date property fields such as date:date:start and date:date:is_datetime when required by the schema.
If using CLI mode, create or save the generated article as Markdown and run:
python3 scripts/notion_publisher.py publish \
--database-id "NOTION_DATABASE_ID_OR_URL" \
--title "Article title" \
--body-file article.md \
--status Draft \
--type Post \
--category "LLM" \
--tags "LLM,工具" \
--slug "article-slug" \
--summary "Short summary" \
--date "2025-06-12" \
--cover "https://example.com/cover.jpg"
CLI mode writes inline images as native Notion image blocks through the official API. This is preferred when the user needs images to render inside Notion App, not only in a frontend that parses Markdown.
If a page was created from a Notion database template and the user later wants to replace or insert article content, call the current environment's Notion update page tool with replace_content or targeted content updates.
In CLI mode, use scripts/notion_publisher.py update.
Supported update modes:
--mode replace: delete/archive existing child blocks, then append the new article blocks. This matches MCP replace_content.--mode append: keep existing child blocks and append the new blocks at the end.The CLI update command can also update page properties and cover:
python3 scripts/notion_publisher.py update \
--page-id "NOTION_PAGE_ID_OR_URL" \
--mode replace \
--body-file article.md \
--status Published \
--type Post \
--category "LLM" \
--tags "LLM,工具" \
--slug "article-slug" \
--summary "Short summary" \
--date "2025-06-12" \
--cover "https://example.com/cover.jpg"
CLI update mode writes inline images as native Notion image blocks and should be preferred in OpenClaw when the user needs images to render inside Notion App.
If using MCP, search through the target data source with the current environment's Notion search tool, then fetch candidate pages and inspect properties before updating.
If using CLI mode, run:
python3 scripts/notion_publisher.py search \
--data-source-id "collection://DATA_SOURCE_ID" \
--query "keyword" \
--status Draft \
--limit 25
Use the returned id as --page-id for scripts/notion_publisher.py update.
When the user says /notion-publisher reset, delete:
~/.notion_publish/notion-publisher-config.json
Then re-run the initial config flow. Do not delete template caches unless the user explicitly asks.
Published unless the user confirms.