Need to convert Markdown to PDF in your React Native or Expo app? You've probably hit the same wall — no clean solution exists. The usual approach means hand-writing HTML strings, fighting with WebViews, manually applying styles, and after all that effort still ending up with a bland, unstyled PDF.
There's a better way.
react-native-md-to-pdf is an open-source library that converts Markdown directly into a fully styled, properly formatted PDF on both iOS and Android. You write Markdown. The library handles everything else — parsing, HTML generation, theming, fonts, and PDF output — powered by expo-print under the hood.
react-native-md-to-pdf
A powerful, customizable library to convert Markdown to PDF in React Native.
🚀 Zero-dependency markdown parser 🎨 Granular Theming (CSS-like) 🔤 Custom Fonts Support ✅ Task List / Checkbox rendering 📱 iOS & Android (with Expo Go & Bare workflow support) ⚛️ Works with Expo
Installation
npm install react-native-md-to-pdf expo-print expo-file-system
# or
yarn add react-native-md-to-pdf expo-print expo-file-system
Note: This library depends on
expo-print(for PDF generation) andexpo-file-system(for saving files). If you are in a bare React Native project, ensure you install these modules correctly.
Usage
Simple Example
import { useMdToPdf } from 'react-native-md-to-pdf';
export default function App() {
const { convertToPdf } = useMdToPdf();
const handleGenerate = async () => {
const result = await convertToPdf('# Hello World', {
fileName: 'my-doc',
pageSize: 'A4',
});
console.log('PDF…✨ Why use it?
- 🚀 Zero-dependency Markdown parser — no heavy external parsers
- 🎨 Granular CSS-like theming — style every element:
h1,code,blockquote,table,img - 🔤 Custom Fonts — inject any TTF/OTF font via base64 (great for Google Fonts)
- ✅ Task lists —
- [x]renders as styled checkboxes - 🔍 HTML Preview — get the full HTML back to render in a
WebViewbefore saving - 📱 iOS & Android — works with Expo Go and bare workflow
📦 Installation
npm install react-native-md-to-pdf expo-print expo-file-system
# or
yarn add react-native-md-to-pdf expo-print expo-file-system
Bare React Native? Run
npx expo install expo-print expo-file-systemto correctly link the native modules.
🚀 Basic Usage — PDF in 3 Lines
The library exposes a useMdToPdf hook. Call convertToPdf and you're done.
import { useMdToPdf } from 'react-native-md-to-pdf';
export default function App() {
const { convertToPdf } = useMdToPdf();
const handleGenerate = async () => {
const result = await convertToPdf('# Hello World\n\nThis is my first PDF!', {
fileName: 'my-document',
pageSize: 'A4',
});
console.log('PDF saved to:', result.filePath);
console.log('Total pages:', result.pageCount);
};
return <Button title="Generate PDF" onPress={handleGenerate} />;
}
The PDF is saved to the cache directory and you get back the absolute filePath and pageCount.
🔍 Feature 1: HTML Preview in a WebView
Before saving a PDF, it's a great UX pattern to first show a live preview. convertToHtml returns a complete HTML5 document you plug directly into a WebView.
The key benefit: the preview uses the exact same page size, margins, and theme as the final PDF — what users see is what they get.
import { useMdToPdf } from 'react-native-md-to-pdf';
import { WebView } from 'react-native-webview';
export default function PreviewScreen() {
const { convertToHtml } = useMdToPdf();
const [html, setHtml] = useState('');
const handlePreview = () => {
const output = convertToHtml('# My Report\n\nSome **content** here.', {
pageSize: 'A4',
});
setHtml(output);
};
return (
<>
<Button title="Preview" onPress={handlePreview} />
{html ? <WebView source={{ html }} style={{ flex: 1 }} /> : null}
</>
);
}
🎨 Feature 2: Deep Theming — Style Every Element
This is where react-native-md-to-pdf really shines. The theme option gives you granular, CSS-like control over every Markdown element individually.
const result = await convertToPdf(markdown, {
pageSize: 'A4',
theme: {
fontFamily: 'Open Sans',
background: '#ffffff',
colors: {
text: '#111827',
heading: '#dc2626',
link: '#2563eb',
code: '#38bdf8',
codeBackground: '#1f2937',
},
// Per-element styles — CSS properties as camelCase
h1: { fontSize: '32px', borderBottom: '2px solid #dc2626', paddingBottom: '10px' },
h2: { color: '#4b5563', borderBottom: '1px solid #e5e7eb' },
code: { backgroundColor: '#1f2937', color: '#38bdf8', borderRadius: '6px' },
blockquote: { borderLeft: '4px solid #dc2626', paddingLeft: '12px', color: '#6b7280' },
img: { borderRadius: '8px', boxShadow: '0 4px 6px rgba(0,0,0,0.1)' },
},
});
You can theme h1–h6, p, a, img, table, blockquote, code, and pre individually.
🔤 Feature 3: Custom Fonts
Want your PDF to use a professional font? Pass it as base64-encoded TTF/OTF data. The library accepts both a full data URI or raw base64 — whichever is easier for you.
const result = await convertToPdf(markdown, {
pageSize: 'A4',
theme: {
fontFamily: 'Open Sans', // Must match the key in `fonts`
},
fonts: {
'Open Sans': 'data:font/ttf;base64,AAEC...', // Full data URI
'Fira Code': 'AAEC...', // Raw base64 (library auto-wraps it)
},
});
Google Fonts tip: Download the TTF from fonts.google.com, convert to base64 with any online tool, and pass it in. Your PDF will embed the font natively.
✅ Feature 4: Task Lists
The library fully supports GitHub Flavored Markdown task syntax — rendered as styled, display-only checkboxes. Perfect for status reports and checklists.
## Project Status
- [x] Set up project
- [x] Add Markdown parser
- [x] Implement theming
- [ ] Write tests
- [ ] Publish to npm
No extra config needed — it just works.
⚛️ Feature 5: <MdToPdfView> Component
For more complex flows — like a live Markdown editor with a preview + one-tap PDF export — use the MdToPdfView component with an imperative ref.
It uses a render prop pattern: your children function receives the HTML string so you control how it's rendered in the WebView.
import { MdToPdfView, type MdToPdfViewRef } from 'react-native-md-to-pdf';
import { WebView } from 'react-native-webview';
export default function EditorScreen() {
const ref = useRef<MdToPdfViewRef>(null);
const [loading, setLoading] = useState(false);
return (
<>
<MdToPdfView
ref={ref}
markdown="# My Document\n\nStart typing your content here."
pdfOptions={{ pageSize: 'A4', fileName: 'my-export' }}
onGenerateStart={() => setLoading(true)}
onGenerateComplete={(result) => {
setLoading(false);
console.log('Saved to:', result.filePath);
}}
onGenerateError={(err) => {
setLoading(false);
console.error(err.code, err.message);
}}
>
{/* Render prop receives the full HTML5 document string */}
{(html) => <WebView source={{ html }} style={{ flex: 1 }} />}
</MdToPdfView>
<Button
title={loading ? 'Generating...' : 'Save as PDF'}
onPress={() => ref.current?.generatePdf()}
disabled={loading}
/>
</>
);
}
You can also query the ref programmatically at any time:
const html = ref.current?.getHtml(); // Get the current HTML string
const busy = ref.current?.isConverting; // Check if export is in progress
📊 Full PdfOptions Reference
| Option | Type | Default | Description |
|---|---|---|---|
fileName |
string |
timestamp | Output file name (no extension) |
pageSize |
'A4', 'Letter', etc. |
'A4' |
Standard page size |
orientation |
'portrait' \ |
'landscape' |
'portrait' |
margins |
{ top, bottom, left, right } |
20mm all |
Page margins e.g. { top: '15mm' }
|
directory |
string |
cache dir | Where to save the PDF |
base64 |
boolean |
false |
Include base64 data in the result |
theme |
ThemeConfig |
default | Full styling configuration |
fonts |
Record<string, string> |
— | Font name → base64 data map |
🩺 Troubleshooting
expo-print or expo-file-system not found
npx expo install expo-print expo-file-system
PDF not saving on Android
Make sure directory points to a writable path, e.g. FileSystem.documentDirectory.
Custom font not rendering in the PDF
Ensure you pass the font name to theme.fontFamily too: theme: { fontFamily: 'Open Sans', ... } and that the base64 string is a valid TTF/OTF/WOFF file.
GENERATION_FAILED error
Usually means expo-print hit an issue. Check your HTML is valid and the device has sufficient memory for the page count.
🔗 Find it on npm
If this saves you time, a ⭐ on GitHub is hugely appreciated — it helps other developers find the library. Feedback, issues, and PRs are always welcome!
Happy coding! 🚀
Top comments (0)