Configure callback functions to respond to editor events and customize file operations.
Monitor loading state changes to show/hide loading indicators.
Type signature:
onLoadingStateChange?: (isLoading: boolean) => void
Example:
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onLoadingStateChange: (isLoading: boolean) => {
const loader = document.getElementById('loading-spinner');
if (loader) {
loader.style.display = isLoading ? 'block' : 'none';
}
},
});
Respond to configuration changes. Triggered when layout, theme, language, branding, default font, or units & guides settings change. See index.IConfigChangeData.
Type signature:
onConfigChange?: (config: IConfigChangeData) => void
IConfigChangeData interface:
interface IConfigChangeData {
themeConfig?: IThemeConfig;
defaultFont?: IDefaultFontConfig;
unitsAndGuides?: IUnitsAndGuidesConfig;
languageConfig?: ILanguageConfig;
brandingConfig?: IBrandingConfig;
}
Example:
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onConfigChange: (config) => {
console.log('Configuration changed:', config);
// Save user preferences to localStorage
localStorage.setItem('pdfUserPreferences', JSON.stringify(config));
// Or sync to backend
await fetch('/api/user/preferences', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config),
});
},
});
Handle errors that occur during PDF operations. This callback receives standard JavaScript Error objects.
Type signature:
onError?: (error: Error) => void
Example:
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onError: (error: Error) => {
console.error('PDF Editor Error:', error);
// Show user-friendly error message
showNotification({
type: 'error',
title: 'PDF Error',
message: error.message,
});
},
});
Integrate with error tracking services like Sentry, LogRocket, or custom logging:
import { PdfEditor } from '@avanquest/pdf-web-viewer';
import * as Sentry from '@sentry/browser';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onError: (error: Error) => {
// Log to Sentry
Sentry.captureException(error, {
tags: {
component: 'pdf-editor',
action: 'pdf-operation',
},
extra: {
timestamp: new Date().toISOString(),
userId: currentUser?.id,
},
});
// Display user-friendly message
showToast({
type: 'error',
message: 'An error occurred while processing the PDF. Please try again.',
});
},
});
Handle password-protected files with a custom UI. The callback receives the worker error details and a setPassword helper. You can return a password immediately or call setPassword later (e.g., after a modal submit) to resolve the pending request.
Type signature:
onPasswordRequired?: IPasswordRequiredCallback
Exports:
import type { IPasswordRequiredCallback, ISetPasswordFn, IPasswordRequiredError } from '@avanquest/pdf-web-viewer';
See also: the "Password Callback Types" section in Configuration Options for the full type shapes.
Example: Custom password modal with error message
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onPasswordRequired: async (error: IPasswordRequiredError, setPassword: ISetPasswordFn) => {
// Show your modal with error.message or error.data.message
const password = await showPasswordModal({
title: 'Password required',
message: error.message,
});
// Option A: return password directly
if (password) return password;
// Option B: resolve later via service (leave Promise pending)
// setPassword(password);
},
});
When onPasswordRequired is provided, the built-in password dialog is suppressed. Returning an empty string or rejecting the promise cancels the attempt; to retry, call setPassword or editor.ui.pdfWebService.setDocumentPassword with a new password.
Replace the default file picker with a custom implementation. When provided, this callback is invoked instead of the default file input behavior — from every open entry point, including "Open document" in a side-by-side pane: the file you then pass to openDocument({ file }) lands in the pane the gesture pointed at.
Type signature:
onOpenFile?: () => Promise<void>
Example: Custom File Picker
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onOpenFile: async () => {
// Show custom file picker
const file = await customFilePicker({
accept: '.pdf',
multiple: false,
});
if (file) {
// Open the selected file using the service
await editor.ui.pdfWebService.openDocument({ file });
}
},
});
Example: Cloud Storage Integration
Integrate with Google Drive, Dropbox, OneDrive, or other cloud storage:
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onOpenFile: async () => {
try {
// Show cloud file picker (e.g., Google Drive Picker)
const fileUrl = await googleDrivePicker.pick({
mimeTypes: ['application/pdf'],
});
if (fileUrl) {
// Fetch file from cloud storage
const response = await fetch(fileUrl, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
const blob = await response.blob();
const file = new File([blob], 'document.pdf', { type: 'application/pdf' });
// Open the file
await editor.ui.pdfWebService.openDocument({ file });
}
} catch (error) {
console.error('Failed to open file from cloud:', error);
showToast({ type: 'error', message: 'Failed to open file from cloud storage' });
}
},
});
Which file formats the open UI offers. The viewer reads these itself, and they are always offered:
| Extension | accept sent to the picker |
|---|---|
pdf |
application/pdf |
wwf |
.wwf |
png |
image/png |
tiff / tif |
image/tiff / image/tif |
jpeg / jpg |
image/jpeg / image/jpg |
bmp |
image/bmp |
gif |
image/gif |
txt |
text/plain, .txt |
Anything else is greyed out: the file picker filters it, and a cloud-drive listing shows it
but refuses the click. openFormatsConfig.additional widens that set — for formats your
code opens.
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
openFormatsConfig: {
additional: [
{ ext: 'docx', accept: ['.docx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'] },
{ ext: 'odt' }, // accept defaults to ['.odt']
],
},
// …and convert them before the viewer sees them.
onOpenFile: async () => {
const file = await myPicker();
const pdf = /\.(docx|odt)$/i.test(file.name) ? await myConverter.toPdf(file) : file;
await editor.ui.pdfWebService.openDocument({ file: pdf });
},
});
Type signature:
openFormatsConfig?: {
additional?: Array<{ ext: string; accept?: string[] }>;
};
ext has no leading dot and matches case-insensitively; accept takes MIME types, .ext
tokens, or both, exactly as <input type="file" accept> does, and defaults to ['.<ext>'].
An entry without a usable ext is skipped with a console error rather than corrupting the
picker's filter. The list is replaced on each config change, so dropping it drops the formats.
Declaring a format only makes it selectable — it does not teach the viewer to read it.
Convert the file in onOpenFile (or in your provider's getFileAsFile) before handing it
over. One that reaches the viewer's own processing unconverted is rejected like any
unreadable file, on the standard open-failure notification:
Failed to open file "report.docx": File format is not supported
Customize how files leave the viewer. The callback receives a File object with the filename already set.
This is the single exit point for every export — the document itself (Download/Save, Ctrl+S), converted files, extracted images, split/compressed results, attachments, form data and snapshots all flow through it.
Type signature:
onExportFile?: (file: File) => void
Tip: one-way on purpose — it delivers a copy and reports nothing back, so it is not a save channel. Writing the document to the machine is
deviceConfig.saveOver/saveInto; the export is only what happens when neither is implemented. See Saving & Downloading.
Example: Custom Download with Timestamp
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onExportFile: (file: File) => {
// Add timestamp to filename
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const newFilename = `${file.name.replace('.pdf', '')}-${timestamp}.pdf`;
// Create download link
const url = URL.createObjectURL(file);
const a = document.createElement('a');
a.href = url;
a.download = newFilename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
},
});
Example: Cloud Storage Upload
Save files to cloud storage instead of downloading to the user's device:
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onExportFile: async (file: File) => {
try {
// Show upload progress
showToast({ type: 'info', message: 'Uploading to cloud...' });
// Upload to cloud storage
const formData = new FormData();
formData.append('file', file);
formData.append('userId', currentUser.id);
const response = await fetch('/api/documents/upload', {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
},
body: formData,
});
if (!response.ok) {
throw new Error('Upload failed');
}
const { url, documentId } = await response.json();
// Show success message
showToast({
type: 'success',
message: `File saved to cloud`,
action: {
label: 'View',
onClick: () => window.open(url, '_blank'),
},
});
// Update UI or navigate
console.log('Document ID:', documentId);
} catch (error) {
console.error('Upload failed:', error);
showToast({ type: 'error', message: 'Failed to upload file to cloud' });
}
},
});
Customize the print behavior. The callback receives the PDF file and an optional flag indicating whether to open in a new tab.
Type signature:
onPrint?: (file: File, newTab?: boolean) => void
Example: Custom Print Handler
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onPrint: (file: File, newTab?: boolean) => {
const printUrl = URL.createObjectURL(file);
if (newTab) {
// Open in new tab for printing
window.open(printUrl, '_blank');
} else {
// Print directly using hidden iframe
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.style.position = 'absolute';
iframe.src = printUrl;
document.body.appendChild(iframe);
iframe.onload = () => {
iframe.contentWindow?.print();
// Clean up after printing
setTimeout(() => {
document.body.removeChild(iframe);
URL.revokeObjectURL(printUrl);
}, 1000);
};
}
},
});
Example: Print with Analytics
Track print operations:
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onPrint: (file: File, newTab?: boolean) => {
// Track print event
analytics.track('pdf_print', {
fileName: file.name,
fileSize: file.size,
printMode: newTab ? 'new-tab' : 'direct',
timestamp: Date.now(),
});
// Perform print
const printUrl = URL.createObjectURL(file);
if (newTab) {
window.open(printUrl, '_blank');
} else {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = printUrl;
document.body.appendChild(iframe);
iframe.onload = () => {
iframe.contentWindow?.print();
setTimeout(() => {
document.body.removeChild(iframe);
URL.revokeObjectURL(printUrl);
}, 1000);
};
}
},
});
Handle snapshot area captures with custom logic. When this callback is provided, the default snapshot preview dialog is bypassed, and the callback receives a Blob containing the PNG image data.
Type signature:
onSnapshotArea?: (blob: Blob) => void
Example: Automatic Download
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onSnapshotArea: (blob: Blob) => {
// Automatically download the snapshot
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `snapshot-${Date.now()}.png`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast({
type: 'success',
message: 'Snapshot downloaded successfully',
});
},
});
Example: Upload to Server
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onSnapshotArea: async (blob: Blob) => {
try {
console.log('Snapshot captured:', blob.size, 'bytes');
// Upload to backend
const formData = new FormData();
formData.append('snapshot', blob, `snapshot-${Date.now()}.png`);
formData.append('userId', currentUser.id);
const response = await fetch('/api/snapshots/upload', {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
},
body: formData,
});
if (!response.ok) {
throw new Error('Upload failed');
}
const { snapshotId, url } = await response.json();
showToast({
type: 'success',
message: 'Snapshot uploaded successfully',
action: {
label: 'View',
onClick: () => window.open(url, '_blank'),
},
});
console.log('Snapshot ID:', snapshotId);
} catch (error) {
console.error('Failed to upload snapshot:', error);
showToast({
type: 'error',
message: 'Failed to upload snapshot',
});
}
},
});
Example: Copy to Clipboard
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onSnapshotArea: async (blob: Blob) => {
try {
// Copy image to clipboard
await navigator.clipboard.write([
new ClipboardItem({
'image/png': blob,
}),
]);
showToast({
type: 'success',
message: 'Snapshot copied to clipboard',
});
} catch (error) {
console.error('Failed to copy snapshot:', error);
showToast({
type: 'error',
message: 'Failed to copy snapshot to clipboard',
});
}
},
});
Behavior:
Override the built-in document context menu. When the user right-clicks (or long-presses) within the document, the callback receives the context menu type and the mouse position, so the host application can show its own menu or fall back to the default one.
Type signature:
onContextMenuRequested?: (data: IContextMenuRequestData) => boolean | Promise<boolean>
interface IContextMenuRequestData {
/** Which context menu the viewer would show (page background, selected object, text editor, …). */
menuType: ContextMenu;
/** Click position relative to the document render element. */
position: { x: number; y: number };
/** Bitmask of ContextMenuOperations flags allowed for the current selection. */
operations: ContextMenuOperations;
}
enum ContextMenuOperations {
AllowCopy = 1 << 0,
AllowPaste = 1 << 1,
AllowCut = 1 << 2,
AllowUndo = 1 << 3,
AllowRedo = 1 << 4,
AllowToBack = 1 << 5,
AllowToFront = 1 << 6,
AllowMergeTextBlocks = 1 << 8,
ESignFieldRequired = 1 << 9,
}
The same mask drives the enabled/disabled state of the built-in menu items — test flags with a bitwise AND to decide which items of a custom menu to enable.
Example: Custom Context Menu
import { PdfEditor, ContextMenu, ContextMenuOperations, IContextMenuRequestData } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onContextMenuRequested: (data: IContextMenuRequestData) => {
// Only override the plain page menu; keep the default for everything else
if (data.menuType !== ContextMenu.View) {
return false;
}
showMyContextMenu({
x: data.position.x,
y: data.position.y,
items: [
{ label: 'My Action', onClick: () => doSomething() },
{ label: 'Paste', disabled: (data.operations & ContextMenuOperations.AllowPaste) === 0, onClick: () => pasteHere(data.position) },
{ label: 'Add text', onClick: () => addTextHere(data.position) },
],
});
return true; // handled — the built-in menu is suppressed
},
});
Behavior:
true: The built-in menu is suppressed — the host application is expected to show its own UIfalse: The viewer falls back to the built-in context menudata.position is the click position relative to the document render element.Executing menu actions from a custom menu:
Items of the built-in menu map to DocumentCommands executed on the document view. From a custom menu, run them via doCommand, passing data.position back unchanged for position-based commands:
const view = editor.ui.pdfWebService.getPrimaryDocumentViewElement()?.documentView;
view?.doCommand(DocumentCommand.COPY);
view?.doCommand(DocumentCommand.PASTE, data.position); // paste at the click point
view?.doCommand(DocumentCommand.TYPE_TEXT, data.position); // start the add-text tool at the click point
view?.doCommand(DocumentCommand.INSERT_IMAGE, data.position); // open the image picker, insert at the click point
await view?.print();
Replace built-in dialogs with custom implementations for specific dialog types.
Type signature:
dialogs?: CustomDialogs
type CustomDialogs = Array<ICustomDialog> | null;
interface ICustomDialog {
type: EDialogWorkerType | EDialogDocumentType;
callback: DialogCallback;
}
type DialogCallback = (
event: TDialogTemplateConfig,
close?: () => void,
submit?: (value: string) => void
) => Promise<void>;
Example: Custom Dialog Implementation
import { PdfEditor, EDialogDocumentType } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
dialogs: [
{
type: EDialogDocumentType.SAVE_CHANGES,
callback: async (event, close, submit) => {
// Show custom confirmation dialog
const result = await showCustomDialog({
title: 'Save Changes?',
message: 'Do you want to save your changes before closing?',
buttons: [
{ label: 'Save', value: 'save', primary: true },
{ label: "Don't Save", value: 'discard' },
{ label: 'Cancel', value: 'cancel' },
],
});
if (result === 'save') {
submit?.('save');
} else if (result === 'discard') {
close?.();
}
// Cancel does nothing (keeps dialog open)
},
},
],
});
Note: The dialogs feature allows fine-grained control over specific internal dialogs. For most use cases, the file operation callbacks (onOpenFile, onExportFile, onPrint) provide simpler customization options.
Comprehensive example showing all callbacks working together:
import { PdfEditor, IConfigChangeData } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
// State monitoring
onLoadingStateChange: (isLoading: boolean) => {
const spinner = document.getElementById('loading-spinner');
if (spinner) {
spinner.style.display = isLoading ? 'flex' : 'none';
}
},
onConfigChange: (config: IConfigChangeData) => {
// Persist user preferences
localStorage.setItem('pdfUserPreferences', JSON.stringify(config));
// Optionally sync to backend
fetch('/api/user/preferences', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config),
}).catch((err) => console.error('Failed to sync preferences:', err));
},
// Error handling
onError: (error: Error) => {
console.error('PDF Editor Error:', error);
// Log to error tracking
if (window.Sentry) {
window.Sentry.captureException(error);
}
// Show user notification
showToast({
type: 'error',
message: error.message || 'An error occurred',
});
},
// File operations
onOpenFile: async () => {
try {
const file = await showCustomFilePicker({
accept: '.pdf',
multiple: false,
});
if (file) {
await editor.ui.pdfWebService.openDocument({ file });
showToast({ type: 'success', message: 'File opened successfully' });
}
} catch (error) {
console.error('Failed to open file:', error);
showToast({ type: 'error', message: 'Failed to open file' });
}
},
onExportFile: async (file: File) => {
try {
// Upload to cloud storage
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/documents/save', {
method: 'POST',
body: formData,
});
if (response.ok) {
const { documentId } = await response.json();
showToast({
type: 'success',
message: 'File saved to cloud',
action: {
label: 'View',
onClick: () => window.open(`/documents/${documentId}`, '_blank'),
},
});
}
} catch (error) {
console.error('Failed to save file:', error);
showToast({ type: 'error', message: 'Failed to save file' });
}
},
onPrint: (file: File, newTab?: boolean) => {
const printUrl = URL.createObjectURL(file);
if (newTab) {
window.open(printUrl, '_blank');
} else {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = printUrl;
document.body.appendChild(iframe);
iframe.onload = () => {
iframe.contentWindow?.print();
setTimeout(() => {
document.body.removeChild(iframe);
URL.revokeObjectURL(printUrl);
}, 1000);
};
}
},
onSnapshotArea: async (blob: Blob) => {
try {
// Upload snapshot to server
const formData = new FormData();
formData.append('snapshot', blob, `snapshot-${Date.now()}.png`);
const response = await fetch('/api/snapshots/upload', {
method: 'POST',
body: formData,
});
if (response.ok) {
const { url } = await response.json();
showToast({
type: 'success',
message: 'Snapshot saved',
action: {
label: 'View',
onClick: () => window.open(url, '_blank'),
},
});
}
} catch (error) {
console.error('Failed to save snapshot:', error);
showToast({ type: 'error', message: 'Failed to save snapshot' });
}
},
});
Track user interactions and usage patterns:
import { PdfEditor, IConfigChangeData } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onLoadingStateChange: (isLoading: boolean) => {
if (isLoading) {
analytics.track('pdf_loading_started');
} else {
analytics.track('pdf_loading_completed');
}
},
onConfigChange: (config: IConfigChangeData) => {
analytics.track('pdf_config_changed', {
hasTheme: !!config.themeConfig,
hasLanguage: !!config.languageConfig,
hasBranding: !!config.brandingConfig,
timestamp: Date.now(),
});
},
onOpenFile: async () => {
analytics.track('pdf_open_initiated');
try {
const file = await showFilePicker();
if (file) {
analytics.track('pdf_file_selected', {
fileName: file.name,
fileSize: file.size,
fileType: file.type,
});
await editor.ui.pdfWebService.openDocument({ file });
}
} catch (error) {
analytics.track('pdf_open_failed', { error: error.message });
}
},
onExportFile: (file: File) => {
analytics.track('pdf_downloaded', {
fileName: file.name,
fileSize: file.size,
timestamp: Date.now(),
});
// Proceed with download
const url = URL.createObjectURL(file);
const a = document.createElement('a');
a.href = url;
a.download = file.name;
a.click();
URL.revokeObjectURL(url);
},
onPrint: (file: File, newTab?: boolean) => {
analytics.track('pdf_printed', {
fileName: file.name,
fileSize: file.size,
printMode: newTab ? 'new-tab' : 'direct',
timestamp: Date.now(),
});
// Proceed with print
const url = URL.createObjectURL(file);
if (newTab) {
window.open(url, '_blank');
} else {
// ... iframe print logic
}
},
onSnapshotArea: async (blob: Blob) => {
analytics.track('pdf_snapshot_captured', {
blobSize: blob.size,
blobType: blob.type,
timestamp: Date.now(),
});
// Handle snapshot
try {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `snapshot-${Date.now()}.png`;
a.click();
URL.revokeObjectURL(url);
} catch (error) {
analytics.track('pdf_snapshot_failed', { error: error.message });
}
},
onError: (error: Error) => {
analytics.track('pdf_error_occurred', {
errorMessage: error.message,
errorName: error.name,
errorStack: error.stack,
timestamp: Date.now(),
});
// Show error to user
console.error('PDF Error:', error);
},
});