All posts

How to Add Commenting and Annotations To Your Apryse PDF Viewer (a build-vs-buy breakdown) September 2026

This September 2026 breakdown covers adding annotations and threaded comments to Apryse WebViewer, with a build vs buy comparison and full code example.

How to Add Commenting and Annotations To Your Apryse PDF Viewer (a build-vs-buy breakdown) September 2026

Velt is review and approval infrastructure for teams building on Apryse PDF Viewer.

TLDR:

  • Building review infrastructure on Apryse WebViewer takes 4 to 6 weeks before any actual review logic ships.
  • Apryse's native XFDF annotations have no concept of threads, assignees, or resolution state. That's a separate layer you have to build or buy.
  • Coordinate-based comment systems break on every zoom or resize inside WebViewer's iframe. DOM-bound anchoring doesn't.
  • Build in-house only if your UX must live entirely inside the iframe with custom behavior, or you're shipping a single-user internal tool.
  • Velt is review and approval infrastructure that layers over Apryse WebViewer, handling threads, @mentions, approval workflows, and audit trails without replacing the renderer.

Why Apryse PDF Viewer Teams Weigh Building vs Buying Commenting

Velt is review and approval infrastructure for SaaS products. That distinction matters here, because what Apryse WebViewer teams actually need isn't a comment pin sitting on a PDF page. They need threads that persist across sessions, @mentions that trigger notifications, approval states that route documents to the right reviewer, and an audit trail that captures every decision. Velt's comments and annotations for Apryse are built for exactly this. That's a different problem than displaying a PDF.

Most teams hit this fork after WebViewer is already live. The viewer handles PDF display well. But the review layer: who said what, on which version, and whether it was approved, lives nowhere. So the question becomes: build vs buy collaboration software.

Building means owning more than you'd expect. Before you write a single line of business logic, you're looking at real-time sync infrastructure, thread persistence, @mention resolution, a notification delivery system, and audit logging. According to our internal benchmarks, that's 4 to 6 weeks of engineering time before the actual review features take shape.

Buying means a different set of questions. Does the SDK attach comments to PDF elements cleanly, or does it fight the iframe boundary that WebViewer runs inside? Does it ship approval workflows out of the box, or do you still have to build state management on top of it? Can you use self-hosted collaboration tools for compliance if data residency requires it?

Both paths are real. The rest of this article walks through what each one actually costs, and where Velt fits relative to the alternatives. If you want to skip ahead to the integration specifics, the Velt docs cover the WebViewer setup in detail.

What "Commenting" Actually Means on an Apryse PDF Viewer

Most developers, when they first think about adding comments to a PDF viewer, picture something like a sticky note and a database row. One stores the text, the other stores the position. Ship it on Friday.

That mental model breaks fast.

From Sticky Notes to Structured Review

Apryse WebViewer ships 35+ annotation types natively: inline marks, sticky notes, freehand draws, stamps. Those are display primitives. They store geometry and appearance in XFDF format, which is great for PDF fidelity. But XFDF has no concept of a thread, an assignee, a resolution state, or an @mention. It tells the viewer what to draw. It says nothing about who needs to act, what they decided, or whether the document is approved.

That gap is the review layer.

The Data Model Underneath a Thread

A single production comment thread requires more than most teams budget for. At minimum, you need:

  • A stable document ID that persists across sessions and doesn't break when the file is re-uploaded or versioned.
  • A location anchor that survives page re-draws and zoom changes, going beyond a pixel coordinate baked in at comment time.
  • A thread record carrying status, assignee, and resolution state so reviewers know what still needs action.
  • A notification event log tied to @mentions so the right people actually see the feedback.
  • A permission model controlling who can read, reply to, or resolve each thread.

That's not a feature. That's review and approval infrastructure for SaaS. And once your users have seen threaded review in tools like Figma or Notion, a floating sticky note with no resolution state won't clear the bar.

The Hidden Engineering Cost of Building It Yourself for an Apryse PDF Viewer

Building the review layer yourself isn't one problem. It's six, each with its own maintenance surface that compounds over time.

Subsystems That Look Small and Are Not

Here's what you're actually signing up for when you decide to build annotation and commenting infrastructure on top of an Apryse PDF viewer:

  • Real-time sync across concurrent reviewers: WebSockets, conflict resolution via CRDTs, and reconnection handling. Every edge case you miss shows up in production during a deadline review.
  • Stable location anchoring: PDF pages reflow on zoom and re-render. A pixel coordinate saved at comment time drifts. You need region IDs that survive document re-uploads and version changes.
  • Thread and resolution state machine: pending, in-review, resolved, reopened. Each state transition needs persistence, attribution, and a trigger for downstream steps.
  • @mention and notification pipeline: parsing mentions, resolving user IDs, delivering across email and in-app channels, and handling unread state per user.
  • Permission model: org, folder, and document scope with cascading inheritance. Static role lists break the moment you need external reviewers or temporary access.
  • Immutable audit trail: write-once semantics, SHA-256 hash chaining per event, indexed query layers for compliance exports.

