Skip to main content

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.
documentId
string
required
The document ID to add the highlight to
id
string
required
Client-generated unique identifier for the highlight
type
HighlightTypeEnum
required
Type of highlight: TEXT or IMAGE
pageNumber
number
required
The page number where the highlight is located
boundingRect
Rectangle
required
The bounding rectangle that encompasses the entire highlight
rects
Rectangle[]
required
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:
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.
highlightId
string
required
The unique identifier of the highlight to delete
documentId
string
required
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.
documentId
string
required
The document ID
id
string
required
The highlight ID to update
type
HighlightTypeEnum
required
Type of highlight: typically IMAGE for area highlights
pageNumber
number
The page number (optional)
boundingRect
Rectangle
required
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:
  1. Bounding Rectangle: A single rectangle that encompasses the entire highlight
  2. 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

Build docs developers (and LLMs) love