Multi-Platform Blog Publisher

Gokul NathanGokul Nathan
12 min read

Multi-Platform Blog Publisher

GitHub Actions Hashnode Dev.to MIT License Node.js


πŸš€ Automatically publish your articles to multiple blogging platforms using GitHub Actions

Table of Contents

Overview

This project automates the process of synchronizing content between GitHub README files and various blogging platforms. Eliminate manual copy-pasting and separate content maintenanceβ€”write once in Markdown and publish everywhere!

GitHub README β†’ Blog Post Automation

Supported Platforms

  • βœ… Hashnode - Fully integrated
  • βœ… Dev.to - Fully integrated
  • 🚧 Medium - API restrictions prevent automation
  • 🚧 LinkedIn - OAuth limitations prevent API usage

Features

  • ⚑ Zero Setup: No need to create API scriptsβ€”everything is included
  • 🌐 Multi-Platform: Supports Hashnode and Dev.to
  • 🧠 Smart Updates: Automatically tracks and updates existing posts
  • πŸ–ΌοΈ Image Processing: Converts relative image paths to GitHub raw URLs
  • πŸ“ Flexible Content: Works with any markdown content structure
  • πŸ”’ Secure: All credentials stay in your repository secrets
  • ♻️ State Management: Saves post metadata for future updates

Quick Start

1. Fork or Use This Repository

You can either fork this repository or use it as a GitHub Action in your own repository.

2. Set Up Your Content Repository

Create a repository structure like this:

your-blog-repo/
β”œβ”€β”€ content/                 # Your articles directory
β”‚   └── README.md           # Your article content
β”œβ”€β”€ .github/workflows/
β”‚   └── publish.yml         # Workflow using these actions
β”œβ”€β”€ images/                 # Optional: images referenced in articles
β”‚   └── screenshot.png
└── README.md              # Project documentation

3. Configure GitHub Secrets

Go to Settings β†’ Secrets and variables β†’ Actions and add the required secrets.

4. Create Workflow File

Create .github/workflows/publish.yml with the provided configuration.

5. Push and Publish! πŸŽ‰

Your articles will be automatically published when you push to the main branch.


Repository Structure

article-automation/
β”œβ”€β”€ hashnode-publish/
β”‚   β”œβ”€β”€ action.yml          # Hashnode action definition
β”‚   └── scripts/
β”‚       └── hashnode.js     # Hashnode publishing script
β”œβ”€β”€ devto-publish/
β”‚   β”œβ”€β”€ action.yml          # Dev.to action definition
β”‚   └── scripts/
β”‚       └── devto_post.js   # Dev.to publishing script
β”œβ”€β”€ api/                    # Legacy API scripts
β”‚   β”œβ”€β”€ hashnode.js
β”‚   └── devto_post.js
β”œβ”€β”€ package.json            # Dependencies for scripts
β”œβ”€β”€ image.png              # Project logo
└── README.md              # This file

Installation

Add this to your workflow file:

- name: Publish to Hashnode
  uses: gokulnathan66/article-automation/hashnode-publish@main
  with:
    hashnode-pat: ${{ secrets.HASHNODE_PAT }}
    # ... other configuration

Method 2: Fork and Customize

  1. Fork this repository
  2. Customize the scripts in hashnode-publish/scripts/ or devto-publish/scripts/
  3. Update action configurations in action.yml files
  4. Use your forked version in workflows

Usage

GitHub Secrets Configuration

For Hashnode:

  • HASHNODE_PAT - Your Hashnode Personal Access Token
  • HASHNODE_PUBLICATION_ID - Your publication ID
  • HASHNODE_PUBLICATION_HOST - Your publication domain (e.g., yourblog.hashnode.dev)
  • VAR_EDIT_TOKEN_GIT - GitHub token with repo and actions:write permissions

For Dev.to:

  • DEV_TO_API_KEY - Your Dev.to API Key
  • VAR_EDIT_TOKEN_GIT - GitHub token with repo and actions:write permissions

Workflow Configuration

Create .github/workflows/publish.yml:

name: Multi-Platform Publishing

on:
  push:
    branches: [main]
    paths: 
      - 'content/**'
      - 'README.md'
  workflow_dispatch:

env:
  NODE_VERSION: '20'

