Multi-Platform Blog Publisher

Multi-Platform Blog Publisher
π Automatically publish your articles to multiple blogging platforms using GitHub Actions
Table of Contents
- Overview
- Features
- Quick Start
- Repository Structure
- Installation
- Usage
- Configuration
- Content Format
- Setup Instructions
- Usage Examples
- Troubleshooting
- Contributing
- License
- Acknowledgments
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
Method 1: Use as GitHub Action (Recommended)
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
- Fork this repository
- Customize the scripts in
hashnode-publish/scripts/
ordevto-publish/scripts/
- Update action configurations in
action.yml
files - Use your forked version in workflows
Usage
GitHub Secrets Configuration
For Hashnode:
HASHNODE_PAT
- Your Hashnode Personal Access TokenHASHNODE_PUBLICATION_ID
- Your publication IDHASHNODE_PUBLICATION_HOST
- Your publication domain (e.g.,yourblog.hashnode.dev
)VAR_EDIT_TOKEN_GIT
- GitHub token withrepo
andactions:write
permissions
For Dev.to:
DEV_TO_API_KEY
- Your Dev.to API KeyVAR_EDIT_TOKEN_GIT
- GitHub token withrepo
andactions: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
Input | Required | Default | Description |
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
Input | Required | Default | Description |
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.

### 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
- Content Detection: Finds
README.md
in your content directory or repository root - Title Extraction: Uses the first
# heading
as the article title - Image Processing: Converts relative image paths to GitHub raw URLs
- Tag Processing: Extracts tags from
Tags:
line at the end of content - Smart Publishing:
- Checks for existing posts with the same title
- Creates new post if none exists
- Updates existing post if found
- State Management: Saves post IDs and metadata to repository variables for future updates
Troubleshooting
Common Issues
Issue | Solution | Additional Steps |
Action not found | Ensure repository is public or you have proper access | Check the action reference path is correct |
README not found | Check content directory path and verify file exists | Verify content-path input matches your structure |
API errors | Verify all secrets are correctly configured | Test API keys manually with curl commands |
Permission errors | Ensure GitHub token has required scopes | Regenerate token with proper permissions |
Image not loading | Check image paths and ensure they're accessible | Use absolute URLs or verify relative paths |
Tags not working | Check tag format and characters | Use only alphanumeric characters and hyphens |
Content not updating | Clear browser cache and check post URLs | Verify the post ID variables are updated |
Debugging Steps
- Check Action Logs: Go to the Actions tab in your repository for detailed execution logs
- Verify Secrets: Ensure all required secrets are set in repository settings
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
- Check File Paths: Verify your content structure matches the expected format
- 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., 
). 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
- Fork the repository
- Clone your fork
git clone https://github.com/yourusername/article-automation.git cd article-automation
- Install dependencies
npm install
- Create a feature branch
git checkout -b feature/amazing-feature
- Make your changes
- Test your changes (add tests if applicable)
- Commit your changes
git commit -m 'Add some amazing feature'
- Push to the branch
git push origin feature/amazing-feature
- 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
- Create Feature Branch: Use descriptive names (e.g.,
feature/add-medium-support
) - Write Tests: Add unit tests for new functionality
- Update Documentation: Keep README and inline docs current
- Test Thoroughly: Ensure all tests pass and no regressions
- 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
- Built with GitHub Actions
- Integrates with Hashnode and Dev.to
- Inspired by the need for automated content distribution
- Thanks to all contributors
Subscribe to my newsletter
Read articles from Gokul Nathan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
