The Autodidacts

Exploring the universe from the inside out

Script: export post as Markdown via Ghost Admin API

Once upon a time I deleted the production database

Note: this post is part of #100DaysToOffload, a challenge to publish 100 posts in 365 days. These posts are generally shorter and less polished than our normal posts; expect typos and unfiltered thoughts! View more posts in this series.

Long, long ago, when I was a young and inexperienced sysadmin, I ran some Heroku commands, and discovered that I had deleted my production Heroku PostgreSQL database for this Ghost blog. There was no option to rollback, and my backups didn’t include my draft posts. On that day, I decided to store the primary versions of all my posts locally, as versioned markdown files, that I paste into Ghost when I’m ready to publish.

This has worked fine, but the Ghost editor has gotten so nice, I often want to use it for drafting posts. And at the same time, Markdown, though still supported via the Markdown card, no longer has first-class support, and there’s no way to copy-and-paste formatting created with the new Ghost editor into a Markdown file.

I looked around and couldn’t find any existing scripts to export posts as markdown. So I wrote a little script, based on the Ghost Admin API demos repo, that fetches the content of a post by ID, and converts it to Markdown with the wonderful tool Pandoc (which I’ve written about before).

I thought I’d share it in case it’s useful for other people.

You will need to have the following installed:

  • NodeJS
  • NPM
  • Pandoc

The only other dependency is the Ghost Admin API SDK, which can be installed with:

npm install @tryghost/admin-api

Here is the script itself:

//  First:
//      npm install @tryghost/admin-api
//  Run with:
//      node get-post-as-markdown.js "POSTID" https://blah.ghost.io "ADMIN_API_KEY" | pandoc -f html+smart -t markdown-smart --wrap="preserve"

if (process.argv.length < 4) {
    console.error('Missing an argument');
    process.exit(1);
}

const postId = process.argv[2];
const url = process.argv[3];
const key = process.argv[4];

const GhostAdminAPI = require('@tryghost/admin-api');
const api = new GhostAdminAPI({
    url: url,
    key: key,
    version: 'v6.8'
});

api.posts.read({id: postId}, {formats: ['html']})
    .then((post) => {
            console.log("<h1>"+post.title+"</h1>")
            console.log("<h2>"+post.excerpt+"</h2>")
            console.log(""+post.slug+"")
            console.log(post.html);
    })
    .catch((err) => {
        console.error(err);
    });

So, how do we use it?

To output the text to your console for testing, run it with:

node get-post-as-markdown.js "POSTID" https://blah.ghost.io "ADMIN_API_KEY" | pandoc -f html+smart -t markdown-smart --wrap="preserve"

or, to write to a file (more useful!), use:

node get-post-as-markdown.js "POSTID" https://blah.ghost.io "ADMIN_API_KEY" | pandoc -f html+smart -t markdown-smart --wrap="preserve" -o "$(date -I) My Output Filename 1.md"

Obviously, replace POSTID with your post id, which you can see when you’re editing a post, because it’s the last part of the url (https://YOURSITE.com/ghost/#/editor/post/POSTID). And replace the url with your Ghost url, and the API key with one that you set-up by going to https://www.YOURSITE.com/ghost/#/settings/integrations/new, and following the steps, and then copying the ADMIN_API_KEY value.

Caveats:

  1. Sometimes it hard wraps lines. If you don’t like that, try switching --wrap=preserve for --wrap=none.
  2. For some reason, even though it’s set to output HTML, Ghost editor cards such as snippets don’t always render as HTML.
  3. It smartens straight typewriter-style quotes and apostrophes into proper curly quotation marks and apostrophes, because I like that. If you don’t, switch this part -f html+smart -t markdown-smart to -f html -t markdown.
  4. Square brackets and other special characters may have unecessary backslash escapes, which you’ll probably want to remove manually.
  5. When you pull from a private draft, links have {rel=noreferrer} — I’m partly responsible — and I haven't bothered to automate removing this yet. (Edit: you can pipe the markdown through this ripgrep command: rg --passthru -NUF '{rel="noreferrer"}' -r ''.)

Sign up for updates

Join the newsletter for curious and thoughtful people.
No Thanks

Great! Check your inbox and click the link to confirm your subscription.