DEV Community

Cover image for GitHub action to publish your blog post to
Aral Roca
Aral Roca

Posted on • Originally published at

GitHub action to publish your blog post to

Original article:

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: DEV_TO=${{secrets.DEV_TO}} yarn run publish:post
      - 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 it does?

  • Programs the action on push to master and every day at 17:00 UTC using a cron.
  • Installs dependencies with yarn install --pure-lockfile
  • Sets environment variable DEV_TO using GitHub secrets. This is required for our script.
  • Runs our script to publish to
  • 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 occourrence 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.

Top comments (0)