How to Publish Markdown to Dev.to and Hashnode with OpenCode

OpenCode can turn repository Markdown into drafts for Dev.to and Hashnode, then use Groniz to publish or schedule the approved versions. Do not send the same file twice. Start with one source file, create a body-only…

OpenCode can turn repository Markdown into drafts for Dev.to and Hashnode, then use Groniz to publish or schedule the approved versions. Do not send the same file twice. Start with one source file, create a body-only version for each destination, and review the code, links, metadata, and canonical URL plan. Inspect each live Groniz integration separately. Groniz handles OAuth, platform-specific formatting, scheduling, and delivery across 32+ networks. OpenCode keeps the article tied to the repository. Dev.to and Hashnode capabilities can change, so each integration's live schema is authoritative.

The two-destination workflow

Source Markdown
  -> validate code, links, and claims
  -> choose one canonical public URL
  -> create body-only Dev.to and Hashnode variants
  -> review metadata separately
  -> discover each integration's live schema
  -> approve and deliver separately
  -> verify both native articles

Dev.to and Hashnode both publish articles for developer audiences, but they do not share the same required settings, limits, metadata, or post types.

Prerequisites

Before you begin, make sure you have:

  • OpenCode with access to the article repository.
  • A Groniz account and a connected Dev.to integration.
  • A connected Hashnode integration before attempting that destination.
  • The source Markdown file and every local image it references.
  • A decision about the canonical public URL and publication order.
  • Permission to publish under the selected author or publication.

Choose one way to connect OpenCode to Groniz. For the standard Skill install, run:

npx skills add groniz/groniz-cli

OpenCode discovers skills from project locations including .agents/skills/<name>/SKILL.md, as described in the official OpenCode skills guide. If you prefer MCP, OpenCode supports remote servers in opencode.json; the official MCP guide documents that client configuration.

To use the native CLI, install the binary and authenticate through its browser device flow:

curl -fsSL https://groniz.com/install.sh | sh
groniz auth:login
groniz whoami

For remote MCP, create an API key and add the server to opencode.json:

{
  "mcp": {
    "groniz": {
      "type": "remote",
      "url": "https://mcp.groniz.com/mcp",
      "enabled": true,
      "headers": { "Authorization": "Bearer YOUR_API_KEY" },
    },
  },
}

Keep credentials outside version control.

Why Markdown publishing needs destination-specific work

Broken code blocks and stale commands are hard to miss in a developer article. With repository access, OpenCode can run examples, follow relative links, and compare claims with current files before it touches the publishing layer.

A Dev.to version may need different publication settings from a Hashnode version. Handle its series, organization, canonical URL, and cover image only through fields exposed by that integration's live schema. Never infer one schema from the other. For the wider architecture, start with publishing to social media from OpenCode and the Groniz Connectors overview. The repository-verification pattern also appears in the Codex GitHub release-to-X guide, although that guide produces short-form output.

Prepare the source

Keep the master article neutral about the destination. A practical structure is:

articles/
  retry-guide.md
  assets/
    retry-flow.png
  publish/
    retry-guide.devto.body.md
    retry-guide.hashnode.body.md

Before creating either destination version, ask OpenCode to:

  1. Run or validate every command it can safely test.
  2. Resolve relative links and identify repository-only URLs.
  3. Check headings, fenced code languages, tables, and image references.
  4. Map factual product claims to source files.
  5. Flag secrets, private hostnames, internal issue IDs, and unreleased features.

Keep the source file authoritative. Make corrections there first, then regenerate both destination variants.

The source file does not determine the canonical public URL. If the article already exists on your site, record that public URL. If Dev.to or Hashnode will host the canonical edition, publish that edition first and capture its URL before preparing the other copy.

Separate metadata from the Markdown body

A repository article may begin with YAML front matter:

---
title: "Retry handling in the Example API"
description: "A practical guide to retry policies."
tags: [api, reliability]
---

Article body starts here.

Remove that block before delivery. Each .body.md file should contain only the article body. Put the proposed title, description, tags, cover image, publication, series, and canonical URL in the publication manifest. Map a metadata field into --settings only when that integration's live schema exposes it. Otherwise, YAML may appear as article text, or a request may send Hashnode fields to Dev.to and vice versa.

Create two channel-native drafts

Give OpenCode a transformation request that preserves the technical body while allowing destination-specific framing:

Use articles/retry-guide.md as the source of truth. Produce separate Dev.to
and Hashnode body-only drafts. Strip YAML front matter from the bodies. Preserve
all code and technical qualifiers. Return proposed title, description, tags,
cover asset, canonical plan, and any destination setting that must be discovered
at runtime separately. Do not assume the two destinations share fields. Do not
publish or schedule anything.

