Skip to main content

Crier: Cross-Post Your Content Everywhere

I’ve published crier to PyPI - a command-line tool for cross-posting content to multiple platforms simultaneously.

Like a town crier announcing news across the land, crier broadcasts your content to dev.to, Ghost, WordPress, Hashnode, Medium, Bluesky, Mastodon, Threads, Telegram, Discord, and more.

Installation

pip install crier

The Problem

If you maintain a blog and want to reach audiences on multiple platforms, you face tedious manual work: copy content to dev.to, create a Bluesky post with a link, toot on Mastodon, post to your Discord server, etc. Each platform has its own interface, API, and quirks.

The Solution

Crier lets you publish once and distribute everywhere:

# Publish to multiple platforms at once
crier publish post.md --to devto --to bluesky --to mastodon --to discord

That single command:

  • Publishes the full article to dev.to
  • Creates a Bluesky post with title, description, and link
  • Toots on Mastodon with hashtags from your tags
  • Posts an announcement embed to your Discord server

Supported Platforms

PlatformTypeNotes
dev.toBlogFull article support
HashnodeBlogFull article support
MediumBlogPublish only
GhostBlogFull article support
WordPressBlogSelf-hosted or .com
ButtondownNewsletterEmail subscribers
BlueskySocialPosts with link cards
MastodonSocialToots with hashtags
ThreadsSocialShort posts
LinkedInSocialProfessional network
Twitter/XSocialCopy-paste mode
TelegramChannelBot posts
DiscordChannelWebhook embeds

How It Works

Crier reads standard markdown with YAML front matter:

---
title: "My Post Title"
description: "A brief description"
tags: [python, cli, automation]
canonical_url: https://myblog.com/my-post
---

Your content here...

For blog platforms, it publishes the full article. For social platforms, it creates short announcements with links back to your canonical URL.

GitHub Actions: Fully Automated Cross-Posting

The real power comes from combining crier with GitHub Actions. Push a new post to your repo and it automatically gets cross-posted everywhere.

Quick Setup

# In your content repository
crier init-action

This creates a workflow file and sets up your API keys as GitHub secrets automatically.

How It Works

  1. You push a new markdown file to posts/
  2. GitHub Action triggers
  3. Crier checks its registry for what’s already published
  4. Crier publishes to any platforms that are missing
  5. The registry is committed back to your repo

Here’s what the workflow looks like:

name: Cross-Post Content

on:
  push:
    branches: [main]
    paths:
      - 'posts/**/*.md'

jobs:
  crosspost:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - run: pip install crier

      - name: Cross-post to platforms
        env:
          CRIER_DEVTO_API_KEY: ${{ secrets.CRIER_DEVTO_API_KEY }}
          CRIER_BLUESKY_API_KEY: ${{ secrets.CRIER_BLUESKY_API_KEY }}
        run: |
          crier backfill ./posts --yes

      - name: Save publication state
        run: |
          git config user.name "github-actions[bot]"
          git add .crier/
          git diff --staged --quiet || git commit -m "Update publication registry"
          git push

The Registry

Crier maintains a registry (.crier/registry.yaml) tracking what’s been published:

posts:
  "posts/my-article.md":
    title: "My Article"
    publications:
      devto:
        id: "12345"
        url: https://dev.to/user/my-article
      bluesky:
        id: "abc123"

This means adding a new platform later will automatically backfill your existing content.

CLI Commands

crier publish FILE [--to PLATFORM]...  # Publish to platform(s)
crier backfill PATH [--profile NAME]   # Publish missing content
crier audit PATH                        # Show what needs publishing
crier list PLATFORM                     # List your articles
crier update PLATFORM ID --file FILE   # Update existing
crier platforms                         # Show available platforms
crier config set KEY VALUE              # Configure API keys
crier init-action                       # Set up GitHub Actions

Configuration

Set up API keys once:

crier config set devto.api_key YOUR_DEV_TO_KEY
crier config set bluesky.api_key "handle.bsky.social:app-password"
crier config set mastodon.api_key "mastodon.social:access-token"

Or define profiles for common workflows:

# ~/.config/crier/config.yaml
profiles:
  blogs: [devto, hashnode]
  social: [bluesky, mastodon]
  all: [blogs, social]

Then use: crier publish post.md --profile all

Why I Built This

I maintain content across several platforms and got tired of the manual copy-paste workflow. Crier automates the tedious parts while keeping you in control of what goes where.

The GitHub Actions integration is the key feature: write once in your repo, push, and your content appears everywhere automatically.

Discussion