Migration·5 min read·

How do I bring my existing Markdown files into VeloCMS?

There's no batch Markdown importer yet. Here's the real way to move a folder of .md files — from Jekyll, Hugo, Obsidian, or anywhere else — into VeloCMS today.

A lot of writers and developers have a folder of .md files — maybe from a Jekyll, Hugo, or Eleventy site, maybe from Obsidian, maybe from years of personal notes they finally want to publish. Here's the honest state of getting them into VeloCMS.

The honest answer: there's no batch Markdown importer yet

VeloCMS's import page (Admin → Import) currently covers WordPress, Squarespace, Webflow, Substack, Ghost, Medium, and Instagram. There's no dropzone that reads a folder or ZIP of plain .md files, no front-matter parser, and no preview table that shows which posts are ready to go. If that's what you were expecting, it doesn't exist today. What's real is two ways to bring the content across yourself.

For a handful of posts: rebuild them in the editor

Go to Admin → Posts → New Post and write or format each post's content directly in VeloCMS's editor, then set the title, slug, tags, and excerpt yourself from whatever front matter you had. It's manual, not automatic — but it's realistic and reliable for a blog under roughly 20 posts.

For a larger archive: script it against the API

For dozens or hundreds of files, rebuilding each post by hand isn't realistic. VeloCMS has a public REST API for creating posts — POST /api/v1/posts — which accepts title, slug, content_html, excerpt, status (draft or published), tags, and category_slug. Generate a key with the posts:write scope at Admin → Settings → Advanced → API Keys (this requires a Pro plan or higher). From there, write a short script: read each file's own YAML front matter with a standard YAML parser, convert the Markdown body to HTML with a converter you trust, and decide yourself how your front-matter fields map onto VeloCMS's fields. That's more reliable than a generic auto-mapper would be — different static site generators genuinely use different field names (Jekyll's date vs. Hugo's publishDate vs. Gatsby's date, for instance), and only you know which is which for your own files.

import fs from "node:fs";
import matter from "gray-matter";
import { marked } from "marked";

const files = fs.readdirSync("./posts").filter((f) => f.endsWith(".md"));

for (const file of files) {
  const { data, content } = matter(fs.readFileSync(`./posts/${file}`, "utf8"));

  await fetch("https://yourname.velocms.org/api/v1/posts", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.VELOCMS_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      title: data.title,
      slug: data.slug, // omit to let VeloCMS generate one from the title
      content_html: marked.parse(content),
      excerpt: data.description ?? data.summary,
      tags: data.tags ?? [],
      status: "draft",
    }),
  });
}

Images

There's no images/ or assets/ folder convention the importer reads automatically, because there is no importer. Upload each image at Admin → Media (or POST /api/v1/media with a media:write key) and reference the URL it returns inside your post's content_html. If your images are already hosted elsewhere — GitHub, a CDN, an absolute URL — you can point straight at that URL instead; VeloCMS doesn't require you to re-host it.

After you're in

Review each imported post in Admin → Posts before it goes live: check the formatting rendered correctly, confirm images loaded, and confirm slugs look right. When a batch is ready, select it and use the bulk-publish action in the posts table rather than opening each post individually.

Frequently asked questions

  • Can I import .mdx files (MDX with JSX)? Not directly — MDX's JSX components won't render through either path above. Convert to plain Markdown or HTML first and recreate any interactive components as VeloCMS editor or page-builder blocks.
  • What HTML does the API actually accept? Whatever your Markdown converter outputs. Standard tags — headings, paragraphs, lists, links, images, fenced code blocks — render correctly through content_html; anything unusual is worth checking in a draft before you publish it.
  • Can I fix a post after I've already created it through the API? Yes — PATCH /api/v1/posts/{id} with the same posts:write key updates a post you already created, or you can just open it in Admin → Posts and edit it directly.
  • Can I import from Obsidian this way? Yes — Obsidian's Markdown is standard, so the same 'convert to HTML, then POST' path works. Obsidian's internal [[Page Name]] links don't carry over automatically and need to become regular links.