@tobyt/expo-pdf-markup - v0.6.1
    Preparing search index...

    @tobyt/expo-pdf-markup - v0.6.1

    @tobyt/expo-pdf-markup

    npm version npm downloads license TypeScript

    An Expo module for displaying and annotating PDFs, with support for iOS, Android, and Web.

    ๐Ÿ“– API Reference ยท ๐Ÿš€ Example App

    Status: Early development โ€” actively tested in the Choir app.

    Platform PDF rendering Annotations
    iOS โœ… โœ…
    Android โœ… โœ…
    Web โœ… โœ…
    npm install @tobyt/expo-pdf-markup
    
    npx pod-install
    

    Web rendering uses pdfjs-dist. Install it as a dependency:

    npm install pdfjs-dist
    

    Then add the Metro config plugin to your metro.config.js:

    const { getDefaultConfig } = require('expo/metro-config');
    const { withPdfMarkup } = require('@tobyt/expo-pdf-markup/metro');

    const config = getDefaultConfig(__dirname);
    module.exports = withPdfMarkup(config);

    withPdfMarkup does two things automatically:

    1. Patches import.meta in pdfjs-dist so Metro can bundle it (pdfjs-dist v4 uses ESM syntax in a Node.js-only code path that is otherwise unreachable in a browser).
    2. Copies the pdfjs worker to public/pdf.worker.min.mjs in your project root so it is served alongside your app (same-origin, no CORS issues). The default worker URL is ./pdf.worker.min.mjs (relative to the page), so it works whether your app is hosted at the root or a sub-path.

    The public/pdf.worker.min.mjs file is regenerated on each Metro start if missing, so you can add it to .gitignore:

    public/pdf.worker.min.mjs
    

    If you are not using Metro (e.g. Webpack or a custom CDN), set the worker URL before mounting the view:

    import { setPdfJsWorkerSrc } from '@tobyt/expo-pdf-markup';

    setPdfJsWorkerSrc('https://your-cdn.example.com/pdf.worker.min.mjs');

    expo-asset returns a full URL on web (https://โ€ฆ or http://โ€ฆ), not a local path. Pass it directly to source โ€” pdfjs accepts URLs:

    // asset.localUri on web is already a URL; the .replace() is a no-op
    setPdfPath(asset.localUri.replace('file://', ''));

    Full API reference is available at tobyt42.github.io/expo-pdf-markup.

    import { ExpoPdfMarkupView } from '@tobyt/expo-pdf-markup';
    import type { AnnotationMode } from '@tobyt/expo-pdf-markup';
    import { Asset } from 'expo-asset';
    import { useEffect, useState } from 'react';
    import { StyleSheet } from 'react-native';

    export default function App() {
    const [pdfPath, setPdfPath] = useState<string | null>(null);
    const [annotations, setAnnotations] = useState(JSON.stringify({ version: 1, annotations: [] }));

    useEffect(() => {
    async function preparePdf() {
    const asset = Asset.fromModule(require('./assets/document.pdf'));
    await asset.downloadAsync();
    if (asset.localUri) setPdfPath(asset.localUri.replace('file://', ''));
    }
    preparePdf();
    }, []);

    if (!pdfPath) return null;

    return (
    <ExpoPdfMarkupView
    source={pdfPath}
    style={StyleSheet.absoluteFill}
    annotationMode="ink"
    annotationColor="#FF0000"
    annotationLineWidth={3}
    annotations={annotations}
    onLoadComplete={({ nativeEvent: { pageCount } }) => console.log(`Loaded ${pageCount} pages`)}
    onPageChanged={({ nativeEvent: { page, pageCount, pageWidth, pageHeight } }) =>
    console.log(`Page ${page + 1} of ${pageCount} (${pageWidth}ร—${pageHeight}pt)`)
    }
    onAnnotationsChanged={({ nativeEvent }) => setAnnotations(nativeEvent.annotations)}
    onError={({ nativeEvent: { message } }) => console.error(message)}
    />
    );
    }

    If you provide onTextInputRequested, the built-in native prompt is skipped and your callback is used instead. The callback receives a request object with mode, page, point, and currentText for edit flows, so you can prefill your own UI when the user taps an existing text annotation while the text tool is active.

    This module uses the Expo Modules API. The example directory contains a test app.

    # Build the module
    npm run build

    # Run the example app
    cd example
    npx expo start

    Toby Terhoeven

    MIT