JSON Format

The following annotation types are supported in PSPDFKit for Web:

  • pspdfkit/image

  • pspdfkit/ink

  • pspdfkit/link

  • pspdfkit/markup/highlight

  • pspdfkit/markup/redaction

  • pspdfkit/markup/squiggly

  • pspdfkit/markup/strikeout

  • pspdfkit/markup/underline

  • pspdfkit/note

  • pspdfkit/comment-marker

  • pspdfkit/shape/ellipse

  • pspdfkit/shape/line

  • pspdfkit/shape/polygon

  • pspdfkit/shape/polyline

  • pspdfkit/shape/rectangle

  • pspdfkit/stamp

  • pspdfkit/text

Flow Types

We use Flow type declarations to specify the format of our annotations.

The optional keys are specified as follows:

{ optionalKey?: value; }

In order to save traffic, these keys should not be included in the record if the value is undefined.

Basic Properties

Basic Types

We define a number of low-level types that are used throughout all annotation types.


Geometry always represents points inside a page. Rects are positioned at the top left point:

// [left, top, width, height] as a number in px.
declare type Rect = [number, number, number, number];

// [x, y] in px.
declare type Point = [number, number];

// Counterclockwise angle in degrees. Should be 0, 90, 180, or 270.
declare type Rotation = number;

// Inset used for drawing cloudy borders more precisely [left, top, right, bottom].
declare type CloudyBorderInset = [number, number, number, number];


// "#RRGGBB"
declare type Color = string;

// 0.0 to 1.0.
declare type Opacity = number;

// 0.0 to 1.0; the default is 0.5.
declare type Intensity = number;

// ISO 8601 with full date, time, and time zone information.
// e.g. "2012-04-23T18:25:43.511Z"
// - https://en.wikipedia.org/wiki/ISO_8601
// - https://www.w3.org/TR/NOTE-datetime
declare type Timestamp = string;

