🌟🌟🌟 Welcome m2w 2.7.2! LaTeX math rendering, tables, and GFM Admonition support now available. Current v2.7.2 also features rate limiting, resumable uploads, and batch processing for safe mass uploads of 1000+ articles!
Chinese tutorial: Docker系列 WordPress系列 WordPress上传或更新Markdown的最佳实践-m2w 2.0
m2w is a tool for automatically uploading or updating local Markdown to WordPress via Python, based on REST API (2.5+) or Password.
m2w has these features:
- Support REST API, which is safer then conventional password!
- Use
config/user.jsonto maintain the user information in a little different way comparing withm2w 1.0. - You can just keep your file structures locally as you like.
- You can manage lots of websites at the same time via multiple
legacy_*.json. - All you need to deal with is a single python script
myblog.pyinstead of two (update.pyandupload.pyinm2w 1.0). - Ignore repeated new markdown files for uploading (
v2.2.4+) - LaTeX math rendering (
v2.7.2+): Support for inline math ($...$) and display math ($$...$$,\begin...\end{}) formulas - Markdown tables (
v2.7.2+): Native table support for better content formatting - GFM Admonition (
v2.7.2+, optional): GitHub-style callout boxes (> [!NOTE],> [!TIP], etc.) - requirespip install m2w[admonition] - Rate limiting & batch processing (
v2.7+): Prevent server bans with configurable delays, batch processing, and exponential backoff for HTTP 429 errors - Resumable uploads (
v2.7+): Progress tracking saves your work—interrupt and resume without losing progress
Miniconda is recommended to manage Python version and related dependencies.
- Python >= 3.7.6
- Runtime dependencies:
python-frontmatter>=1.0.0,markdown>=3.3.6,python-wordpress-xmlrpc>=2.3,httpx>=0.24.0(seerequirements.txt) - Packaging now follows PEP 621 in
pyproject.toml(setuptools);setup.pyremains only for compatibility.
After 2022-12-10, m2w was uploaded onto PyPi. Install it via:
pip install m2w
# or pin a version
pip install -i https://pypi.org/simple m2w==2.7.2
# For GFM Admonition support (optional)
pip install m2w[admonition]From source, you can use the modern build flow:
python -m pip install --upgrade build
python -m build # generate wheel + sdist under dist/
python -m pip install dist/m2w-*.whl # install the built artifact
# editable install for development
python -m pip install -e .This step is needed only when you want to use the REST API mode.
- If any, please allow Application password of WordPress in Wordfence:
- Go to personal settings and add a new REST API:
- Please record the new REST API in a safe place. If you forget it or suspect its safety, please remove the old API and create a new one:
- Install m2w from PyPi or this Github repotory.
- Build a
myblog.pyfile (or other names you like) in<path01>. Here is the demo. Create<path02>/config/user.jsonand setpath_m2was<path02>inmyblog.py:
path_m2w = '<path02>' # Absolute path of m2w config folder- Define
<path02>/config/user.json. You can add many websites likeweb01! Please go to the demo for more details. Here are some interpretations:
- user.json for REST API mode:
"web01": {
"domain": "https://domain-01.com",
"username": "username-01",
"application_password": "password-01",
"path_markdown": [
"E:/Github/m2w/@test/main",
"E:/Github/m2w/@test/main2"
],
"post_metadata": {
"category": ["test"],
"tag": ["test"],
"status": "publish"
},
"path_legacy_json": "/config/legacy"
}- user.json for Password mode:
"web01": {
"domain": "https://domain-01.com",
"username": "username-01",
"password": "password-01",
"path_markdown": [
"E:/Github/m2w/@test/main",
"E:/Github/m2w/@test/main2"
],
"post_metadata": {
"category": ["test"],
"tag": ["test"],
"status": "publish"
},
"path_legacy_json": "/config/legacy"
}- domain, username, application_password/password: The information of your WordPress site and account.
application_passwordis REST API, andpasswordis the conventional password of your account. If bothapplication_passwordandpasswordexist, onlyapplication_passwordis available for m2w. - path_markdown: Add as much top folders as you want!
- post_metadata/path_legacy_json: Set default if you don't know what they are.
- Run
myblog.pylike:
python <path01>/myblog.pyNeed to ignore local helper files? In
myblog.pysetignore_files = ["AGENTS.md", "CLAUDE.md"](globs andre:regex supported). No user.json edits required.
- Default ignore list in
myblog.py:["AGENTS.md", "CLAUDE.md"](avoid uploading AI helper docs). - You can add globs (e.g.,
"**/draft-*.md","notes/**") or regex prefixed withre:(e.g.,"re:.*/temp-.*\\.md$"). - Leave
ignore_filesempty/remove it to scan all markdown files (backward compatible for older scripts).
When uploading a large number of articles (e.g., 1000+), you may want to enable rate limiting to avoid server bans:
# In myblog.py
rate_limit_enabled = True # Enable rate limiting
request_delay = 1.0 # Delay between requests (seconds)
batch_size = 10 # Files per batch
batch_delay = 5.0 # Delay between batches (seconds)
max_429_retries = 5 # Max retries on HTTP 429
initial_backoff = 2.0 # Initial backoff time (seconds)
progress_enabled = True # Enable progress saving
progress_file = None # None = same dir as legacy.jsonRecommended configurations:
- Conservative (strict servers):
request_delay=2.0,batch_size=5,batch_delay=10.0 - Balanced (recommended):
request_delay=1.0,batch_size=10,batch_delay=5.0 - Aggressive (lenient servers):
request_delay=0.5,batch_size=20,batch_delay=3.0
You can use frontmatter in your Markdown files to control article behavior:
---
title: Your Article Title
slug: custom-url-alias
status: publish
post_type: post
category: [Technology]
tag: [python, wordpress]
---Available fields:
| Field | Description | Example |
|---|---|---|
title |
Article title (used for matching existing articles) | title: My First Post |
slug |
Custom URL alias | slug: my-custom-url |
status |
Article status: publish, draft, delete |
status: publish |
post_type |
Content type: post, page, or custom types like shuoshuo |
post_type: shuoshuo |
category |
Article categories | category: [Tech, Python] |
tag |
Article tags | tag: [code, tutorial] |
Examples:
Create a shuoshuo (status update):
---
title: Today's weather is nice!
post_type: shuoshuo
slug: shuoshuo-2025-01-24
status: publish
---Set custom URL alias:
---
title: My Article
slug: my-custom-url
status: publish
---Delete an article:
---
title: Old Article
status: delete
---Note: When
status: deleteis set, the article will be deleted from WordPress and the local Markdown file will be removed. Use with caution!`
This demo is conducted in Win10 with VScode.
As shown in the following GIF, all changed or brand-new markdowns can be automatically updated/upload via just a simple command python myblog.py!
See CHANGELOG.MD for the complete version history.
Current release: v2.7.2 (2026-01-24)
- LaTeX math rendering support (
$...$ and$$...$$ / \begin...\end{}) - Markdown tables support
- GFM Admonition support (optional, requires
pip install m2w[admonition]) - Thanks to @Mareep-YANG for contributing via PR #20!
- @huangwb8 - Project founder
- @FoxSuzuran - Core maintainer
- @Mareep-YANG - LaTeX math, tables, and GFM Admonition support (PR #20)
- @Shulelk - Custom post types, URL aliases, and status control features (PR #21)
Feel free to dive in! Open an issue or submit PRs. m2w follows the Contributor Covenant Code of Conduct.
This software depends on other packages that may be licensed under different open source licenses.
m2w is released under the MIT license. See LICENSE for details.
Applications similar to m2w
- nefu-ljw/python-markdown-to-wordpress: The root project of m2w!
- WordPressXMLRPCTools: Manage WordPress posts in Hexo way.
- markpress: Write WordPress in Markdown in Your Favorite Text Editor
- wordpress-markdown-blog-loader: This utility loads markdown blogs into WordPress as a post. It allows you to work on your blog in your favorite editor and keeps all your blogs in git.