jobs:
  publish-hashnode:
    runs-on: ubuntu-latest
    if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'

    steps:
      - name: Checkout content
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Validate required secrets
        run: |
          if [ -z "${{ secrets.HASHNODE_PAT }}" ]; then
            echo "Error: HASHNODE_PAT secret is not set"
            exit 1
          fi

      - name: Publish to Hashnode
        uses: gokulnathan66/article-automation/hashnode-publish@main
        with:
          hashnode-pat: ${{ secrets.HASHNODE_PAT }}
          hashnode-publication-id: ${{ secrets.HASHNODE_PUBLICATION_ID }}
          hashnode-publication-host: ${{ secrets.HASHNODE_PUBLICATION_HOST }}
          github-token: ${{ secrets.VAR_EDIT_TOKEN_GIT }}
          saved-post-id: ${{ vars.HASHNODE_SAVED_POST_ID }}
          saved-post-slug: ${{ vars.HASHNODE_SAVED_POST_SLUG }}
          saved-post-title: ${{ vars.HASHNODE_SAVED_POST_TITLE }}
          saved-post-url: ${{ vars.HASHNODE_SAVED_POST_URL }}
          saved-post-published-at: ${{ vars.HASHNODE_SAVED_POST_PUBLISHED_AT }}
          saved-post-updated-at: ${{ vars.HASHNODE_SAVED_POST_UPDATED_AT }}

  publish-devto:
    runs-on: ubuntu-latest
    if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'

    steps:
      - name: Checkout content
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Validate required secrets
        run: |
          if [ -z "${{ secrets.DEV_TO_API_KEY }}" ]; then
            echo "Error: DEV_TO_API_KEY secret is not set"
            exit 1
          fi

      - name: Publish to Dev.to
        uses: gokulnathan66/article-automation/devto-publish@main
        with:
          devto-api-key: ${{ secrets.DEV_TO_API_KEY }}
          github-token: ${{ secrets.VAR_EDIT_TOKEN_GIT }}
          saved-post-id: ${{ vars.DEV_TO_SAVED_POST_ID }}
          saved-post-title: ${{ vars.DEV_TO_SAVED_POST_TITLE }}
          saved-post-url: ${{ vars.DEV_TO_SAVED_POST_URL }}
          saved-post-published-at: ${{ vars.DEV_TO_SAVED_POST_PUBLISHED_AT }}
          saved-post-updated-at: ${{ vars.DEV_TO_SAVED_POST_UPDATED_AT }}

Configuration

Hashnode Action Inputs

InputRequiredDefaultDescription
hashnode-patβœ…-Hashnode Personal Access Token
hashnode-publication-idβœ…-Publication ID
hashnode-publication-hostβœ…-Publication host
github-tokenβœ…-GitHub token
content-path❌'content'Path to content files
node-version❌'20'Node.js version
saved-post-*❌-Previously saved post data for updates

Dev.to Action Inputs

InputRequiredDefaultDescription
devto-api-keyβœ…-Dev.to API Key
github-tokenβœ…-GitHub token
content-path❌'content'Path to content files
node-version❌'24'Node.js version
saved-post-*❌-Previously saved post data for updates

Content Format

Your markdown files should follow this structure:

# Your Article Title

Your content here with markdown formatting.

## Sections

You can include images, code blocks, and other markdown elements.

