After publishing a post to my site, I usually cross-post it to DEV.to. Originally I would use their RSS feature and cross-posting was quite a painless process, but over time I've added new features to my blog like:
Which meant that I had to keep making manual changes to all my posts to make them ready for publishing on DEV. To save me some time, I wrote a script to automate this process.
If you've never written a script before, I've written a post on renaming files with Node.js scripts which should give you a good overview.
The cross-posting workflow I follow now is this:
devto.js
script.I cross-post my posts to DEV.to via my site’s RSS feed. This way, I get the “Originally published at” message to show up underneath the title of my posts:
If you go to your DEV.to settings page, and click the Extensions option, you’ll have the opportunity to add an RSS feed:
Once you have hooked up your RSS feed, DEV.to will periodically check it to see if there are any new posts, and add the post in DEV as a draft.
After I publish a post on my own site, I go into DEV.to and hit the “Fetch feed now” button to make it show up straightaway. Unfortunately DEV doesn't have an API to do this step from within my script.
To run this script, you’ll need your own DEV API key. I store mine in a .env
file in my site’s repository:
DEV_API_KEY=<key_goes_here>
The script makes use of two of the DEV API’s endpoints:
My posts are stored on my repository with Markdown and frontmatter, in a format like this:
---
title: "Hello! This is the markdown file"
date: 2021-09-25
tags: ["react"]
---
Content of the post goes here.

The script will transform it into this on DEV:
---
title: "Hello! This is the markdown file"
published: false
tags: ["react"]
---
Content of the post goes here.

There’s three things to point out here:
published: false
so it stays in draft modedate
field. If you leave this value, DEV will set it as having been published at midnight of the date you specified. This can reduce the chance of your post actually getting views on DEV’s home page since it’s considered an “old” post.The full version of the script is available on my site’s Github repository, and I’ve got a shortened version below that you can copy-paste.
#!/usr/bin/env node
const { readFile } = require('fs');
const { join } = require('path');
const glob = require('glob');
const fetch = require('node-fetch');
// I store my API key in a .env file
require('dotenv').config();
const updateArticle = (devArticleId, content) => {
fetch(`https://dev.to/api/articles/${devArticleId}`, {
method: 'PUT',
headers: {
'api-key': process.env.DEV_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
article: {
body_markdown: content,
},
}),
})
};
const updateFile = (content, slug) => {
// Update frontmatter
content = content.replace(/date: .*\n/, 'published: false\n');
// Update images
content = content.replace(
/!\[(.+?)\]\(\.\/(.+?)\)/g,
``,
);
// TODO: Any other changes to your content
return content;
}
const devto = () => {
// Looks for folders inside of "posts" folder that matches the given slug.
const slug = process.argv[1];
const file = [
...glob.sync(join(process.cwd(), 'posts', slug, 'index.mdx')),
][0];
readFile(file, 'utf8', (err, content) => {
if (err) reject(err);
const title = content.match(/title: "(.*)"\n/)[1];
content = updateFile(content, slug);
fetch('https://dev.to/api/articles/me/unpublished', {
headers: { 'api-key': process.env.DEV_API_KEY },
})
.then((response) => response.json())
.then((response) => {
if (response.length > 0) {
const draftTitle = response[0].title;
if (draftTitle === title) {
const devArticleId = response[0].id;
updateArticle(devArticleId, content);
}
}
})
});
};
devto();