iamzifei/linkedin-article-publisher-skill/linkedin-article-publisher
- community
Checking that the SKILL.md file is still in the repository.
Publish Markdown articles to LinkedIn Articles editor with proper formatting. Use when user wants to publish a Markdown file/URL to LinkedIn Articles, or mentions "publish to LinkedIn", "post article to LinkedIn", "LinkedIn article", or wants help with LinkedIn article publishing. Handles cover image upload and converts Markdown to rich text automatically.
Install
npx vskill@latest install iamzifei/linkedin-article-publisher-skill/linkedin-article-publisherSKILL.md
LinkedIn Article Publisher
Publish Markdown content to LinkedIn Articles editor, preserving formatting with rich text conversion.
Prerequisites
- Playwright MCP for browser automation
- User logged into LinkedIn in browser
- Python 3.9+ with dependencies:
pip install Pillow pyobjc-framework-Cocoa
Scripts
Located in ~/.claude/skills/linkedin-article-publisher/scripts/:
parse_markdown.py
Parse Markdown and extract structured data:
Returns JSON with: title, cover_image, content_images (with block_index for positioning), html, total_blocks
copy_to_clipboard.py
Copy image or HTML to system clipboard:
Workflow
Strategy: "Text First, Images Later"
For articles with multiple images, paste ALL text content first, then insert images at correct positions using block index.
- Parse Markdown with Python script -> get title, images with block_index, HTML
- Navigate to LinkedIn Articles editor
- Upload cover image (first image)
- Fill title
- Copy HTML to clipboard (Python) -> Paste with Cmd+V
- Insert content images at positions specified by block_index
- Save as draft (NEVER auto-publish)
Efficiency Guidelines
Goal: Minimize wait time between operations for smooth automation.
1. Avoid unnecessary browser_snapshot
Most browser operations (click, type, press_key, etc.) return page state in response. Don't call browser_snapshot after every operation - use the returned state directly.
2. Avoid unnecessary browser_wait_for
Only use browser_wait_for when:
- Waiting for image upload to complete
- Waiting for initial page load (rare cases)
Don't use browser_wait_for for buttons or inputs - they're available immediately after page load.
3. Parallel execution of independent operations
When two operations have no dependencies, call multiple tools in the same message:
4. Chain browser operations continuously
Each browser operation returns page state with element references. Use these references directly for the next operation:
5. Prepare work upfront
Before starting browser operations, complete all preparation:
- Parse Markdown to get JSON data
- Generate HTML file to /tmp/
- Record title, cover_image, content_images info
This allows browser operations to execute continuously without stopping for data processing.
Step 1: Parse Markdown (Python)
Use parse_markdown.py to extract all structured data:
Output JSON:
Key fields:
block_index: The image should be inserted AFTER block element at this index (0-indexed)total_blocks: Total number of block elements in the HTMLafter_text: Kept for reference/debugging only, NOT for positioning
Save HTML to temp file for clipboard:
Step 2: Open LinkedIn Articles Editor
Important: The page loads directly into the article editor. Wait for the editor to fully load:
- Wait for page load: Use
browser_snapshotto check page state - Look for editor elements: Title input field, content editor area, cover image button
- If redirect occurs: LinkedIn may redirect - follow the redirect URL
Note: If user is not logged in, prompt them to log in manually and retry.
Step 3: Upload Cover Image
LinkedIn's cover image upload:
- Find and click the cover image area/button (usually at top of editor, look for "Add a cover image" or camera icon)
- Use browser_file_upload with the cover image path (from JSON output)
- Wait for image upload to complete
Look for elements like:
- "Add a cover image" button
- Cover image placeholder area
- Camera/image icon at top of editor
Step 4: Fill Title
- Find the title input field (usually large text area at top with placeholder "Title")
- Use browser_type to input title (from JSON output)
Step 5: Paste Text Content (Python Clipboard)
Copy HTML to system clipboard using Python, then paste:
Then in browser:
This preserves all rich text formatting (H2, bold, links, lists).
Step 6: Insert Content Images (Block Index Positioning)
Key improvement: Use block_index for precise positioning instead of text matching.
Positioning principle
After pasting HTML, editor content consists of block elements (paragraphs, headers, quotes, etc.). Each image's block_index indicates it should be inserted after the Nth block element.
Operation steps
- Get all block elements: Use browser_snapshot to get editor content, find all child elements under the textbox
- Position by index: Click the block element at
block_indexposition - Paste image: Copy image to clipboard then paste
For each content image (from content_images array):
Positioning strategy
In browser_snapshot, editor content typically appears as:
To insert image after block_index=5:
- Find the 6th child element under editor textbox (0-indexed)
- Click that element
- Paste image
Note: Each inserted image shifts subsequent indices. Insert images in reverse order by block_index (highest to lowest) so earlier indices remain valid.
Reverse insertion example
If 3 images have block_index 5, 12, 27:
- First insert block_index=27 image
- Then insert block_index=12 image
- Finally insert block_index=5 image
This way each insertion doesn't affect previously positioned locations.
Step 7: Save Draft
- Verify content pasted (check if content appears correctly)
- LinkedIn auto-saves drafts periodically (look for "Saved" indicator)
- Optionally click "Publish" dropdown and select "Save as draft"
- Report: "Draft saved. Review and publish manually."
Critical Rules
- NEVER publish - Only save draft
- First image = cover - Upload first image as cover image
- Rich text conversion - Always convert Markdown to HTML before pasting
- Use clipboard API - Paste via clipboard for proper formatting
- Block index positioning - Use block_index for precise image placement
- Reverse order insertion - Insert images from highest to lowest block_index
- H1 title handling - H1 is used as title only, not included in body
Supported Formatting
- H2 headers (## )
- H3 headers (### )
- Blockquotes (> )
- Code blocks (``
...``) - converted to blockquotes for compatibility - Bold text (**)
- Italic text (*)
- Hyperlinks (text)
- Ordered lists (1. 2. 3.)
- Unordered lists (- )
- Paragraphs
Example Flow
User: "Publish /path/to/article.md to LinkedIn"
- Navigate to https://www.linkedin.com/article/new/
- Upload cover image (browser_file_upload for cover only)
- Fill title (from JSON:
title) - Copy & paste HTML:
``bash python ~/.claude/skills/linkedin-article-publisher/scripts/copy_to_clipboard.py html --file /tmp/article_html.html `` Then: browser_press_key Meta+v
- For each content image, in reverse order of block_index:
``bash python copy_to_clipboard.py image /path/to/img.jpg --quality 85 ``
- Click block element at
block_indexposition - browser_press_key Meta+v
- Wait for upload to complete
- Verify content in editor
- "Draft saved. Please review and publish manually."
Best Practices
Why use block_index instead of text matching?
- Precise positioning: Doesn't rely on text content, works even with similar paragraphs
- Reliability: Index is deterministic, won't confuse similar text
- Easy debugging:
after_textkept for human verification
Why use Python instead of browser JavaScript?
- More reliable: Python operates system clipboard directly, not limited by browser sandbox
- Image compression: Compress before upload (--quality 85), reduces upload time
- Code reuse: Scripts are fixed, no need to rewrite conversion logic each time
- Easy debugging: Scripts can be tested independently
Wait strategy
Key understanding: browser_wait_for's time parameter is maximum wait time, not fixed delay. Operations return immediately when conditions are met.
- Bad:
time=10- if upload takes 2 seconds, remaining 8 seconds wasted - Good:
time=3- returns immediately when done, max wait 3 seconds
Principle: Don't assume "how long it takes" - set reasonable maximum, let condition checking return quickly.
Image insertion efficiency
Each image's browser operations reduced from 5 steps to 2:
- Old: click -> add media -> media -> add photo -> file_upload
- New: click paragraph -> Meta+v
Cover image vs content images
- Cover image: Use browser_file_upload (dedicated upload button)
- Content images: Use Python clipboard + paste (more efficient)
LinkedIn-Specific Notes
Editor URL
- Primary:
https://www.linkedin.com/article/new/ - Alternative:
https://www.linkedin.com/pulse/(may redirect here)
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/linkedin-article-publisher/SKILL.md
Popularity
- Repository stars
- 13
- Repository forks
- 4
- 7-day trend score
- 13
Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 17 Mar 2026, 7 months ago.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/iamzifei/linkedin-article-publisher-skill/linkedin-article-publisher)