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:

// © 2025 Curiositry.
//
//  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.

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.