declare type Lines = {
	// Intensities are used to weigh the point during natural
	// drawing. They are received by pressure-sensitive drawing
	// or touch devices. The default value should be used if
	// it's not possible to obtain the intensity.
	intensities: Array<Array<Intensity>>,

	// Points are grouped in segments. Points inside a segment
	// are joined to a line. There must be at least one segment
	// with at least one point.
	points: Array<Array<Point>>,

declare type LineCap =
	| 'square'
	| 'circle'
	| 'diamond'
	| 'openArrow'
	| 'closedArrow'
	| 'butt'
	| 'reverseOpenArrow'
	| 'reverseClosedArrow'
	| 'slash';

declare type LineCaps = {
	start?: LineCap,
	end?: LineCap,

declare type BlendMode =
	| 'normal'
	| 'multiply'
	| 'screen'
	| 'overlay'
	| 'darken'
	| 'lighten'
	| 'colorDodge'
	| 'colorBurn'
	| 'hardLight'
	| 'softLight'
	| 'difference'
	| 'exclusion';

Action Types

PSPDFKit for Web only supports a subset of all possible annotation actions. Currently, these can be triggered when clicking on a link annotation:

declare type BaseAction = {
	subAction?: Action,

declare type GoToAction = BaseAction & {
	type: 'goTo',
	// The `pageIndex` you want to go to.
	pageIndex: number,

declare type GoToRemoteAction = BaseAction & {
	type: 'goToRemote',
	relativePath: string,
	namedDestination: string,

declare type GoToEmbeddedAction = BaseAction & {
	type: 'goToEmbedded',
	newWindow: boolean,
	relativePath: string,
	targetType: 'parent' | 'child',

declare type LaunchAction = BaseAction & {
	type: 'launch',
	filePath: string,

declare type URIAction = BaseAction & {
	type: 'uri',
	// URI, e.g.: `https://pspdfkit.com`
	uri: string,

declare type AnnotationReference = {
	fieldName?: string,
	pdfObjectId?: number,

declare type HideAction = BaseAction & {
	type: 'hide',
	hide: boolean,
	annotationReferences: Array<AnnotationReference>,

declare type JavaScriptAction = BaseAction & {
	type: 'javaScript',
	script: string,

declare type SubmitFormFlags = Array<
	| 'includeExclude'
	| 'includeNoValueFields'
	| 'exportFormat'
	| 'getMethod'
	| 'submitCoordinated'
	| 'xfdf'
	| 'includeAppendSaves'
	| 'includeAnnotations'
	| 'submitPDF'
	| 'canonicalFormat'
	| 'excludeNonUserAnnotations'
	| 'excludeFKey'
	| 'embedForm',

declare type SubmitFormAction = BaseAction & {
	type: 'submitForm',
	uri: string,
	flags: SubmitFormFlags,
	fields?: Array<AnnotationReference>,

declare type ResetFormAction = BaseAction & {
	type: 'resetForm',
	flags?: 'includeExclude',
	fields?: Array<AnnotationReference>,

declare type NamedAction = BaseAction & {
	type: 'named',
		| 'nextPage'
		| 'prevPage'
		| 'firstPage'
		| 'lastPage'
		| 'goBack'
		| 'goForward'
		| 'goToPage'
		| 'find'
		| 'print'
		| 'outline'
		| 'search'
		| 'brightness'
		| 'zoomIn'
		| 'zoomOut'
		| 'saveAs'
		| 'info',

declare type Action =
	| GoToAction
	| GoToRemoteAction
	| GoToEmbeddedAction
	| LaunchAction
	| URIAction
	| HideAction
	| JavaScriptAction
	| SubmitFormAction
	| ResetFormAction
	| NamedAction;

Base Annotation Type

Every annotation shares a set of common values, which are formalized in the BaseAnnotation type. You will later see all annotations extend this type using type intersection.

This means that a specific annotation needs to have all the properties of BaseAnnotation and all the properties of its specific annotation type:

// See the PDF Reference for the below flags. One difference: Instead of a
// `print` flag, like in PDF, we have `noPrint` so that we don't have
// `print` enabled on almost all annotations.
declare type Flags = Array<
  | "noPrint"
  | "noZoom"
  | "noRotate"
  | "noView"
  | "hidden"
  | "invisible"
  | "readOnly"
  | "locked"
  | "toggleNoView"
  | "lockedContents"

declare type BaseAnnotation = {
  // The spec version that the record is compliant to. Always `1`.
  v: 1,
  // An annotation must always be inside a specific page.
  pageIndex: number,
  // The bounding box of the annotation within the page.
  bbox: Rect,
  // Modifies the transparency of the annotation.
  opacity: Opacity,
  // The object ID from the source PDF.
  pdfObjectId?: number;
  // PDF Flags.
  flags?: Flags;
  // Optional PDF Action.
  action?: Action;
  // The name of the creator of the annotation.
  creatorName?: string,
  // The date of the annotation creation.
  createdAt: Timestamp,
  // The date of the last annotation update.
  updatedAt: Timestamp
  // Optional annotation name. This is used to identify the annotation.
  name?: string;

declare type Annotation =
  | MarkupAnnotation
  | TextAnnotation
  | NoteAnnotation
  | EllipseAnnotation
  | RectangleAnnotation
  | LineAnnotation
  | PolylineAnnotation
  | InkAnnotation
  | LinkAnnotation
  | ImageAnnotation
  | StampAnnotation
  | CommentMarkerAnnotation;

pspdfkit/markup/{highlight, squiggly, strikeout, underline}

Markup annotations include highlight, squiggly, strikeout, and underline. All of these require a list of rects that they are drawn to. The highlight annotation will lay the color on top of the element and apply the multiply blend mode.

Markup annotations
declare type MarkupAnnotation = BaseAnnotation & {
		| 'pspdfkit/markup/highlight'
		| 'pspdfkit/markup/squiggly'
		| 'pspdfkit/markup/strikeout'
		| 'pspdfkit/markup/underline',
	// List of rects on the page where the markup is drawn.
	rects: Array<Rect>,
	blendMode?: BlendMode,
	color: Color,
	note?: string,


Redaction annotations are markup annotations, but they support some additional properties, so we have a specific type for them.

ℹ️ Note: You have to have licensed the Redaction component in order to use redaction annotations.

declare type RedactionAnnotation = MarkupAnnotation & {
  type: "pspdfkit/markup/redaction"
  // Outline color is the border color of a redaction annotation when it hasn't yet been applied to the document.
  outlineColor?: Color,
  // Fill color is the background color that a redaction will have when applied to the document.
  fillColor?: Color,
  // The text that will be printed on top of an applied redaction annotation.
  overlayText?: string,
  // Specifies whether or not the `overlayText` will be repeated multiple times to fill the boundaries of the redaction annotation.
  repeatOverlayText?: boolean,
  rotation?: Rotation

With redaction annotations, the rects property determines the location of the area marked for redaction.


A text annotation can be placed anywhere on the screen. Please keep in mind that fonts are client specific, so you should only use fonts you know are present in the browser where they should be displayed. If a font is not found, PSPDFKit will automatically fall back to a sans-serif font.

Text annotations
declare type TextAnnotation = BaseAnnotation & {
  type: "pspdfkit/text",
  // The text contents.
  text: string,
  // A background that will fill the bounding box.
  backgroundColor?: Color,
  // Size of the text in px (this will scale when you zoom in).
  fontSize: number,
  // The font to render the text. A client will fall back to a sans-serif font
  // if it is not supported or if none is defined.
  font?: string,
  // A text can be only italic, only bold, italic and bold, or none of these.
  fontStyle?: Array<"italic" | "bold">,
  // The color of the rendered glyphs.
  fontColor: Color,
  horizontalAlign: "left" | "center" | "right",
  verticalAlign: "top" | "center" | "bottom",
  // Specifies that the text is supposed to fit in the bounding box.
  // This will only be set on new annotations, as we can't easily figure
  // out if an appearance stream contains all the text for existing annotations.
  isFitting?: boolean,
  callout?: {
    start: Point,
    knee?: Point,
    end: Point,
    cap?: LineCap,
    // Inset applied to the box to size and position the rect for the text [left, top, right, bottom].
    innerRectInset: [number, number, number, number]
  borderStyle?: BorderStyle,
  // Only set if there is a `borderStyle` too.
  borderWidth?: number
  rotation: Rotation;
  cloudyBorderIntensity?: number


Ink annotations are used for freehand drawings on a page. They can contain multiple segments (see the definition of Lines above). Points within a segment are connected to a line.

Ink annotations
declare type InkAnnotation = BaseAnnotation & {
	type: 'pspdfkit/ink',
	// Refer to the above spec for the `Lines` type.
	lines: Lines,
	// The width of the line in px.
	lineWidth: number,
	// PSPDFKit's natural drawing mode. This value is only used by PSPDFKit
	// for iOS.
	isDrawnNaturally: boolean,
	// True if the annotation is an ink signature.
	isSignature?: boolean,
	// The color of the line.
	strokeColor: Color,
	// The color that fills the bounding box.
	backgroundColor?: Color,
	blendMode?: BlendMode,
	note?: string,

A link can be used to trigger an action when clicked. The link will be drawn on the bounding box.

Link annotations
declare type LinkAnnotation = BaseAnnotation & {
	type: 'pspdfkit/link',
	// Refer to the above spec for the `Action` type.
	action: Action,
	note?: string,
	borderStyle?: BorderStyle,
	// Only set if there is a `borderStyle` too.
	borderWidth?: number,
	// The common annotations dictionary says that the C entry should be used for
	// border color in the case of link annotations.
	borderColor?: Color,


Note annotations are “sticky notes” attached to a point in the PDF document. They are represented as markers, and each one has an icon associated with it. Its text content is revealed on selection.

Note annotations
declare type NoteAnnotation = BaseAnnotation & {
	type: 'pspdfkit/note',
	text: string,
		| 'comment'
		| 'rightPointer'
		| 'rightArrow'
		| 'check'
		| 'circle'
		| 'cross'
		| 'insert'
		| 'newParagraph'
		| 'note'
		| 'paragraph'
		| 'help'
		| 'star'
		| 'key',
	// Fills the note shape and its icon [canvas].
	color: Color,


A comment marker annotation marks the position of an Instant Comments thread on the page:

declare type CommentMarkerAnnotation = BaseAnnotation & {
	type: 'pspdfkit/comment-marker',

Shape Annotations

Shape annotations are used to draw different shapes — lines, rectangles, ellipses, polylines, and polygons — on a page.

Shape annotations with a transparent fill color are only selectable around their visible lines. This means you can create a page full of shape annotations, while annotations behind these shape annotations are still selectable.

Blend modes let you decide how different layers of color will interact with each other and what the end result will be. You can check out this Wikipedia entry to learn more about blend modes and their use:

declare type ShapeAnnotation = BaseAnnotation & {
  strokeDashArray?: Array<number>,
  strokeWidth: number,
  strokeColor: Color,
  note?: string,
    | "normal",
    | "multiply",
    | "screen",
    | "overlay",
    | "darken",
    | "lighten",
    | "colorDodge",
    | "colorBurn",
    | "hardLight",
    | "softLight",
    | "difference",
    | "exclusion"


Ellipse annotations are used to draw ellipses on a page.

Ellipse shape annotations
declare type EllipseAnnotation = ShapeAnnotation & {
	type: 'pspdfkit/shape/ellipse',
	// Fills the inside of the shape.
	fillColor?: Color,
	cloudyBorderIntensity?: number,


Rectangle annotations are used to draw rectangles on a page.

Rectangle shape annotations
declare type RectangleAnnotation = ShapeAnnotation & {
	type: 'pspdfkit/shape/rectangle',
	// Fills the inside of the shape.
	fillColor?: Color,
	cloudyBorderIntensity?: number,


Line annotations are used to draw straight lines on a page.

Line shape annotations
declare type LineAnnotation = ShapeAnnotation & {
	type: 'pspdfkit/shape/line',
	startPoint: Point,
	endPoint: Point,
	lineCaps?: LineCaps,
	// Fills the inside of the end/start caps.
	fillColor?: Color,


Polyline annotations are used to draw polylines on a page by hand. They can contain any number of sides, which are defined by the polyline vertices.

Polyline shape annotations
declare type PolylineAnnotation = ShapeAnnotation & {
	type: 'pspdfkit/shape/polyline',
	// Fills the inside of the line caps.
	fillColor?: Color,
	lineCaps?: LineCaps,
	points: Array<Point>,


Polygon annotations are used to draw polygons on a page by hand. They can contain any number of sides, which are defined by the polygon vertices.

Polygon shape annotations
declare type PolygonAnnotation = ShapeAnnotation & {
	type: 'pspdfkit/shape/polygon',
	// Fills the inside of a closed polygon.
	fillColor?: Color,
	points: Array<Point>,
	cloudyBorderIntensity?: number,


Image annotations are used to annotate a PDF with images.

Image annotation
declare type ImageAnnotation = BaseAnnotation & {
	type: 'pspdfkit/image',
	// A description of the image, e.g. "PSPDFKit Logo."
	description?: string,
	// Only if one can be retrieved.
	fileName?: string,
	contentType?: 'image/jpeg' | 'image/png' | 'application/pdf',
	// Either the SHA256 hash of the attachment or the `pdfObjectId` of the attachment.
	imageAttachmentId?: string,
	rotation: Rotation,
	note?: string,


A stamp annotation represents a stamp in a PDF. The image of the stamp depends upon the stampType, which can be one of the following:

  • Accepted

  • Approved

  • AsIs

  • Completed

  • Confidential

  • Departmental

  • Draft

  • Experimental

  • Expired

  • Final

  • ForComment

  • ForPublicRelease

  • InformationOnly

  • InitialHere

  • NotApproved

  • NotForPublicRelease

  • PreliminaryResults

  • Rejected

  • Revised

  • SignHere

  • Sold

  • TopSecret

  • Void

  • Witness

  • Custom

If the stampType is set to Custom, the title, subtitle, and color will define the appearance of the stamp annotation.

Stamp Annotations
declare type StampAnnotation = BaseAnnotation & {
  type: "pspdfkit/stamp",
    | "Accepted"
    | "Approved"
    | "AsIs"
    | "Completed"
    | "Confidential"
    | "Departmental"
    | "Draft"
    | "Experimental"
    | "Expired"
    | "Final"
    | "ForComment"
    | "ForPublicRelease"
    | "InformationOnly"
    | "InitialHere"
    | "NotApproved"
    | "NotForPublicRelease"
    | "PreliminaryResults"
    | "Rejected"
    | "Revised"
    | "SignHere"
    | "Sold"
    | "TopSecret"
    | "Void"
    | "Witness"
    // Not a standard stamp. Displays arbitrary text in the title and subtitle.
    | "Custom",
  title?: string,
  subtitle?: string,
  color?: Color,
  rotation: Rotation,
  note?: string


Attachments were added to support syncing annotation attachments across different devices. The keys in attachments are the calculated SHA256 hashes of the attachment or the pdfObjectId of the attachment. The binary of the attachment is Base64 encoded:

declare type Attachments = {
	[string]: {
		binary: string,

Form Field Values

Form field values were added as new Instant types in order to sync form field values across different devices. The name of a form field value must always be the fully qualified name of a PDF form field:

declare type FormFieldValue = {
	v: 1,
	type: 'pspdfkit/form-field-value',
	name: string,
	// Multiple values are allowed for combo boxes, list boxes, and checkboxes.
	value?: string | Array<string>,


Bookmarks provide a way to mark actions. Optionally, a bookmark can have a name, in which case clients will show the name of the bookmark in their bookmarks toolbar.

The data is structured in the following way:

  • v — The version of the bookmark specification.

  • pdfBookmarkId — The ID under which the bookmark will be stored in the PDF.

  • type — The type of the entry. For bookmarks, this will always be “pspdfkit/bookmark”.

  • name — The optional bookmark name. This is used to identify the bookmark.

  • action — The action that should be triggered when this bookmark is clicked. See action types for more information about this field.

declare type Bookmark = {
	v: 1,
	pdfBookmarkId?: string,
	type: 'pspdfkit/bookmark',
	name?: string,
	action: Action,

Document Operations

Document operations can be used to specify how to edit a document via the relevant server APIs (please see the sections about applying operations and persisting operations for more information):

type Rotation = 0 | 90 | 180 | 270;

type AddPageConfiguration = {
	backgroundColor: string, // #RRGGBB or rgb(number, number, number).
	pageWidth: number,
	pageHeight: number,
	rotateBy: Rotation,
	insets?: [number, number, number, number],

type PageIndex = number | 'first' | 'last';
type PageIndexes = Array<PageIndex> | 'all';

type PageIndexOrRange = PageIndex | [number, number];
type PageIndexesOrRanges = Array<PageIndexOrRange>;

type Document = string | {| id: string, layerName?: string |};

type DocumentOperation =
	| {| type: 'addPage', afterPageIndex: PageIndex, ...AddPageConfiguration |}
	| {| type: 'addPage', beforePageIndex: PageIndex, ...AddPageConfiguration |}
	| {| type: 'duplicatePages', pageIndexes: PageIndexes |}
	| {|
			type: 'movePages',
			pageIndexes: PageIndexes,
			afterPageIndex: PageIndex,
	| {|
			type: 'movePages',
			pageIndexes: PageIndexes,
			beforePageIndex: PageIndex,
	| {| type: 'rotatePages', pageIndexes: PageIndexes, rotateBy: Rotation |}
	| {| type: 'keepPages', pageIndexes: PageIndexes |}
	| {| type: 'removePages', pageIndexes: PageIndexes |}
	| {| type: 'setPageLabel', pageIndexes: PageIndexes, pageLabel: string |}
	| {|
			type: 'importDocument',
			afterPageIndex: PageIndex,
			importedPageIndexes: PageIndexesOrRanges,
			treatImportedDocumentAsOnePage: boolean,
			document: Document,
	| {|
			type: 'importDocument',
			beforePageIndex: PageIndex,
			importedPageIndexes: PageIndexesOrRanges,
			treatImportedDocumentAsOnePage: boolean,
			document: Document,
	| {|
			type: 'applyXfdf',
			dataFilePath: string,
	| {|
			type: 'applyInstantJson',
			dataFilePath: string,
	| {|
			type: 'performOcr',
			pageIndexes: Array<PageIndex>,
			language: string,
	| {|
			type: 'flattenAnnotations',
	| {|
			type: 'updateMetadata',
			metadata: {
				title?: string,
				author?: string,
	| {|
			type: 'applyRedactions',


The importDocument operation allows you to merge a document into the document the operation is applied on. You can upload the document you want to import as part of the same request for applying the operations, or you can provide the ID (and optionally the layer name) of the existing document on Server that you’d like to import.

By providing the importedPageIndexes option, you can select a subset of a document’s pages that will be imported. This option accepts a list of specific page indexes or page ranges in the form of [startIndex, endIndex]. The range, which is the same as the page index, is 0-based and inclusive on both ends, e.g. [0, 2] means the first, second, and third page of the document.

If you’re chaining multiple operations with the importDocument operation, the treatImportedDocumentAsOnePage is useful. When set to true, the imported document is treated as a single page in the operations that follow importDocument. Consider these example operations:

		"type": "importDocument",
		"beforePageIndex": "first",
		"treatImportedDocumentAsOnePage": true,
		"docoument": { "id": "my-document" }
		"type": "rotatePages",
		"pageIndexes": ["first"],
		"rotateBy": 90

First, all pages of the existing document stored on Server with the ID “my-document” will be imported before the target document’s first page. Then, since treatImportedDocumentAsOnePage is set to true, the imported document is treated as a single page, and thus the rotatePages operation will rotate all the imported pages by 90 degrees.


The performOcr operation allows you to run OCR on your document.

For a list of all languages supported by the performOcr operation, see here.


The flattenAnnotations operation permanently embeds all currently existing annotations into a document. That means they can no longer be modified in any way.


The updateMetadata operation allows you to change the title and author metadata stored in your document. When using this operation to change the document title, the title returned by various endpoints — including GET /api/documents/:document_id/document_info — is also changed.


The applyRedactions operation will apply all redaction annotations that exist in the document. It’s always run as the last step, no matter where it’s placed in the list of operations. After redactions are applied, the redaction annotations are removed from the document and replaced with their configured appearance. All content that was covered by the redaction annotations is permanently and irreversible removed.

Page Index Placeholders

When applying an operation that requires one or more page indexes, there are special placeholders supported that make it easier:

  • "all" — Can be used wherever a list of page indexes is required and will automatically be replaced by a list of all page indexes in the document the operation is applied to.
    For example: In a document with five pages, pageIndexes: "all" would become pageIndexes: [0, 1, 2, 3, 4].

  • "first" — Can be used inside a list of page indexes and wherever a single page index is required, such as beforePageIndex and afterPageIndex. It will always be replaced by 0.
    For example: afterPageIndex: "first" would become afterPageIndex: 0. Similarly, pageIndexes: ["first", 1 2] would become pageIndexes: [0, 1, 2].

  • "last" — Can be used inside a list of page indexes and wherever a single page index is required, such as beforePageIndex and afterPageIndex. It will be replaced by the index of the last page of the document.
    For example: In a document with five pages, afterPageIndex: "last" would become afterPageIndex: 4. Similarly, pageIndexes: ["last", 1 2] would become pageIndexes: [4, 1, 2].

ℹ️ Note: The placeholders are evaluated before any operations are applied, so if any operation changes the count of pages by deleting or adding them, this won’t be reflected by the placeholders. In these situations, you need to explicitly specify the page indexes, taking into consideration how the operations affect them.


The Instant Comments feature allows users to collaborate on and discuss parts of a document in real time. You can use the PSPDFKit Server comments APIs to add and retrieve comments in a document:

type Comment = {
  // The comment text.
  text: string
  // The name of the comment author.
  creatorName?: string,
  // The date of the comment creation.
  createdAt?: Timestamp,
  // The date of the last comment update.
  updatedAt?: Timestamp,
  // Custom attributes of the comment.
  customData?: { [key: string]: any }

Annotation Replies are not supported in Instant JSON.