PDF content editing API for Node.js
Nutrient Node.js SDK edits the text content of a PDF document programmatically. Existing text blocks can be modified, repositioned, and resized, and the original document structure and formatting survive the edit.
Prerequisites
Before you get started, make sure Nutrient Node.js SDK is up and running.
You can download and use either of the following sample documents for the examples in this guide:
License requirements
Content editing requires a separate license that includes the Content Editor API component, and without it a call such as beginContentEditingSession() throws an error.
Contact Sales to add it to your license.
Beginning a content editing session
Every content editing operation runs inside a dedicated session. The session keeps the data consistent, and it lets several changes accumulate before anything is committed to the document.
Call beginContentEditingSession() on the document instance to start a session. It returns a session object, which is the entry point for every content editing operation:
import fs from "node:fs";import { load } from "@nutrient-sdk/node";
const documentBuffer = fs.readFileSync("4-page-example-document.pdf");const instance = await load({ document: documentBuffer });
// Start a content editing session.const session = await instance.beginContentEditingSession();Detecting text blocks
A text block is a single paragraph or text element. Each one can be read and modified on its own, and the session’s getTextBlocks(pageIndex) method returns an array of the blocks on the page:
import fs from "node:fs";import { load } from "@nutrient-sdk/node";
const documentBuffer = fs.readFileSync("4-page-example-document.pdf");const instance = await load({ document: documentBuffer });
const session = await instance.beginContentEditingSession();
// Get all text blocks on the first page (page index 0).const textBlocks = await session.getTextBlocks(0);
console.log(`Found ${textBlocks.length} text blocks on page 1`);
textBlocks.forEach((block, index) => { console.log(`Block ${index + 1}:`); console.log(` ID: ${block.id}`); console.log(` Text: ${block.text}`); console.log(` Position: (${block.anchor.x}, ${block.anchor.y})`); console.log(` Max Width: ${block.maxWidth}`); console.log(` Bounding Box: ${JSON.stringify(block.boundingBox)}`);});Each text block contains the following properties:
id— Unique identifier for the text block. A PDF has no concept of IDs, so the SDK generates these deterministically.text— The text content of the block. If it’s a multiline block, it’ll contain all lines concatenated with newline characters.anchor— Position coordinates (x, y) of the text block anchor point, in the PDF coordinate system. This is typically the top-left corner, adjusted for the PDF internal offset.maxWidth— Maximum width constraint for the text block.boundingBox— Current bounding rectangle with top, left, width, and height, in PDF points. Use it to place an overlay or annotation over the text block.
Updating text blocks
The updateTextBlocks(textBlocks) method changes the text, the position, and the maximum width of an existing block. It takes an array of objects, one per block, each carrying that block’s id and the properties to change:
import fs from "node:fs";import { load } from "@nutrient-sdk/node";
const documentBuffer = fs.readFileSync("document.pdf");const instance = await load({ document: documentBuffer });
const session = await instance.beginContentEditingSession();
// Get text blocks from the first page.const textBlocks = await session.getTextBlocks(0);const firstBlock = textBlocks[0];
// Update a block.await session.updateTextBlocks([ { id: firstBlock.id, text: "This is the new text content", anchor: { x: 100, y: 200 }, maxWidth: 300 }]);An update only stages the change in the session. commit() applies the staged changes to the document, and discard() throws them away.
To update a text block, provide the following properties in the update object:
id— Required identifier of the text block to update, which you can obtain from thegetTextBlocks(pageIndex)method.text— Optional new text content for the block. Providing it replaces the existing text.anchor— Optional new position coordinates (x, y) for the text block anchor point, which repositions the block within the PDF.maxWidth— Optional new maximum width for the text block, which sets how wide the text runs before it wraps.
Changing the maximum width may affect text wrapping and layout. A width smaller than the current text wraps it to fit the new constraint. A width larger than the current text is not persisted, so the excess space is gone the next time the document loads.
Batch updates
Passing several blocks to updateTextBlocks(textBlocks) at once updates them in a single operation, which performs better than one call per block:
import fs from "node:fs";import { load } from "@nutrient-sdk/node";
const documentBuffer = fs.readFileSync("4-page-example-document.pdf");const instance = await load({ document: documentBuffer });
const session = await instance.beginContentEditingSession();const textBlocks = await session.getTextBlocks(0);
// Update multiple text blocks at once.await session.updateTextBlocks([ { id: textBlocks[0].id, text: "Updated first block" }, { id: textBlocks[1].id, maxWidth: 250 }, { id: textBlocks[2].id, anchor: { x: 200, y: 300 }, maxWidth: 400 }]);Committing changes
The commit() method saves every staged change to the document and closes the session:
const session = await instance.beginContentEditingSession();
// Make your changes.await session.updateTextBlocks([ /* ... */]);
// Save changes and close the session.await session.commit();Discarding changes
The discard() method drops every staged change and closes the session without saving:
const session = await instance.beginContentEditingSession();
// Make your changes.await session.updateTextBlocks([ /* ... */]);
// Cancel changes without saving.await session.discard();
// Session is now inactive, changes are lost.console.log(session.active); // falseChecking session status
The session.active property reports whether a session is still open:
const session = await instance.beginContentEditingSession();
console.log(session.active); // true
await session.discard();
console.log(session.active); // falseA document editing operation performed while a content editing session is active discards the staged changes and closes the session.
Complete example
import fs from "node:fs";import { load } from "@nutrient-sdk/node";
const documentBuffer = fs.readFileSync("4-page-example-document.pdf");const instance = await load({ document: documentBuffer });const session = await instance.beginContentEditingSession();
// Get text blocks from the first page.const textBlocks = await session.getTextBlocks(0);// Update the first text block.await session.updateTextBlocks([ { id: textBlocks[0].id, text: "This is the updated text content", anchor: { x: 100, y: 200 }, maxWidth: 300 }]);// Commit changes to the document.await session.commit();console.log(session.active); // false
// Close the document instance.await instance.close();