The two versions may share most of the tutorial. Still, review their introductions, closing context, metadata, and relationship to the canonical edition separately. Skip the large promotional block that often gets pasted onto syndicated articles. Choose one canonical public URL instead of letting each copy claim to be the original. If a live schema exposes a matching canonical field, use the chosen URL. If it does not, record the limitation rather than inventing a setting, then decide whether publishing both copies is acceptable.

Reusable asset: publication manifest

Save this beside the source article as retry-guide.publish.md:

# Publication manifest

Source file: articles/retry-guide.md
Canonical public URL:
Source commit:

## Dev.to
Integration ID:
Body file: articles/publish/retry-guide.devto.body.md
Required settings: discover live
Approved title/tags:
Approved media paths:
Publication time/timezone:

## Hashnode
Integration ID:
Body file: articles/publish/retry-guide.hashnode.body.md
Required settings: discover live
Approved title/tags:
Approved media paths:
Publication time/timezone:

Reviewer:
Content approval:
Publication approval:

The manifest records the source commit behind each public article. It also keeps approval for one destination from silently carrying over to the other.

Review before either write action

Render both Markdown files locally. Check their headings, lists, callouts, tables, syntax highlighting, embedded HTML, image captions, and link targets. Make sure the platform title does not repeat the first body heading. When practical, copy commands into a clean environment and run them there. Examples should use placeholders instead of credentials, and version-specific instructions should name the version you tested.

Next, review the author identity, title, description, tags, chosen canonical URL, cover image rights, and schedule timezone. If the canonical edition must be published first, confirm that its public URL exists before approving the copy. Approve Dev.to and Hashnode independently. A valid Dev.to draft tells you nothing about whether the Hashnode request is valid.

Deliver through Groniz, one integration at a time

Start by confirming the authenticated account and resolving live IDs:

groniz whoami
groniz integrations:list
groniz integrations:settings <devto-integration-id>
groniz integrations:settings <hashnode-integration-id>

Use each settings result as the specification for that integration. Supply every required value and stay within the reported limits. If the schema lists dynamic integration tools, call only those tools and use their returned values. For example, if an integration requires a publication selector, get it from live data instead of copying one from an example.

Upload approved media before constructing either post request:

groniz upload ./articles/assets/retry-flow.png

Record the returned .path in the manifest. Use that path in the post request, not the local filename or an external URL. Have OpenCode show the complete proposed Dev.to request for publication approval, then repeat the review for Hashnode. Schedule or publish each request only after its own approval.

The CLI commands remain separate because each integration has its own schema:

groniz posts:create \
  -c "$(cat -- ./articles/publish/retry-guide.devto.body.md)" \
  -m "<returned-groniz-media-.path>" \
  -s "2026-08-18T14:00:00Z" \
  -t schedule \
  -i "DEVTO_INTEGRATION_ID" \
  --settings '<devto-required-settings-json>'

groniz posts:create \
  -c "$(cat -- ./articles/publish/retry-guide.hashnode.body.md)" \
  -m "<returned-groniz-media-.path>" \
  -s "2026-08-19T14:00:00Z" \
  -t schedule \
  -i "HASHNODE_INTEGRATION_ID" \
  --settings '<hashnode-required-settings-json>'

Use the returned .path for each -m argument. Resolve the two settings placeholders independently. Show each completed command for its own publication approval.

Verify delivery and handle partial success

After each request, capture the Groniz post ID, status, scheduled or published time, and returned platform URL. Open the article on its native platform and test the table of contents, code blocks, images, outbound links, author, tags, and canonical behavior. Use browser developer tools or page source to inspect the rendered page's canonical link and confirm that it matches the publication manifest.

One destination can succeed while the other fails. Dev.to may publish while Hashnode rejects a field or has no connected integration. Leave the successful destination alone. For the failed destination, refresh its settings, compare the request with that schema, and check whether the rejected operation created a draft before retrying.

Use this failure record:

Destination:
Integration ID:
Source commit:
Post ID/status:
Native URL:
Observed rendering issue or error:
Settings refreshed:
Correction:
Retry separately approved:

Measure the next iteration

Use the Dev.to and Hashnode analytics and reader signals available to the account. Record article views, the reading or engagement indicators each destination provides, useful comments, referral traffic to the canonical site, and corrections from readers. Raw totals do not make a clean comparison because the two platforms have different audiences and distribution systems.

Put concrete findings back into the manifest. For example, record that "tables were hard to scan on mobile" or "the runnable example prompted detailed comments." That gives the next OpenCode pass a specific editorial issue to address. Publishing more often, by itself, does not guarantee growth.

When both integrations are ready, set up their connections in Groniz Connectors.