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 } },
);| Field | What it is | Default |
|---|---|---|
rect | The icon’s box, which the icon fills. Required. | |
file.name | The file’s name, which PDF apps show and save it under. Required for bare bytes. | the File’s name |
file.mimeType | The file’s type, such as 'text/csv'. | the File’s or Blob’s type, or null |
file.description | A short description of the file. | null |
icon | 'paperclip', 'push-pin', 'graph' or 'tag' | 'paperclip' |
color | The icon’s color, '#rrggbb' | yellow |
opacity | From 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:
| Field | What it is |
|---|---|
size | The file’s size in bytes. |
checksum | The MD5 of the file’s bytes, in lowercase hex. |
createdAt | When the file was attached, as an ISO date. |
modifiedAt | When 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
filebytes is refused withInvalidArg. - 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.
Your feedback goes directly to the documentation team.