Skip to content

HackMD & Astro

HackMD is a collaborative Markdown editor and publishing platform. You can use its API to manage your content in HackMD and display it in your Astro project.

This guide uses the official @hackmd/api client to fetch your notes and markdown-it to render Markdown content.

To get started, you will need:

  1. An Astro project - If you don’t have an Astro project yet, the installation guide will get you up and running.
  2. A HackMD account - You can sign up for free.
  3. A HackMD access token - Create one from the API section of your HackMD settings.
  4. At least one publicly readable note - Set the note’s read permission to Everyone so the example can safely publish it on your site.

Create a .env file in the root of your project and add your HackMD access token:

.env
HACKMD_API_ACCESS_TOKEN=<YOUR_ACCESS_TOKEN>

Do not prefix this variable with PUBLIC_. This keeps the token available only to your server-side code and prevents Astro from exposing it to the browser.

Read more about environment variables and .env files in Astro.

Install the HackMD API client and Markdown renderer:

Terminal window
npm install @hackmd/api markdown-it

Create a hackmd.ts file in a new src/lib/ directory. This file initializes the API client, renders Markdown, and creates a URL-friendly identifier for each note:

src/lib/hackmd.ts
import { API } from '@hackmd/api';
import MarkdownIt from 'markdown-it';
export const client = new API(import.meta.env.HACKMD_API_ACCESS_TOKEN);
const md = new MarkdownIt({
html: false,
linkify: true,
typographer: true,
});
export function renderMarkdown(content: string) {
return md.render(content);
}
export function getNoteSlug(note: { permalink: string | null; shortId: string }) {
return note.permalink ?? note.shortId;
}

The html: false option prevents raw HTML in a note from being passed directly to your generated page. Standard Markdown is still rendered as HTML.

Your project will use the following files:

  • Directorysrc/
    • Directorylib/
      • hackmd.ts
    • Directorypages/
      • index.astro
      • Directorynotes/
        • [slug].astro
  • .env
  • astro.config.mjs
  • package.json

This example creates an index of publicly readable notes and a statically generated page for each note.

Use getNoteList() in src/pages/index.astro to retrieve your notes. Filter the results so that only notes with the guest read permission are included in the public site:

src/pages/index.astro
---
import { client, getNoteSlug } from '../lib/hackmd';
const notes = await client.getNoteList();
const publicNotes = notes.filter((note) => note.readPermission === 'guest');
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>Astro + HackMD</title>
</head>
<body>
<main>
<h1>My HackMD notes</h1>
<ul>
{
publicNotes.map((note) => (
<li>
<a href={`/notes/${getNoteSlug(note)}/`}>{note.title}</a>
</li>
))
}
</ul>
</main>
</body>
</html>

Create src/pages/notes/[slug].astro to generate a static page for every public note. The note list provides the route and note ID, then getNote() retrieves the full Markdown content for that page:

src/pages/notes/[slug].astro
---
import { client, getNoteSlug, renderMarkdown } from '../../lib/hackmd';
export async function getStaticPaths() {
const notes = await client.getNoteList();
return notes
.filter((note) => note.readPermission === 'guest')
.map((note) => ({
params: { slug: getNoteSlug(note) },
props: { noteId: note.id },
}));
}
interface Props {
noteId: string;
}
const { noteId } = Astro.props;
const note = await client.getNote(noteId);
const content = renderMarkdown(note.content);
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>{note.title}</title>
</head>
<body>
<main>
<article>
<Fragment set:html={content} />
</article>
</main>
</body>
</html>

HackMD uses markdown-it with extensions for features such as task lists, footnotes, containers, and a table of contents. The minimal configuration above handles standard Markdown. Install only the markdown-it plugins required by your notes.

Follow the deployment guide to publish your Astro site. Because getStaticPaths() runs at build time, you must start a new build to publish changes made in HackMD. You can trigger builds manually or use your hosting provider’s deploy hooks and scheduling features.

  • daily-oops - A blog that uses HackMD as its CMS
  • astro-hackmd - A minimal Astro site that uses HackMD as its CMS

More CMS guides

Featured CMS partners

  • CloudCannon

    Git-based CMS built for speed, security, and zero headaches.

All CMS guides

Contribute Community Sponsor