How to Generate Multiple Markdown Presentations with Marp and JavaScript
Learn how to generate multiple Marp presentations from multiple Markdown files at once using marp-cli, glob, and a custom Node.js build script—just like a static site generator.

I freaking love Markdown. I write almost everything in Markdown.
I write this blog post in Markdown. I chat with LLMs in Markdown. I write docs in Markdown. Everything is Markdown.
One time, I was invited as a guest speaker, and I was thinking, can I write a keynote presentation in Markdown? The answer is yes. That’s when I found out about Marp.
Now, I won’t go into the details of how to create a Markdown presentation with Marp. If you’re very familiar with Markdown, the official docs are actually pretty easy to follow.
What I want to write about today is how to generate multiple slides from multiple Markdown files in one go.
You see, Marp is designed to generate one slide from one Markdown file at a time. So if I have a special repository to store all my Markdown presentations, I need to generate the slides one by one.
What I want is something similar to most static site generators like Jekyll or Hugo, where I have multiple Markdown files and it generates a ready-to-use website where every Markdown file is converted to HTML.
See what I mean? So, here’s how I did it.
Generate Multiple Slides from Multiple Markdown Files with Marp
Let’s get started by creating our directory.
mkdir -p slides
cd slides
Now, because we use Markdown files for the presentations, why not use git too:
git init
Then let’s initialize with npm:
npm init
Let’s add our dev dependencies. We just need marp-cli and glob for now.
npm install --save-dev @marp-team/marp-cli glob
Once the packages are installed, let’s update the package.json by changing the type to module and adding a scripts section:
{
/* ... */
"type": "module",
"scripts": {
"publish": "node build_marp_slides.js",
"serve": "npx http-server dist/"
},
/* ... */
}
Now before we create our script, let’s structure our Markdown files.
Create a new directory named content and put all your Markdown files there.
Here’s the structure from my GitHub Marp demo repo.
.
├── build_marp_slides.js
├── content
│ ├── basic
│ │ └── intro.md
│ ├── index.md
│ ├── marp.md
│ ├── oop.md
│ └── portfolio.md
├── package-lock.json
└── package.json
I recommend creating one index.md file at the root of content/ and linking all your presentations there.
Now, for the important part, create a new JavaScript file named build_marp_slides.js. This script will work like this:
- Scan the
contentdirectory recursively - For each Markdown file found, generate the slide to HTML using Marp CLI into the
dist/directory - The
dist/directory structure will follow thecontentdirectory structure
And so, here’s the script. Just copy and paste:
#!/usr/bin/env node
import { marpCli } from '@marp-team/marp-cli';
import { glob } from 'glob';
import { existsSync, rmSync, mkdirSync, readdirSync, renameSync } from 'fs';
import { join, relative, dirname, basename } from 'path';
const SOURCE_DIR = './content';
const OUTPUT_DIR = './dist';
async function buildSlides() {
// Clean and prepare output
console.log('🧹 Cleaning output directory...');
if (existsSync(OUTPUT_DIR)) {
rmSync(OUTPUT_DIR, { recursive: true, force: true });
}
mkdirSync(OUTPUT_DIR, { recursive: true });
console.log(`🎞️ Generating all slides from '${SOURCE_DIR}'...`);
// Find all markdown files
const markdownFiles = await glob(`${SOURCE_DIR}/**/*.md`);
// Generate all HTML slides in-place under content/
try {
const exitCode = await marpCli(markdownFiles);
if (exitCode !== 0) {
throw new Error(`Marp CLI exited with code ${exitCode}`);
}
} catch (error) {
console.error('❌ Error generating slides:', error.message);
process.exit(1);
}
// Find all generated .html files
function findHtmlFiles(dir) {
const files = [];
const items = readdirSync(dir, { withFileTypes: true });
for (const item of items) {
const fullPath = join(dir, item.name);
if (item.isDirectory()) {
files.push(...findHtmlFiles(fullPath));
} else if (item.isFile() && item.name.endsWith('.html')) {
files.push(fullPath);
}
}
return files;
}
const htmlFiles = findHtmlFiles(SOURCE_DIR);
// Move generated .html files into dist/
for (const htmlFile of htmlFiles) {
// Get relative path from SOURCE_DIR
const relPath = relative(SOURCE_DIR, htmlFile);
const dir = dirname(relPath);
const base = basename(relPath);
const name = basename(base, '.html');
// Normalize dir: if it's "." then make it empty
const normalizedDir = dir === '.' ? '' : dir;
let outDir;
if (name === 'index') {
// Keep index in the same directory inside dist
outDir = normalizedDir ? join(OUTPUT_DIR, normalizedDir) : OUTPUT_DIR;
} else {
// Create directory dist/<dir>/<name>/ and move there as index.html
outDir = normalizedDir
? join(OUTPUT_DIR, normalizedDir, name)
: join(OUTPUT_DIR, name);
}
mkdirSync(outDir, { recursive: true });
const destPath = join(outDir, 'index.html');
console.log(`🧩 Moving: ${htmlFile} → ${destPath}`);
renameSync(htmlFile, destPath);
}
console.log(`✅ All slides built successfully in '${OUTPUT_DIR}'`);
}
buildSlides().catch(error => {
console.error('❌ Build failed:', error);
process.exit(1);
});
Once the script and Markdown files are ready, you can build your presentations using this command:
npm run publish
A new dist/ directory will be generated containing all your presentations in HTML.
You can view them in your browser—just start the HTTP server with:
npm run serve
Here’s a demo I created and published to GitHub Pages.
Conclusion
That’s it for today.
If you have a Git repository with all of your Markdown presentation files, you can now build them all in a single go, just like a static site generator.
Now you can focus on writing and worry less about building and distributing.
As usual, if you have any questions or a better method, leave a comment below. Thanks for reading, and see you next time!