The Hidden Problem with Online PDF Editors
Editing a PDF online sounds simple: upload the file, make your changes, and download it again.
But here's the part most people don't realize: the document has to leave your computer first.
For a public flyer, this may not matter. But contracts, invoices, tax forms, IDs, financial statements, and internal company documents can contain information you may not want to send to a third-party server just to add a signature, fill out a field, or redact a number.
The good news? There's a better way!
This was one of the reasons we decided to build the PDF editor for Utilvo with browser-based processing.
Instead of uploading the document to a remote server, the editor handles the PDF directly inside your browser.
The basic difference looks like this:
Traditional approach
User
│
│ Upload PDF
▼
Remote Server
│
│ Process file
▼
Processed PDF
│
│ Download
▼
User
With local browser processing, the workflow is refreshingly different:
User
│
│ Select PDF
▼
Browser
│
├── PDF.js
├── WebAssembly
├── pdf-lib
└── Editor
│
▼
Processed PDF
│
│ Download
▼
User
The beautiful idea is simple: if the browser can process the document locally, there's no need to upload the file at all.
Processing the PDF in the Browser
The first step is getting the PDF into browser memory — and it's easier than you might think!
The browser already provides the File API, so the application can read the selected file directly:
const file = input.files[0];
if (!file) return;
const buffer = await file.arrayBuffer();
At this point, the application has the PDF bytes locally. There's no upload request involved. Your file is safe right where it is.
From there, the document can be passed to the PDF rendering layer. PDF.js parses and renders PDF pages in the browser, and the editing tools work on top of the rendered document:
const loadingTask = pdfjsLib.getDocument({
data: buffer
});
const pdf = await loadingTask.promise;
const page = await pdf.getPage(1);
The important insight here is that rendering and editing are separate problems.
PDF.js handles the rendering side. The editor layer handles text, shapes, annotations, forms, links, and other changes.
PDF Editing Is Not Just Drawing on a Canvas
Now, here's where things get really interesting — and where our approach truly shines!
A PDF is not simply an image. Text, graphics, annotations, form fields, and other elements are represented as objects and operators inside the document.
That means there's a difference between changing what the user sees and changing what actually exists inside the PDF.
For example, imagine a document contains:
Bank Account: 1234 5678 9012
A simple approach to redaction would be to draw a black rectangle over the number:
Bank Account: ███████████████
Visually, it looks correct. But here's the alarming part: if the original text still exists underneath the rectangle, the information has not really been removed. 😱
This is why proper PDF redaction needs to operate on the document content itself. Our editor handles redaction at the content-stream level by removing the underlying text operators and replacing the affected area with an opaque element.
This is the crucial difference between covering information and removing information.
Understanding PDF Content Streams
PDF pages can contain content streams with operators that describe what should be displayed. For example, text can be represented using operators such as Tj and TJ:
BT
/F1 12 Tf
(1234 5678 9012) Tj
ET
If an application only draws something on top of this content, the original text can still remain in the stream.
A redaction operation therefore needs to modify the underlying content rather than only the visual layer. This is one of the reasons a serious PDF editor needs to understand the PDF structure instead of treating every page as a flat image.
Creating Real PDF Forms — The Right Way!
Here's more good news for anyone who's ever dealt with broken "fillable" PDFs. 📋
A rectangle drawn on a PDF may look like an input field, but visually resembling a field doesn't make it an interactive PDF form field.
AcroForms use specific PDF field types:
/Tx → Text field
/Btn → Checkbox / Radio button
/Ch → Choice field
/Sig → Signature field
The editor supports these types along with other form controls such as date fields and list boxes.
A simplified text field can be represented conceptually as:
/FT /Tx
/T (FullName)
/V ()
The important part is that this becomes part of the actual PDF structure — not just a box drawn on top of the page.
When the document is exported, the form fields are written into the PDF as actual objects and associated with the document's AcroForm structure. Appearance streams are also generated so that the fields render correctly in any PDF viewer.
Adding Vector Annotations That Stay Sharp
The same principle applies to annotations. A PDF editor may need to add:
- text
- arrows
- rectangles
- circles
- highlights
- freehand drawings
- stamps
- links
These elements don't have to become screenshots. They can be represented as vector objects inside the PDF!
For example, a simple rectangle can be represented using PDF graphics operators:
q
1 0 0 1 100 200 cm
0 0 200 50 re
f
Q
The exact operators depend on the object being created, but the idea is straightforward: describe the geometry instead of turning the page into an image.
This keeps annotations razor-sharp when the document is zoomed or printed. 🖨️
The editor uses PDF transformation matrices and standard graphics operators for vector placement.
Where pdf-lib Fits
Once the document needs to be modified, the application needs a way to create and update PDF structures. This is where pdf-lib shines.
A simple example of adding text to a PDF:
const pdfDoc = await PDFDocument.load(buffer);
const page = pdfDoc.getPage(0);
page.drawText("Reviewed", {
x: 50,
y: 50,
size: 12
});
const output = await pdfDoc.save();
The important part: the resulting PDF is generated from the document data rather than from a screenshot of the page.
The browser can then create a downloadable file:
const blob = new Blob([output], {
type: "application/pdf"
});
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "edited.pdf";
link.click();
URL.revokeObjectURL(url);
The entire operation happens locally. Isn't that amazing?
Why WebAssembly?
PDF processing can become expensive as documents get larger or the operations become more complex. And here's where WebAssembly comes to the rescue!
Instead of relying only on JavaScript for every operation, parts of the processing pipeline can run through WebAssembly inside the browser:
JavaScript
│
│ Application Logic
▼
WebAssembly
│
│ Document Processing
▼
PDF Data
This is one of the technologies that makes browser-based document processing genuinely practical.
The editor's workflow uses WebAssembly memory for local PDF processing rather than sending the document to a cloud processing service.
The browser is no longer just displaying the document. It's doing the actual work. 💪
Keeping the Editor State Local
There's another part of the problem that's easy to overlook.
A PDF editor has to remember what the user is doing. A user might add a text box, move it, resize it, change its properties, add a form field, rotate a page, reorder pages, and then export the document.
So the application needs an internal state that connects the rendered PDF with all these editing operations:
Editor State
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Pages Annotations Form Fields
│ │ │
└──────────────┼──────────────┘
▼
PDF Serialization
│
▼
Final PDF
The editor architecture uses a client-side engine to coordinate the PDF rendering layer, interactive annotation overlays, and the PDF serialization layer.
This is why a PDF editor is more than a collection of buttons placed around a PDF viewer. There's a document state underneath the interface that has to stay synchronized with the final PDF.
The Privacy Side — The Best Part!
The main reason for keeping this processing local is privacy, and this is where the good news really delivers.
If a document is processed on a remote server, the document has to cross the network first. With local processing, the file can remain inside the browser while the user edits it.
That means there's no need for a document upload endpoint or a temporary server-side processing queue at all!
The distinction is simple:
Server Processing
PDF
│
▼
Upload
│
▼
Server
│
▼
Process
│
▼
Download
versus:
Browser Processing
PDF
│
▼
Browser Memory
│
├── Render
├── Edit
├── Redact
├── Add Forms
└── Export
│
▼
Download
Privacy, therefore, is not only a policy decision. It becomes part of the application architecture.
Of course, local processing doesn't automatically solve every security problem — the browser, extensions, third-party scripts, and the user's own device still matter. But if the document doesn't need to be uploaded in the first place, one entire part of the risk disappears completely.
The Trade-Off
Let's be honest about the challenges, because transparency matters too.
Browser-based PDF processing comes with its own hurdles:
- Large documents can use a significant amount of memory
- Different browsers can behave differently
- PDF files can contain structures that are difficult to preserve
- Exporting a modified PDF while maintaining compatibility requires more work than simply rendering the original document
These are real engineering problems. But they're problems worth solving when the alternative is uploading every document to a remote service.
For us, the goal wasn't to make every possible PDF operation run inside the browser. The goal was simpler:
If the browser can process the document locally, why send it somewhere else?
That question influenced the architecture from the very beginning.
Putting Everything Together
The complete workflow is roughly:
User
│
│ Select PDF
▼
┌─────────────────┐
│ Browser File │
│ API │
└────────┬────────┘
│
▼
┌─────────────────┐
│ PDF.js │
│ Parse/Render │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Editor Layer │
│ │
│ Text │
│ Shapes │
│ Forms │
│ Links │
│ Redaction │
└────────┬────────┘
│
▼
┌─────────────────┐
│ pdf-lib / WASM │
│ PDF Serialization│
└────────┬────────┘
│
▼
Final PDF
│
▼
Download
The PDF stays in the browser throughout the entire editing process.
If you want to see how this approach works in practice, you can try the Advanced PDF Editor directly in your browser.
Final Thoughts
PDF editing looks simple from the outside.
But once you start working with actual PDF structures, things become more interesting. There's a difference between drawing over text and removing it, between drawing a form field and creating a real AcroForm field, and between placing an image on a page and creating a native vector object.
Building all of this inside the browser adds another layer of challenges — but it also makes it possible to keep the document on the user's machine instead of sending it to a remote processing service.
That was the main idea behind this editor.
Not every PDF operation needs a server.
Sometimes, the browser is enough.
Where do you see the biggest limitations of client-side PDF processing today? Share your thoughts in the comments!

Top comments (1)