Add Visual Proof to your PDFs with Interactive Overlays.
This guide shows you how to build document viewers that don't just extract data, but show users exactly where that data came from. Transform user trust with coordinate-backed visual proof in just 15 lines of code.
Introduction
This guide shows you how to build document viewers that don't just extract data, but show users exactly where that data came from. Transform user trust with coordinate-backed visual proof in just 15 lines of code.
15-Line Setup
Copy our code and add overlays to any PDF viewer.
Automatic Scaling
No coordinate math required. Works with any container size.
Build Trust
Show users exactly where data came from with interactive highlights.
The Problem: Users Don't Trust 'Black Box' Extraction
You've built a document processing system that extracts data perfectly. But when you show users the results, they ask: "How do I know this is correct?" Without visual proof, users are forced to manually verify every field against the original document.
Traditional document viewers show the PDF and the extracted JSON separately. Users have to hunt through the document to find where "Total: $1,250.00" actually appears. This creates a trust gap that kills adoption and forces expensive manual review processes.
Prerequisites
Before you start, you'll need:
- SDK Installation:
npm i @ninjadoc-ai/sdk@^1.0.7 react-pdf - PDF Worker Setup: Configure PDF.js worker for client-side rendering (see
public/pdf.worker.mjs) - API Key: Get your Ninjadoc API key from the dashboard
- BFF Routes: Set up backend routes to proxy API calls (see below)
- Processed Document: A document that has been processed through the Ninjadoc API
Security Note
Compatibility
BFF Route Setup (Required)
Before using the SDK, set up these backend routes in your application:
BFF Route Configuration
// app/routes/api.ninjadoc.$.tsx
import { createRemixApiRoute } from '@ninjadoc-ai/sdk/frameworks/remix';
const { loader, action } = createRemixApiRoute({
// API key automatically read from NINJADOC_API_KEY env var
});
export { loader, action };
// Add to app/routes.ts:
// route("/api/ninjadoc/*", "routes/api.ninjadoc.$.tsx")This creates secure proxy routes that the SDK calls from your browser, keeping your API key safe on the server.
Quick Start: The 15-Line Interactive Overlay
Instead of building complex coordinate systems, our SDK handles all the math. You get automatic scaling, multi-page support, and interactive highlights that work with any PDF viewer. Just pass in your processed document and container size.
Minimal Interactive Viewer
// The simplest possible overlay implementation - just 15 lines!
import { createBrowserClient } from '@ninjadoc-ai/sdk';
import { HighlightOverlay } from '@ninjadoc-ai/sdk/react';
import { useState, useEffect } from 'react';
function DocumentViewer({ jobId, pdfFile }) {
const [document, setDocument] = useState(null);
const client = createBrowserClient({ baseUrl: window.location.origin });
useEffect(() => {
// Single method returns everything: regions + page dimensions + job status
client.processDocument(jobId).then(setDocument);
}, [jobId]);
if (!document) return <div>Loading...</div>;
return (
<div style={{ position: 'relative' }}>
{/* Your PDF viewer component goes here */}
<PDFViewer file={pdfFile} width={800} height={600} />
{/* Automatic coordinate scaling - no math required! */}
<HighlightOverlay
document={document}
containerSize={{ width: 800, height: 600 }}
onRegionClick={(region) => {
alert(`Found: ${region.metadata.extractedText}`);
}}
/>
</div>
);
}Production Implementation: Multi-Page with Polling
This production-ready component handles the complete workflow: document processing with polling, multi-page navigation, error handling, and interactive region selection. The overlay automatically scales coordinates to match your PDF viewer dimensions.
Container Sizing Tips
- • Use a container ref to measure actual rendered dimensions
- • Pass those measurements to HighlightOverlay's containerSize prop
- • Recompute on window resize for responsive accuracy
- • Account for padding/margins in your container calculations
Production Document Viewer
// Production-ready implementation with polling, error handling, and multi-page support
import { createBrowserClient } from '@ninjadoc-ai/sdk';
import { HighlightOverlay } from '@ninjadoc-ai/sdk/react';
import { Document, Page } from 'react-pdf';
import { useState, useEffect } from 'react';
// Custom hook for document processing with polling (BFF-only, no secrets)
function useDocumentProcessing(jobId) {
const [document, setDocument] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (!jobId) return;
const client = createBrowserClient({ baseUrl: window.location.origin });
const pollForCompletion = async () => {
try {
let status;
do {
status = await client.getJobStatus(jobId);
if (status.status === 'completed') {
const processedDoc = await client.processDocument(jobId);
setDocument(processedDoc);
setLoading(false);
return;
} else if (status.status === 'failed') {
throw new Error('Processing failed');
}
await new Promise(resolve => setTimeout(resolve, 2000));
} while (['queued', 'processing'].includes(status.status));
} catch (err) {
setError(err.message);
setLoading(false);
}
};
pollForCompletion();
}, [jobId]);
return { document, loading, error };
}
// Production component with multi-page support
function ProductionDocumentViewer({ jobId, pdfFile, apiKey }) {
const { document, loading, error } = useDocumentProcessing(jobId, apiKey);
const [currentPage, setCurrentPage] = useState(1);
const [numPages, setNumPages] = useState(null);
const [pageSize, setPageSize] = useState({ width: 0, height: 0 });
const [activeRegion, setActiveRegion] = useState(null);
if (loading) return <div className="text-center p-8">Processing document...</div>;
if (error) return <div className="text-red-600 p-8">Error: {error}</div>;
if (!document) return null;
// Filter regions for current page
const currentPageRegions = document.regions.filter(region => region.page === currentPage);
return (
<div className="space-y-4">
{/* Page Navigation */}
{numPages > 1 && (
<div className="flex items-center justify-center gap-4">
<button
onClick={() => setCurrentPage(p => Math.max(p - 1, 1))}
disabled={currentPage <= 1}
className="px-4 py-2 bg-blue-600 text-white disabled:bg-gray-400"
>
Previous
</button>
<span>Page {currentPage} of {numPages}</span>
<button
onClick={() => setCurrentPage(p => Math.min(p + 1, numPages))}
disabled={currentPage >= numPages}
className="px-4 py-2 bg-blue-600 text-white disabled:bg-gray-400"
>
Next
</button>
</div>
)}
{/* Document Viewer with Overlays */}
<div className="relative border overflow-hidden">
<Document
file={pdfFile}
onLoadSuccess={({ numPages }) => setNumPages(numPages)}
>
<div className="relative">
<Page
pageNumber={currentPage}
width={800}
onRenderSuccess={(page) => setPageSize({ width: page.width, height: page.height })}
/>
{/* The magic happens here - automatic coordinate scaling! */}
<HighlightOverlay
document={{
...document,
regions: currentPageRegions // Only show current page regions
}}
containerSize={pageSize}
onRegionClick={(region) => {
setActiveRegion(region.id);
// Build your verification UI here
console.log('Clicked region:', region.label, region.metadata.extractedText);
}}
activeRegionId={activeRegion}
/>
</div>
</Document>
</div>
{/* Region Details Panel */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{currentPageRegions.map((region) => (
<div
key={region.id}
className={`border p-4 cursor-pointer transition-colors ${
activeRegion === region.id ? 'border-blue-500 bg-blue-50' : 'border-gray-200'
}`}
onClick={() => setActiveRegion(region.id)}
>
<h4 className="font-medium">{region.label}</h4>
<p className="text-sm text-gray-600">
Confidence: {(region.confidence * 100).toFixed(1)}%
</p>
<p className="text-sm bg-gray-50 p-2 mt-2">
{region.metadata.extractedText}
</p>
</div>
))}
</div>
</div>
);
}Styling & Theming
Customize the overlay appearance to match your application's design system:
Overlay Customization
<HighlightOverlay
document={document}
containerSize={pageSize}
className="custom-overlay"
style={{
'--highlight-color': '#3b82f6',
'--highlight-opacity': '0.3',
'--border-width': '2px'
}}
onRegionClick={(region) => {
// Handle click with custom styling
}}
/>For advanced customization, you can also build your own overlay component using the coordinate data from the ProcessedDocument response.
Frequently Asked Questions
How does automatic coordinate scaling work?
The SDK extracts the original PDF page dimensions during processing and stores them with your results. The HighlightOverlay component automatically calculates scale factors based on your container size, so coordinates always align perfectly regardless of zoom level or container dimensions.
Can I customize the overlay appearance?
Yes! The HighlightOverlay component accepts custom styling through CSS classes and inline styles. You can change colors, borders, opacity, and hover effects. For advanced customization, you can also build your own overlay component using the coordinate data from the ProcessedDocument.
How do I handle multi-page documents?
Each region includes a page number. Filter the regions array by the current page before passing to HighlightOverlay. The SDK automatically handles different page dimensions, so each page scales correctly even if they have different sizes.
What PDF viewers work with this?
Any PDF viewer that gives you control over container dimensions works. We've tested with react-pdf, PDF.js, and custom canvas implementations. The key is knowing your viewer's rendered dimensions to pass as containerSize.
Further reading
- →Invoice Processing API Guide: Extract invoice data with coordinate proof
- →Bill of Lading API Guide: Extract logistics data with coordinate proof
- →Ninjadoc vs Docsumo: developer-first Q&A vs enterprise workflows
- →Ninjadoc vs AWS Textract: developer-first Q&A vs foundation-model extraction
- →Complete API Documentation: SDK reference and examples
Ready to Build Trust with Visual Proof?
Add interactive overlays to your document viewers and transform user trust with coordinate-backed visual verification.
- No Credit Card Required
- Interactive PDF Overlays