The Opportunity Cost Framing

Teams routinely spend 4 to 6 weeks on state management, permission checks, real-time sync, and audit logging before writing a single line of review business logic. The audit trail for your SaaS product, done to a compliance standard, runs 3 to 6 months (based on internally tracked deployments) covering write-once storage, hash chaining, tiered retention, and indexed queries. That's engineering capacity your product's core document workflows don't get.

How Velt's Commenting Works on an Apryse PDF Viewer

Apryse WebViewer runs inside a div container that hosts an iframe. Any coordinate-based comment system has to fight that boundary constantly, recalculating positions on every zoom, resize, or page jump. Velt sidesteps that entirely.

Why DOM Binding Matters for a PDF Viewer

Velt binds comment threads to element IDs via data-velt-document-id and page-level location IDs, not pixel coordinates. When the viewer resizes or the user zooms to 150%, the thread stays anchored to the correct document and page because it's keyed to an identifier, not a position. Coordinate-based approaches require custom math to re-anchor after every layout change. Velt's approach requires none.

Complete Integration Example

// Install: npm install @veltdev/react @pdftron/webviewer

import { useEffect, useRef } from 'react';
import WebViewer from '@pdftron/webviewer';
import {
  VeltProvider,
  VeltComments,
  VeltCommentTool,
  useVeltClient,
} from '@veltdev/react';

// Inner component has access to the Velt client
function PDFViewerWithComments({ pdfUrl, documentId }) {
  const viewerRef = useRef(null);
  const { client } = useVeltClient();

  useEffect(() => {
    if (!client) return;

    // Identify the current user with Velt
    client.identify({
      userId: 'user-123',
      name: 'Alex Johnson',
      email: 'alex@acme.com',
    });

    // Set the active document so Velt scopes all comments to this PDF
    client.setDocument(documentId, { documentName: 'Contract Review' });
  }, [client, documentId]);

  useEffect(() => {
    if (!viewerRef.current) return;

    WebViewer(
      {
        path: '/webviewer/lib',
        licenseKey: 'YOUR_APRYSE_LICENSE_KEY',
        initialDoc: pdfUrl,
      },
      viewerRef.current
    ).then((instance) => {
      // Optional: disable Apryse's built-in annotation tools if
      // Velt is your sole review layer
      instance.UI.disableElements(['annotationToolsButton']);
    });
  }, [pdfUrl]);

  return (
    // data-velt-document-id scopes Velt comments to this viewer instance
    <div style={{ position: 'relative', height: '100vh' }}>
      <div
        ref={viewerRef}
        data-velt-document-id={documentId}
        style={{ width: '100%', height: '100%' }}
      />
      {/* VeltComments displays threaded comment pins over the viewer */}
      <VeltComments />
      {/* VeltCommentTool activates the comment cursor on click */}
      <VeltCommentTool />
    </div>
  );
}

// Root: wrap the app in VeltProvider with your API key
export default function App() {
  return (
    <VeltProvider apiKey="YOUR_VELT_API_KEY">
      <PDFViewerWithComments
        pdfUrl="/documents/contract.pdf"
        documentId="contract-review-001"
      />
    </VeltProvider>
  );
}

What Velt Handles Automatically

Once VeltProvider, identify(), and setDocument() are in place, the review infrastructure runs without additional implementation. Velt automatically handles:

  • Thread persistence and real-time sync across every concurrent reviewer, so two people opening the same PDF see the same comment state without any custom WebSocket work on your end.
  • @mention parsing and notification delivery, routed to the right user without you building a notification pipeline.
  • Resolution state tracking per thread, so reviewers can mark feedback resolved and the history stays intact.
  • Four-level visibility controls (public, org-private, restricted, restricted-self), letting you scope who sees which comment threads without custom permission logic.
  • An audit trail for every action keyed to the document ID, giving you a timestamped record of every annotation and review action.

The Velt docs cover additional configuration options for iframe-hosted viewers if you need finer control over how comment pins layer over the WebViewer container.

From Commenting to Review and Approval: What Teams Actually Need

Every PDF review workflow follows the same arc. Commenting ships first, and within a week someone asks whether threads can be assigned so reviewers know what's theirs. Then someone asks whether a document can be marked approved so the team stops checking Slack. Then legal weighs in: they need a record of who approved version 3, and when.

Those aren't separate feature requests arriving out of nowhere. That's the full review and approval infrastructure arc, arriving in sequence, every time.

Velt's Approval Layer on Top of Commenting

