GitHub action to publish your blog post to

I started writing when I joined in 2017, joining the community motivated me.

After a few articles I decided to create my own personal blog. However, I've always wanted to continue contributing to That's why I post articles on my personal blog and then share them on with the canonical. I suppose it's a standard practice and more than one of you are doing it.

In order to make my life a little easier, I've recently made a GitHub action that posts directly to when it detects a new article on my blog.

How I detect a new post

To know if the article is new and needs to be published, you can use the markdown metadata to find out. In my case, I keep the date of publication as metadata (in case I want to publish it another day even if it's merged to master).

Then, once it's posted to with the GitHub action I create another metadata so it gets tagged as published.

Why? Because the GitHub action will run:

  • Whenever something is pushed to master.
  • Every day at 17:00 UTC.

This way, marking the post as already published, we avoid publishing it twice if we push an article to master at 16:00.

GH action diagram to publish to
GH action diagram to publish to

GitHub action in action

name: Publishing post

    branches: [master]
    - cron: '0 17 */1 * *'

    runs-on: ubuntu-latest

        node-version: [14.x]

      - uses: actions/checkout@v2

      - name: Publishing post
        uses: actions/setup-node@v1
          node-version: ${{ matrix.node-version }}
      - run: yarn install --pure-lockfile
      - run: yarn run publish:post
          DEV_TO: ${{ secrets.DEV_TO }} 
      - run: |
          git config aralroca
          git config
          git add -A
          git diff --quiet && git diff --staged --quiet || git commit -m "[bot] Published to"
          git push origin master

What does it do?

  1. Programs the action on push to master and every day at 17:00 UTC using a cron.
  2. Installs dependencies with yarn install --pure-lockfile
  3. Sets environment variable DEV_TO using GitHub secrets. This is required for our script.
  4. Runs our script to publish to
  5. Commits and pushes to master only when there are changes.

Script to publish to

In our package.json file we have to indicate that the script runs our node file:

  "scripts": {
    "publish:post": "node ./publish/index.js"

This is the content of our script that publishes articles to

async function deploy() {
  const post = getNewPost()

  if (!post) {
    console.log('No new post detected to publish.')

  await deployToDevTo(post)

console.log('Start publishing')
  .then(() => {
  .catch((e) => {
    console.log('ERROR publishing:', e)

The getNewPost function returns the post already formatted in the way needs, null in case that there aren't new posts:

const fs = require('fs')
const path = require('path')
const matter = require('gray-matter')

const deployToDevTo = require('./dev-to')

function getNewPost() {
  const today = new Date()

  return (
      .map((slug) => {
        const post = matter(fs.readFileSync(path.join('posts', slug)))
        return {, slug }
      .filter((p) => {
        const created = new Date(

        return (
          ! &&
          created.getDate() === today.getDate() &&
          created.getMonth() === today.getMonth() &&
          created.getFullYear() === today.getFullYear()
      .map(({ slug, data, content }) => {
        const id = slug.replace('.md', '')
        const canonical = `${id}`
        const body = `***Original article: ${canonical}***\n${content}`

        return {
          body_markdown: body,
          canonical_url: canonical,
          created: data.created,
          description: data.description,
          main_image: data.cover_image,
          published: true,
          series: data.series,
          tags: data.tags,
          title: data.title,
      })[0] || null

I use the gray-matter library to retrieve the markdown metadata and its content.

Here's the deployToDevTo function used in our script:

const fetch = require('isomorphic-unfetch')
const path = require('path')
const fs = require('fs')

function createPost(article) {
  return fetch('', {
    method: 'POST',
    headers: {
      'api-key': process.env.DEV_TO,
      'content-type': 'application/json',
    body: JSON.stringify({ article }),
    .then((r) => r.json())
    .then((res) => {
      console.log(' -> OK', `${res.slug}`)
      return res.slug
    .catch((e) => {
      console.log(' -> KO', e)

async function deployToDevTo(article) {
  const devToId = await createPost(article)

  if (!devToId) return

  const postPath = path.join('posts', article.slug)
  const post = fs.readFileSync(postPath).toString()
  let occurrences = 0

  // Write 'published_devto' metadata before the second occurrence of ---
    post.replace(/---/g, (m) => {
      occurrences += 1
      if (occurrences === 2) return `published_devto: true\n${m}`
      return m

We request to the API to upload the article and then modify our markdown file to add the published_devto: true metadata. This way, our GitHub action will detect that there are changes to upload to master.


In this short article we've seen how to create a GitHub action to post automatically our personal blog new articles to I hope you find it useful.

Discuss on Dev.toDiscuss on TwitterEdit on GitHub
Do all roads lead to Rome?

Do all roads lead to Rome?

Teaful DevTools Released!

Teaful DevTools Released!

Teaful: tiny, easy and powerful React state management

Teaful: tiny, easy and powerful React state management

React state with a fragmented store

React state with a fragmented store