Overview
The Highlight API allows users to create and manage highlights within PDF documents. Highlights can be either text selections or image areas.
Procedures
add
Create a new highlight in a document. Available to document owners and editors.
The document ID to add the highlight to
Client-generated unique identifier for the highlight
type
HighlightTypeEnum
required
Type of highlight: TEXT or IMAGE
The page number where the highlight is located
The bounding rectangle that encompasses the entire highlight
Array of rectangles that make up the highlight. For text highlights, this typically includes one rectangle per line. For image highlights, usually just one rectangle.
Example:
Text Highlight
Image Highlight
import { api } from "@/lib/api" ;
import { HighlightTypeEnum } from "@prisma/client" ;
const mutation = api . highlight . add . useMutation ();
// Add a text highlight across two lines
mutation . mutate ({
documentId: "clx123abc" ,
id: "highlight-" + crypto . randomUUID (),
type: HighlightTypeEnum . TEXT ,
pageNumber: 5 ,
boundingRect: {
x1: 100 ,
y1: 200 ,
x2: 500 ,
y2: 240 ,
width: 400 ,
height: 40 ,
pageNumber: 5
},
rects: [
{
x1: 100 ,
y1: 200 ,
x2: 500 ,
y2: 220 ,
width: 400 ,
height: 20 ,
pageNumber: 5
},
{
x1: 100 ,
y1: 220 ,
x2: 300 ,
y2: 240 ,
width: 200 ,
height: 20 ,
pageNumber: 5
}
]
});
delete
Delete a highlight from a document. Available to document owners and editors.
The unique identifier of the highlight to delete
The document ID (for authorization verification)
Example:
const mutation = api . highlight . delete . useMutation ({
onSuccess : () => {
console . log ( "Highlight deleted successfully" );
}
});
mutation . mutate ({
highlightId: "h123" ,
documentId: "clx123abc"
});
updateAreaHighlight
Update an existing area/image highlight’s position. Available to document owners and editors.
The highlight ID to update
type
HighlightTypeEnum
required
Type of highlight: typically IMAGE for area highlights
The page number (optional)
The new bounding rectangle for the highlight
This procedure updates only the bounding rectangle, not the individual rects array. It’s typically used for area/image highlights that have been moved or resized.
Example:
const mutation = api . highlight . updateAreaHighlight . useMutation ();
// Resize an image highlight
mutation . mutate ({
documentId: "clx123abc" ,
id: "h123" ,
type: HighlightTypeEnum . IMAGE ,
pageNumber: 12 ,
boundingRect: {
x1: 150 ,
y1: 300 ,
x2: 500 , // Changed from 450
y2: 550 , // Changed from 500
width: 350 ,
height: 250
}
});
Data Models
HighlightTypeEnum
enum HighlightTypeEnum {
TEXT = "TEXT" , // Text selection highlight
IMAGE = "IMAGE" // Area/image highlight
}
Highlight Schema
interface Highlight {
id : string ;
type : HighlightTypeEnum ;
documentId : string ;
createdAt : Date ;
pageNumber : number | null ;
boundingRectangle : Coordinate | null ;
rectangles : Coordinate [];
}
interface Coordinate {
id : string ;
x1 : number ;
y1 : number ;
x2 : number ;
y2 : number ;
width : number ;
height : number ;
pageNumber : number | null ;
}
Highlight Position Data
Highlights are stored with two types of position data:
Bounding Rectangle : A single rectangle that encompasses the entire highlight
Rectangles Array : Individual rectangles that make up the highlight
Text Highlights
For text highlights that span multiple lines:
{
boundingRect : {
// Encompasses all lines
x1 : 100 , y1 : 200 ,
x2 : 500 , y2 : 260
},
rects : [
// First line
{ x1: 100 , y1: 200 , x2: 500 , y2: 220 },
// Second line
{ x1: 100 , y1: 220 , x2: 500 , y2: 240 },
// Third line (partial)
{ x1: 100 , y1: 240 , x2: 300 , y2: 260 }
]
}
Image Highlights
For area/image highlights:
{
boundingRect : {
x1 : 150 , y1 : 300 ,
x2 : 450 , y2 : 500
},
rects : [
// Usually just one rectangle
{ x1: 150 , y1: 300 , x2: 450 , y2: 500 }
]
}
Authorization
All highlight procedures require the user to be either:
The document owner, OR
A collaborator with EDITOR role
Viewers cannot create, update, or delete highlights.
// Authorization check in the API
const doc = await ctx . prisma . document . findUnique ({
where: {
id: input . documentId ,
OR: [
{ ownerId: ctx . session . user . id },
{
collaborators: {
some: {
userId: ctx . session . user . id ,
role: CollaboratorRole . EDITOR ,
},
},
},
],
},
});
if ( ! doc ) {
throw new TRPCError ({
code: "UNAUTHORIZED" ,
message: "You are not authorized to edit this document" ,
});
}
Error Handling
Common errors:
UNAUTHORIZED - User doesn’t have permission to edit the document
INTERNAL_SERVER_ERROR - Database or validation error
const mutation = api . highlight . add . useMutation ({
onError : ( error ) => {
if ( error . data ?. code === "UNAUTHORIZED" ) {
toast . error ( "You don't have permission to add highlights" );
} else {
toast . error ( "Failed to add highlight" );
}
},
onSuccess : () => {
toast . success ( "Highlight added" );
}
});
Best Practices
Generate IDs Client-Side Generate highlight IDs on the client using crypto.randomUUID() for optimistic updates
Validate Coordinates Ensure coordinates are within page boundaries before sending to the API
Batch Operations When adding multiple highlights, use React Query’s batch mutation features
Handle Cascading Deletes Highlights are automatically deleted when their parent document is deleted