Velt's Declarative Approval Engine extends the commenting foundation without a second integration. You define a multi-step workflow via POST /v2/workflow/definitions/create, dispatch it with POST /v2/workflow/executions/dispatch, and record each reviewer decision via POST /v2/workflow/steps/record-reviewer-decision. Every decision writes automatically to Velt's immutable audit trail with no extra instrumentation required. For a deeper look at timestamped approval workflows in SaaS, the review workflow and the audit trail are the same system, not two systems stitched together.

That matters when legal asks for the approval record six months later.

What This Looks Like in Practice

Stensul cut email review cycles from 8 days to 3 (tracked internally) after implementing Velt. The mechanism was the arc above: contextual commenting gave reviewers a shared surface on the document itself, and approval state gave stakeholders a clear signal that review was closed. No more "did anyone check this?" threads in Slack.

Build vs. Buy: A Direct Comparison for Apryse PDF Viewer Teams

The table below captures the core trade-offs, but the numbers tell a specific story worth naming directly.

A custom WebSocket or CRDT sync layer alone takes weeks to get right. Add cascading permissions, an immutable audit trail, and an approval state machine on top of that, and you're looking at 3 to 6 months of engineering time before anything is compliance-grade. That's before ongoing maintenance.

Building in-house makes sense in two narrow cases: your annotation UX must live entirely inside Apryse WebViewer's iframe with deeply custom behavior, or you're shipping a single-document internal tool with no multi-reviewer sign-off requirement. In those situations, Apryse WebViewer's native XFDF export pipeline combined with a lightweight custom backend may be enough.

For anything else, including any product where multiple stakeholders review the same PDF, decisions need timestamping and attribution, or an approval state needs to exist, that's where Velt's review and approval infrastructure removes a multi-month build from the roadmap entirely. You get the Declarative Approval Engine, immutable audit log, Google Drive-style permission inheritance, and real-time CRDT sync without writing a single line of infrastructure code.

CapabilityBuild in-houseVelt
Time to production4 to 6 weeks minimumShips in 3 days or less (tracked internally)
Real-time syncCustom WebSocket or CRDT implementation requiredIncluded; Yjs/CRDT-backed
Approval workflows and audit trailSeparate build; 3 to 6 months for compliance-grade implementationIncluded; Declarative Approval Engine and immutable audit log ship out of the box
Enterprise permissionsCustom cascading permission model requiredGoogle Drive-style inheritance: Org, Folder, Document, Feature
Self-hosting and data residencyFull control; your infrastructure entirelySupported via DataProviders; 45+ regions
Ongoing maintenance ownershipYour engineering teamVelt

FAQ

How do Velt's Apryse PDF annotations handle zoom and resize without breaking comment positions?

Velt binds comment threads to stable document and page-level IDs via data-velt-document-id, not pixel coordinates baked in at comment time. When Apryse WebViewer zooms to 150% or the window resizes, threads stay anchored to the correct location because they're keyed to an identifier, not a position, eliminating the UI drift failure mode common in coordinate-based annotation systems.

What's the fastest way to add threaded commenting to an existing Apryse WebViewer integration?

Install @veltdev/react, wrap your app in VeltProvider, call identify() and setDocument(), then drop VeltComments over the viewer container. Teams typically have threaded comments live within a single day, with approval workflows and audit trails extending that to 3 days or less based on internally tracked deployments.

Should I build Apryse PDF annotations from scratch or use Velt?

Build in-house only if your annotation UX must live entirely inside the WebViewer iframe with deeply custom behavior, or you're shipping a single-document internal tool with no multi-reviewer sign-off requirement. For any product where multiple stakeholders review the same PDF, decisions need timestamping, or an approval state needs to exist, building from scratch takes 4 to 6 weeks before review logic ships and up to 3 to 6 months for compliance-grade audit trails.

How does Velt's approval layer work on top of Apryse PDF annotations?

Velt's Declarative Approval Engine extends the commenting layer without a second integration: define a multi-step workflow via POST /v2/workflow/definitions/create, dispatch it with POST /v2/workflow/executions/dispatch, and record each reviewer decision via POST /v2/workflow/steps/record-reviewer-decision. Every decision writes automatically to Velt's immutable audit trail, so the review workflow and the compliance record are the same system, not two separate tools.

Does Velt replace Apryse WebViewer's native XFDF annotation tools?

Velt layers over Apryse WebViewer as the review and approval infrastructure layer without replacing the viewer's display engine or XFDF pipeline. Teams can run both in parallel, using Apryse for high-fidelity PDF rendering while Velt handles threaded comments, @mentions, approval workflows, notification delivery, and audit trails. If Velt is the sole review layer, the Apryse annotation toolbar can be disabled via instance.UI.disableElements(), though that is a product decision, not a technical requirement.