![Screenshot](https://raw.githubusercontent.com/gokulnathan66/article-automation/main/images/screenshot.png)

### Code Examples

```javascript
// Example: Basic error handling in Node.js
try {
  const result = processData();
  console.log("Processing successful:", result);
} catch (error) {
  console.error("Error occurred:", error.message);
  process.exit(1);
}

function processData() {
  // Your processing logic here
  return "Hello, World!";
}

Tags: javascript, tutorial, webdev, githubactions Tag "github-actions" contains non-alphanumeric or prohibited unicode characters


> **Important**: Tags should be added at the end of your content using the format `Tags: tag1, tag2, tag3`. Avoid non-alphanumeric characters in tags.

---

## Setup Instructions

### Getting Hashnode Credentials

#### 1. Personal Access Token
1. Go to [Hashnode](https://hashnode.com) β†’ **Settings** β†’ **Developer**
2. Generate **Personal Access Token**
3. Add as `HASHNODE_PAT` secret in your repository

#### 2. Publication Details
1. Go to your publication dashboard
2. Copy **Publication ID** from the URL or settings
3. Use your publication domain (e.g., `yourblog.hashnode.dev`)

### Getting Dev.to API Key

1. Go to [Dev.to](https://dev.to) β†’ **Settings** β†’ **Extensions**
2. Scroll to **"DEV Community API Keys"**
3. Generate a new API key
4. Add as `DEV_TO_API_KEY` secret in your repository

### Setting up GitHub Token

1. Go to **GitHub** β†’ **Settings** β†’ **Developer settings** β†’ **Personal access tokens** β†’ **Tokens (classic)**
2. Click **"Generate new token"** β†’ **"Generate new token (classic)"**
3. Configure the token:
   - **Note**: `Article Automation Token`
   - **Expiration**: Choose appropriate duration (30-90 days recommended)
   - **Scopes**: Select the following:
     - βœ… `repo` (full repository access)
     - βœ… `workflow` (update GitHub Action workflows)
     - βœ… `write:packages` (if using packages)
4. Click **"Generate token"** and copy the token immediately
5. Add as `VAR_EDIT_TOKEN_GIT` secret in your repository

> **Security Note**: 
> - Set an expiration date for your GitHub token for better security
> - GitHub will notify you before the token expires
> - Store the token securelyβ€”you won't be able to see it again
> - Regenerate tokens periodically as a security best practice

### Local Development Setup

For testing locally, create a `.env` file in your project root:

```env
# Required for Hashnode
HASHNODE_PAT=your_hashnode_personal_access_token
HASHNODE_PUBLICATION_ID=your_publication_id
HASHNODE_PUBLICATION_HOST=yourblog.hashnode.dev

# Required for Dev.to
DEV_TO_API_KEY=your_devto_api_key

# Required for GitHub operations
VAR_EDIT_TOKEN_GIT=your_github_token

# Optional: Content path (defaults to 'content')
CONTENT_PATH=content

⚠️ Important: Never commit your .env file to version control. Add it to your .gitignore file.


Usage Examples

Single Platform Deployment

Hashnode only:

uses: gokulnathan66/article-automation/hashnode-publish@main

Dev.to only:

uses: gokulnathan66/article-automation/devto-publish@main

Custom Content Path

uses: gokulnathan66/article-automation/hashnode-publish@main
with:
  # ... other inputs
  content-path: 'articles'  # Look in 'articles' directory instead

Different Node.js Version

uses: gokulnathan66/article-automation/devto-publish@main
with:
  # ... other inputs
  node-version: '18'  # Use Node.js 18 instead of default 24

How It Works

  1. Content Detection: Finds README.md in your content directory or repository root
  2. Title Extraction: Uses the first # heading as the article title
  3. Image Processing: Converts relative image paths to GitHub raw URLs
  4. Tag Processing: Extracts tags from Tags: line at the end of content
  5. Smart Publishing:
    • Checks for existing posts with the same title
    • Creates new post if none exists
    • Updates existing post if found
  6. State Management: Saves post IDs and metadata to repository variables for future updates

Troubleshooting

Common Issues

IssueSolutionAdditional Steps
Action not foundEnsure repository is public or you have proper accessCheck the action reference path is correct
README not foundCheck content directory path and verify file existsVerify content-path input matches your structure
API errorsVerify all secrets are correctly configuredTest API keys manually with curl commands
Permission errorsEnsure GitHub token has required scopesRegenerate token with proper permissions
Image not loadingCheck image paths and ensure they're accessibleUse absolute URLs or verify relative paths
Tags not workingCheck tag format and charactersUse only alphanumeric characters and hyphens
Content not updatingClear browser cache and check post URLsVerify the post ID variables are updated

Debugging Steps

  1. Check Action Logs: Go to the Actions tab in your repository for detailed execution logs
  2. Verify Secrets: Ensure all required secrets are set in repository settings
  3. Test API Keys:

    # Test Hashnode API
    curl -H "Authorization: Bearer YOUR_TOKEN" https://gql.hashnode.com
    
    # Test Dev.to API  
    curl -H "api-key: YOUR_API_KEY" https://dev.to/api/articles/me
    
  4. Check File Paths: Verify your content structure matches the expected format
  5. Review Variables: Check repository variables for saved post metadata

Error Messages and Solutions

"Post not found" or "Failed to update"

  • Cause: Post metadata variables are incorrect or outdated
  • Solution: Delete the post variables from repository settings and republish

"Invalid API key" or "Unauthorized"

  • Cause: API keys are expired or incorrect
  • Solution: Regenerate API keys and update repository secrets

"File not found" errors

  • Cause: Content path configuration doesn't match your repository structure
  • Solution: Update the content-path input in your workflow

"Image upload failed"

  • Cause: Images are not accessible or paths are incorrect
  • Solution: Use GitHub raw URLs or verify image accessibility

Important Warnings

⚠️ Content Overwriting: When you update something in your blog manually, it will be overwritten by this action. You must update the README, and that will automatically update the blog.

ℹ️ Local Testing: If you want to test locally, create a .env file with the required variables.

Frequently Asked Questions

Q: Can I publish to multiple platforms simultaneously?

A: Yes, the workflow supports parallel publishing to both Hashnode and Dev.to. Each platform runs in a separate job.

Q: How do I handle images in my articles?

A: Use relative paths in your markdown (e.g., ![Alt text](https://raw.githubusercontent.com/gokulnathan66/article-automation/main/images/photo.jpg)). The action automatically converts them to GitHub raw URLs.

Q: Can I schedule automatic publishing?

A: Yes, add a schedule trigger to your workflow:

on:
  schedule:
    - cron: '0 9 * * 1'  # Every Monday at 9 AM UTC

Q: What happens if my API key expires?

A: The action will fail with an authentication error. Update your repository secrets with a new API key.

Q: Can I customize the publication process?

A: Yes, fork the repository and modify the scripts in the hashnode-publish/scripts/ or devto-publish/scripts/ directories.

Q: How do I prevent certain files from triggering publication?

A: Use the paths filter in your workflow to specify which files should trigger the action.


Contributing

We welcome contributions! Here's how you can help:

Development Setup

  1. Fork the repository
  2. Clone your fork
    git clone https://github.com/yourusername/article-automation.git
    cd article-automation
    
  3. Install dependencies
    npm install
    
  4. Create a feature branch
    git checkout -b feature/amazing-feature
    
  5. Make your changes
  6. Test your changes (add tests if applicable)
  7. Commit your changes
    git commit -m 'Add some amazing feature'
    
  8. Push to the branch
    git push origin feature/amazing-feature
    
  9. Open a Pull Request

Contribution Guidelines

Code Standards

  • Follow existing code style and conventions
  • Use meaningful variable and function names
  • Add comprehensive comments for complex logic
  • Ensure compatibility with Node.js 18+ and 20+

Pull Request Process

  1. Create Feature Branch: Use descriptive names (e.g., feature/add-medium-support)
  2. Write Tests: Add unit tests for new functionality
  3. Update Documentation: Keep README and inline docs current
  4. Test Thoroughly: Ensure all tests pass and no regressions
  5. Write Clear Commits: Use conventional commit format
    feat: add support for Medium platform
    fix: resolve image upload timeout issue
    docs: update API configuration examples
    

Testing Your Changes

# Install dependencies
npm install

# Run tests (if available)
npm test

# Test locally with sample content
node scripts/test-local.js

Areas for Contribution

  • πŸ†• New platform integrations (Medium, LinkedIn, etc.)
  • πŸ”§ Improved error handling and retry logic
  • πŸ“š Better documentation and examples
  • πŸ§ͺ Comprehensive test coverage
  • 🎨 UI improvements for logs and outputs
  • πŸ” Advanced content processing features

Reporting Issues

When reporting issues, please include:

Required Information

  • Environment: OS, Node.js version, Action version
  • Configuration: Sanitized workflow file (remove secrets)
  • Error Details: Complete error messages and stack traces
  • Steps to Reproduce: Detailed reproduction steps
  • Expected vs Actual: What should happen vs what does happen

Issue Templates

Use the appropriate issue template:

  • πŸ› Bug Report: For unexpected behavior or errors
  • πŸ’‘ Feature Request: For new functionality suggestions
  • πŸ“š Documentation: For documentation improvements
  • ❓ Question: For usage questions and support

Example Issue Report

**Bug Description**: Action fails when publishing large images

**Environment**:
- OS: Ubuntu 22.04
- Node.js: 20.x
- Action Version: @main

**Steps to Reproduce**:
1. Add image larger than 5MB to content
2. Push to main branch
3. Action fails with timeout error

**Expected**: Image should be processed and uploaded
**Actual**: Action times out after 5 minutes

**Logs**: [Attach relevant action logs]

License

This project is licensed under the MIT License - see the LICENSE file for details.

What this means:

  • βœ… Commercial use
  • βœ… Modification
  • βœ… Distribution
  • βœ… Private use
  • ❌ Liability
  • ❌ Warranty

Acknowledgments


Made with ❀️ by Gokul Nathan B Report Bug β€’ Request Feature β€’ Documentation
0
Subscribe to my newsletter

Read articles from Gokul Nathan directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Gokul Nathan
Gokul Nathan