Cross-Post a DEV.to Tutorial to Medium with a Formatting Check
Cross-posting a technical tutorial is easy to start and surprisingly easy to get wrong. A URL import can leave code blocks split, headings as plain text, or metadata incomplete. The result may look acceptable at a glance while damaging the parts readers need most.
This tutorial shows a reviewable DEV.to to Medium workflow using publish-agents, an open-source TypeScript project by Fernando Paladini. Its medium-publisher package imports a public article through Medium's import flow, checks the editor against the source Markdown, and can repair a small set of common formatting problems.
TL;DR
Check out the stable v0.2.3 release, build the @paladini/medium-publisher-mcp package, log in once, and create a Medium draft with publish-devto. Keep the default draft behavior while you inspect the title, code blocks, headings, lists, and metadata.
Prerequisites
You need:
- Node.js 20 or newer.
- A published DEV.to article with a public URL.
- A Medium account that can create stories.
- A terminal that can run
npmand the browser installation step.
The package uses Patchright browser automation and a saved browser session. It does not use a Medium write API key. The project documents Medium UI changes as a compatibility risk, so treat the browser session and the resulting draft as reviewable state rather than an unattended guarantee.
Install the released source
The repository's v0.2.3 release is the stable reference for this walkthrough. Installing from that tag keeps the commands separate from later changes on the default branch.
git clone https://github.com/paladini/publish-agents.git
cd publish-agents
git checkout v0.2.3
npm install
npm run build -w @paladini/medium-publisher-mcp
npm link -w @paladini/medium-publisher-mcp
The build produces the CLI and MCP server from the package source. The package declares Node.js 20 or newer and uses patchright as its browser automation dependency. Its post-install step may install the bundled Chromium browser. If that step was skipped in your environment, run the browser installation command documented by Patchright before continuing.
Create a saved Medium session
Log in interactively once:
medium-publisher login
Then check the session in JSON form:
medium-publisher session-check --json
The project stores browser state in an operating-system data directory by default. On Windows, the documented default is %LOCALAPPDATA%\medium-publisher\storageState.json. You can override it with MEDIUM_STATE_PATH or MEDIUM_PUBLISHER_HOME.
This is an important boundary: the saved state contains authentication cookies. Protect the directory like any other local credential store, do not commit it, and do not copy it into a CI artifact.
Import a DEV.to article as a draft
Use the one-shot command with --draft and --json:
medium-publisher publish-devto --url "https://dev.to/your-name/your-tutorial" --draft --json
The URL must point to a public article. The package fetches the DEV.to article through its public API, then opens Medium's official import route. The --draft flag matters because it leaves the final publication decision with you.
The JSON result includes a Medium URL and metadata details such as whether the title was set, which topics were selected, and whether a hero image was detected. Treat that URL as a draft inspection target until you have checked the actual editor.
Verify the imported editor
Open the returned draft URL in the browser, or use the package's structured extraction command:
medium-publisher extract --url "https://medium.com/p/your-draft/edit" --json
The extraction workflow is useful because it reports the editor outline and formatting flags. Check at least these points manually:
- The title matches the DEV.to source.
- Headings are rendered as headings, not literal Markdown text.
- Each fenced code example is one code block with the expected language.
- Lists, links, and paragraphs remain in the expected order.
- The subtitle and topics describe the article without inventing claims.
- No secrets or private URLs were imported accidentally.
The package includes a source review that compares the editor content with the DEV.to Markdown. It also has a security check for secret-like values and unexpected short links. These checks reduce risk, but they cannot replace reading the draft in the Medium editor.
Repair common formatting failures
If extraction identifies a problem, create an actions file and apply only the targeted fixes. For example:
[
{ "type": "removeEmptyCodeBlocks" },
{ "type": "mergeAdjacentCodeBlocks" },
{ "type": "promoteDemoteHeading", "blockIndex": 4, "level": 2 }
]
Apply it to the draft:
medium-publisher fix-draft --url "https://medium.com/p/your-draft/edit" --actions-file fixes.json --json
Run extract again after the change. A repair is complete only when the editor and the source agree on the affected blocks. If the editor has changed since the release, stop and inspect the draft manually instead of repeatedly applying actions.
What success looks like
A successful draft run gives you a Medium editor URL, the expected title, readable code blocks, intact headings and lists, and metadata that you can explain. The command does not prove that Medium will preserve every future import. It gives you a repeatable starting point and evidence for a human review.
For a final pre-publication check, compare the draft with the original DEV.to article block by block. Pay particular attention to content after the first code block because malformed fences can make later paragraphs appear to be code. The repository's own test suite covers its formatting and metadata helpers, but your article is still the source of truth for the review.
Comments
No comments yet. Start the discussion.