Cross-Post a DEV.to Tutorial to Medium with a Formatting Check Fernando Paladini's open-source project publish-agents offers a reviewable workflow for cross-posting DEV.to tutorials to Medium, using its medium-publisher package to import articles, check formatting, and repair common issues. The tool, built with Patchright browser automation and requiring a saved browser session, outputs a draft for manual inspection before publication. 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 https://github.com/paladini/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. Checkout 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. You need: 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. 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. 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. 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. 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 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. 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. 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. The same package exposes an MCP server. Register medium-publisher-mcp with your MCP client and use medium publish from devto with publish: false for a draft-first flow. The project documents stdio configuration for clients such as Cursor and Claude Code. An agent can prepare the URL and summarize the extraction result, but the account owner should approve login, external publication, and any formatting correction. Browser automation is an integration boundary, not a reason to remove human review. The CLI uses exit code 3 for a missing or expired session. Run medium-publisher login again. A two-factor challenge or bot check may require headed browser interaction. The source article must be public because the workflow reads its public DEV.to representation. Confirm that the URL loads without authentication and that the article has been published. Medium's interface can change. The project waits for topic autocomplete and targets the current import and editor controls, but a future UI change can still break a selector. Use extract , inspect the draft, and report reproducible failures to the repository rather than publishing blindly. Stop the workflow if the source or editor contains credentials, private links, or unexpected content. Delete the draft through Medium's controls as appropriate, rotate exposed credentials, and review the local browser-state directory. No. The documented workflow uses browser automation and a persistent login session. The CLI supports publishing options, but this tutorial deliberately uses --draft . Review the imported editor before choosing a live publication action. The package supports public URL import, while the one-shot publish-devto path expects a public DEV.to article so it can compare the imported result with the source Markdown. No. Medium UI changes, account challenges, and article-specific content can still require manual intervention. AI assistance was used to organize this tutorial and check its wording. Commands, version references, workflow boundaries, and limitations were checked against the public publish-agents repository, its v0.2.3 release, and the package's documented source behavior. Cross-posting is safer when import, repair, and publication are separate decisions. Use publish-devto --draft , inspect the real Medium editor, rerun extraction after any fix, and publish only after the source and destination agree. Have you found a Medium import failure that a block-level source review catches earlier than a visual skim?