EmbedPDF

File attachments

A file attachment pins a file to a spot on a page: a spreadsheet next to a table, a drawing next to a plan. PDF apps show an icon there and open the file from it.

await page.annotations.create(
  {
    subtype: 'file-attachment',
    rect: { x: 40, y: 732, width: 20, height: 20 },
    file: { name: 'figures.csv', mimeType: 'text/csv' },
  },
  { resources: { file: bytes } },
);

The first argument describes the file; its bytes go in the options, as resources.file. A Uint8Array, a Blob or a File works. A File the user picked brings its name and type, so it can go in as it is:

const picked = input.files![0];
 
await page.annotations.create(
  { subtype: 'file-attachment', rect: { x: 40, y: 732, width: 20, height: 20 } },
  { resources: { file: picked } },
);
FieldWhat it isDefault
rectThe icon’s box, which the icon fills. Required.
file.nameThe file’s name, which PDF apps show and save it under. Required for bare bytes.the File’s name
file.mimeTypeThe file’s type, such as 'text/csv'.the File’s or Blob’s type, or null
file.descriptionA short description of the file.null
icon'paperclip', 'push-pin', 'graph' or 'tag''paperclip'
colorThe icon’s color, '#rrggbb'yellow
opacityFrom 0 (invisible) to 1 (solid)1

The icon fills rect, as in every PDF app; 20 × 20 points is the usual size. Move or resize the icon by its rect. Any bytes are a valid file. The engine never guesses a type: without one, mimeType reads null.

Read and download the file#

A read describes the file; downloadResource() returns its bytes, exactly as they were attached:

const { annotations } = await page.annotations.list();
const attachment = annotations.find((annotation) => annotation.subtype === 'file-attachment');
 
if (attachment?.file) {
  console.log(attachment.file.name, attachment.file.size);
  const bytes = await page.annotations.downloadResource(attachment.ref, 'file');
}

A read’s file has the name, type and description you wrote. It also has these fields, which the engine fills in:

FieldWhat it is
sizeThe file’s size in bytes.
checksumThe MD5 of the file’s bytes, in lowercase hex.
createdAtWhen the file was attached, as an ISO date.
modifiedAtWhen the file was last changed.

The engine writes size, checksum and createdAt when it attaches a file. What the PDF doesn’t record reads as null: modifiedAt for a file attached here, and any of the four for some files other apps attached. file itself is null for an attachment another app made without an embedded file.

To let the user save it, wrap the bytes in a Blob:

const blob = new Blob([bytes], { type: attachment.file.mimeType ?? '' });

Replace the file#

file in an update replaces the name, type and description together; new bytes replace the file itself:

await page.annotations.update(attachment.ref, {
  file: { ...attachment.file, name: 'figures-2026.csv' },
});
 
await page.annotations.update(attachment.ref, {}, { resources: { file: newBytes } });

A field of file you leave out is removed, so start from the one you read, as above. size, checksum and the dates in it are ignored: the engine writes those itself. An attachment can’t lose its file (file: null is refused with InvalidArg); delete the annotation instead.

The icon, its color and its opacity change like any field: { icon: 'tag' }.

Copy an attachment#

The read and the bytes together make the same attachment again, on another page or in another document:

const bytes = await page.annotations.downloadResource(attachment.ref, 'file');
 
await otherPage.annotations.create(attachment, { resources: { file: bytes } });

Files on a page and files of the document#

A PDF can also carry files that belong to the document as a whole rather than to a spot on a page. Those are attachments, with their own calls. The two don’t mix: doc.attachments.list() doesn’t list the files pinned to pages, and the calls on this page don’t see the document’s files.

Limits#

  • A name can’t be empty, and a create without the file bytes is refused with InvalidArg.
  • To export an attachment, its file must fit a bundle’s limit for one resource: 50 MB. A larger one refuses the export with PayloadTooLarge.

Permissions#

Creating, changing and deleting file attachment annotations (not doc.attachments.modify) needs the same permissions as any annotation (see Permissions). Downloading the file sends document content out, so it needs doc.download.

Good to know#

Dates#

createdAt is written when the file is attached, in UTC. An import keeps the source file’s dates, whichever attribution it uses: they’re facts about the file, not about who wrote the annotation.

Icons from other apps#

In the PDF, icon is the annotation’s /Name. A name other than the four standard ones reads as 'push-pin', which is also what PDF apps draw for it.

How a copy looks#

A copy’s icon is the engine’s own icon, filling the same rect, whatever icon another app drew for the original.

Was this page helpful?

Your feedback goes directly to the documentation team.