DEV Community

Gaurav Singh
Gaurav Singh

Posted on

How to Convert Markdown to PDF in React Native (The Easy Way)

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 SupportTask 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
Enter fullscreen mode Exit fullscreen mode

Note: This library depends on expo-print (for PDF generation) and expo-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
Enter fullscreen mode Exit fullscreen mode

✨ 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 WebView before 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
Enter fullscreen mode Exit fullscreen mode

Bare React Native? Run npx expo install expo-print expo-file-system to 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} />;
}
Enter fullscreen mode Exit fullscreen mode

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}
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

🎨 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)' },
  },
});
Enter fullscreen mode Exit fullscreen mode

You can theme h1h6, 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)
  },
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}
      />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

📊 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
Enter fullscreen mode Exit fullscreen mode

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)