A lightweight, extensible React PDF annotator and viewer built on top of PDF.js
Supporting the editing of existing PDF file annotations, posting comments, replying, submitting annotation data, and loading for further editing.
English | 简体中文
- ✍️ Rich annotation system
- Highlight, drawing, shapes, text notes
- Signatures (draw / enter / upload)
- Stamps with editor support
- Edit native PDF annotations directly
- 📄 High-fidelity PDF rendering based on PDF.js
- 🎨 Theme system based on Radix UI Themes
- 🌍 Internationalization (zh-CN, en-US)
- 🧩 Highly customizable UI
- Toolbar / Sidebar / Actions fully overridable
- 🏢 Enterprise-friendly configuration
defaultOptionssupports DeepPartial + Deep Merge
- 💾 Export
- Export annotations to PDF
- Export annotations to Excel
- 🧠 Designed for extensibility
- Clean context & extension architecture
- Rectangle
- Circle
- Free Hand (grouped if drawn within a short time)
- Free Highlight (with auto-correction)
- Arrow
- Cloud
- FreeText
- Signature
- Stamp (upload custom images)
- Text Highlight
- Text Strikeout
- Text Underline
- Text
- Square
- Circle
- Ink
- FreeText
- Line
- Polygon
- PolyLine
- Text
- Highlight
- Underline
- StrikeOut
npm install pdfjs-annotation-extension-for-react
or
yarn add pdfjs-annotation-extension-for-reactimport { PdfAnnotator } from 'pdfjs-annotation-extension-for-react'
import 'pdfjs-annotation-extension-for-react/style'
export default function App() {
return (
<PdfAnnotator
title="PDF Annotator"
url="https://example.com/sample.pdf"
user={{ id: 'u1', name: 'Alice' }}
onSave={(annotations) => {
console.log('Saved annotations:', annotations)
}}
/>
)
}import { PdfViewer } from 'pdfjs-annotation-extension-for-react'
import 'pdfjs-annotation-extension-for-react/style'
export default function App() {
return (
<PdfViewer
title="PDF Viewer"
url="https://example.com/sample.pdf"
layoutStyle={{ width: '100vw', height: '100vh' }}
/>
)
}| Name | Type | Default | Description |
|---|---|---|---|
appearance |
auto | dark | light |
auto |
Dark or Light theme. |
theme |
Radix Theme Color | violet |
Theme color of the viewer UI |
title |
React.ReactNode |
— | Page title content; accepts text or custom React nodes |
url * |
string | URL |
— | PDF file URL; supports string URLs or URL objects |
locale |
'zh-CN' | 'en-US' |
zh-CN |
Locale used for internationalization |
initialScale |
PdfScale |
auto |
Initial zoom level of the PDF viewer |
layoutStyle |
React.CSSProperties |
{ width: '100vw', height: '100vh' } |
Styles applied to the PDF viewer container |
isSidebarCollapsed |
boolean |
false |
Whether the sidebar is collapsed by default |
enableRange |
boolean | 'auto' |
auto |
Enables HTTP Range (streaming) loading for PDFs |
An advanced PDF viewer with annotation capabilities.
| Name | Type | Default | Description |
|---|---|---|---|
user |
User |
{ id: 'null', name: 'unknown' } |
Current user information used to identify the annotation author |
enableNativeAnnotations |
boolean |
false |
Native annotations embedded in the PDF file |
defaultShowAnnotationsSidebar |
boolean |
false |
Show Annotations Sidebar |
defaultOptions |
DeepPartial |
— | Default configuration for the annotator; |
initialAnnotations |
IAnnotationStore[] |
— | Existing annotations to be rendered during initialization |
actions |
React.ReactNode | React.ComponentType |
— | Custom actions area |
onSave |
(annotations: IAnnotationStore[]) => void |
— | Callback triggered when annotations are saved |
onLoad |
() => void |
— | Callback triggered when the PDF and annotator are fully loaded |
onAnnotationAdded |
(annotation: IAnnotationStore) => void |
— | Fired when a new annotation is created |
onAnnotationDeleted |
(id: string) => void |
— | Fired when an annotation is deleted |
onAnnotationSelected |
(annotation: IAnnotationStore | null, isClick: boolean) => void |
— | Fired when an annotation is selected or deselected |
onAnnotationUpdated |
(annotation: IAnnotationStore) => void |
— | Fired when an existing annotation is modified |
defaultOptions is not a full config override.
- It is defined as
DeepPartial<PdfAnnotatorOptions> - It will be deep merged with the system default configuration
This ensures:
- You only override what you need
- System defaults remain stable
- Safe for long-term enterprise use
import qiantubifengshouxietiFont from './fonts/qiantubifengshouxieti.ttf';
<PdfAnnotator
url="sample.pdf"
defaultOptions={{
colors: ['#000', '#1677ff'],
signature: {
colors: ['#000000', '#ff0000', '#1677ff'],
type: 'Upload',
maxSize: 1024 * 1024 * 5,
accept: '.png,.jpg,.jpeg,.bmp',
defaultSignature: ['data:image/png;base64,...'],
defaultFont: [
{
label: '楷体',
value: 'STKaiti',
external: false
},
{
label: '千图笔锋手写体',
value: 'qiantubifengshouxieti',
external: true,
url: qiantubifengshouxietiFont
},
{
label: '平方长安体',
value: 'PingFangChangAnTi-2',
external: true,
url: 'http://server/PingFangChangAnTi-2.ttf'
}
]
},
stamp: {
maxSize: 1024 * 1024 * 5,
accept: '.png,.jpg,.jpeg,.bmp',
defaultStamp: ['data:image/png;base64,...'],
editor: {
defaultBackgroundColor: '#2f9e44',
defaultBorderColor: '#2b8a3e',
defaultBorderStyle: 'none',
defaultTextColor: '#fff',
defaultFont: [
{
label: '楷体',
value: 'STKaiti'
}
]
}
}
}}
/><PdfAnnotator
url={pdfUrl}
actions={({ save, exportToPdf, exportToExcel }) => (
<>
<button onClick={save}>Save</button>
<button onClick={() => exportToPdf('annotations')}>
Export PDF
</button>
<button onClick={() => exportToExcel('annotations')}>
Export Excel
</button>
</>
)}
/><PdfAnnotator
url={pdfUrl}
defaultOptions={{
signature: {
defaultSignature: ['data:image/png;base64,...'],
defaultFont: [
{
label: 'Custom Font',
value: 'MyFont',
external: true,
url: '/fonts/myfont.ttf'
}
]
},
stamp: {
defaultStamp: ['data:image/png;base64,...']
}
}}
/>A lightweight PDF viewer with toolbar, sidebar, actions and extensible UI slots.
| Name | Type | Default | Description |
|---|---|---|---|
actions |
React.ReactNode | (context: PdfViewerContextValue) => React.ReactNode |
— | Custom actions area in the toolbar |
sidebar |
SidebarPanel[] |
— | Custom sidebar component |
toolbar |
React.ReactNode | (context: PdfViewerContextValue) => React.ReactNode |
— | Custom toolbar component |
showTextLayer |
boolean |
true |
Whether to render the text layer |
showAnnotations |
boolean |
false |
Whether to render the pdf annotations layer |
defaultActiveSidebarKey |
string |
null | Default Active Sidebar Key |
onDocumentLoaded |
(pdfViewer: PDFViewer | null) => void |
— | Callback invoked when the PDF document is fully loaded and the viewer is initialized |
onEventBusReady |
(eventBus: EventBus | null) => void |
— | Callback invoked when the pdf.js EventBus is ready |
<PdfViewer
url={pdfUrl}
toolbar={(context) => (
<>
<button onClick={() => console.log(context.pdfViewer)}>
PDF Viewer
</button>
<button onClick={context.toggleSidebar}>
Toggle Sidebar
</button>
<button onClick={() => context.setSidebarCollapsed(false)}>
Open Sidebar
</button>
<button onClick={() => context.setSidebarCollapsed(true)}>
Close Sidebar
</button>
</>
)}
/><PdfViewer
url={pdfUrl}
sidebar={[{
key: 'sidebar-1',
title: 'Sidebar 1',
icon: <BsLayoutTextSidebar />,
render: (context) => (
<div style={{ display: 'flex', gap: 10, flexDirection: 'column' }}>
Sidebar 1
<button onClick={context.toggleSidebar}>
toggleSidebar
</button>
<button onClick={() => console.log(context.pdfViewer)}>
Get PDF Viewer
</button>
<button onClick={() => {
context.pdfViewer?.scrollPageIntoView({
pageNumber: 1
})
}}>
goto page1
</button>
<button onClick={() => {
context.pdfViewer?.scrollPageIntoView({
pageNumber: 10
})
}}>
goto page 10
</button>
</div>
)
}]}
/><PdfViewer
url={pdfUrl}
actions={(context) => (
<>
<button onClick={() => console.log(context.pdfViewer)}>
PDF Viewer
</button>
<button onClick={context.toggleSidebar}>
Toggle Sidebar
</button>
<button onClick={() => context.setSidebarCollapsed(false)}>
Open Sidebar
</button>
<button onClick={() => context.setSidebarCollapsed(true)}>
Close Sidebar
</button>
</>
)}
/>- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
MIT