Build Portable Document templates
Turn an uploaded PDF into a Portable Document template, then rename, delete, fetch, or list a project's templates.
A Portable Document is a PDF template scoped to a workspace. You upload a PDF, Blue rasterizes each page into a PNG, and the resulting template becomes a canvas you overlay record fields onto so you can later print a filled PDF for any specific record. Workspaces are Project objects in the API, and a template is a PortableDocument.
This is a separate subsystem from the collaboratively-edited rich-text Documents — they share the “Documents” section but no model, no fields, and no operations. Portable Documents are about laying record data onto a fixed PDF layout; rich-text Documents are about editing prose.
This page covers the template lifecycle: create one from an uploaded file, rename it, delete it, fetch one, list a workspace’s templates, and link a template to record tables. Placing the overlay fields and printing a filled PDF are covered in Map fields and print Portable Documents.
Create a template
Use the createPortableDocument mutation to build a template from a PDF that has already been uploaded. You pass the workspace and the uploaded file; Blue fetches the file from storage, renders each page to a PNG, and stores the pages on the new template.
mutation CreatePortableDocument {
createPortableDocument(input: { projectId: "project_123", fileId: "file_123" }) {
id
uid
name
pages {
id
url
}
createdBy {
id
fullName
}
}
}The name is taken from the source file’s name — there is no name argument on create. Use updatePortableDocument afterwards to rename it.
CreatePortableDocumentInput
| Parameter | Type | Required | Description |
|---|---|---|---|
projectId | String! | Yes | Workspace the template belongs to. Accepts the project ID or slug. |
fileId | String! | Yes | The uploaded PDF to convert. Resolved from the upload, so a file ID, storage key, or URL is accepted. |
See Upload files for how to obtain a fileId.
Response
{
"data": {
"createPortableDocument": {
"id": "clm4n8qwx000008l0g4oxdqn7",
"uid": "pdf_clm4n8qwx000108l0h2abc456",
"name": "Invoice template.pdf",
"pages": [
{ "id": "clm4n8qwx000208l0p1ghi789", "url": "pdf_clm4n8qwx000108l0h2abc456/page_1" },
{ "id": "clm4n8qwx000308l0p2jkl012", "url": "pdf_clm4n8qwx000108l0h2abc456/page_2" }
],
"createdBy": { "id": "clm4n8qwx000408l0u1mno345", "fullName": "Ada Lovelace" }
}
}
}The PortableDocument type
| Field | Type | Description |
|---|---|---|
id | ID! | Unique template identifier. |
uid | String! | Prefixed unique key (pdf_…); also the storage prefix for the page images. |
name | String! | Display name, derived from the source file. |
isActive | Boolean! | Whether the template is active. Inactive templates cannot be printed or exported; they stay editable in settings. New templates are active. |
createdAt | DateTime! | When the template was created. |
updatedAt | DateTime! | When the template was last modified. |
createdBy | User! | The user who created the template. |
project | Project! | The workspace the template belongs to. |
pages | [PortableDocumentPage!]! | The rendered pages, in order. |
databaseLinks | [PortableDocumentDatabaseLink!]! | Links that bind the template’s overlay fields to the fields of record tables. Empty when the template has no links. |
file | File | The source file, if still referenced. |
PortableDocumentPage
| Field | Type | Description |
|---|---|---|
id | ID! | Unique page identifier. |
uid | String! | Prefixed unique key for the page. |
url | String! | Storage key of the rendered PNG (the {document-uid}/page_{n} key). |
createdAt | DateTime! | When the page was created. |
updatedAt | DateTime! | When the page was last modified. |
document | PortableDocument! | The template this page belongs to. |
fields | [PortableDocumentField!]! | Overlay fields placed on this page. |
The fields list is populated by createPortableDocumentField; see Map fields and print Portable Documents.
Rename a template
Use the updatePortableDocument mutation to change a template’s name. The only mutable field is name.
mutation RenamePortableDocument {
updatePortableDocument(input: { id: "pdf_123", name: "Q3 invoice" }) {
id
name
}
}UpdatePortableDocumentInput
| Parameter | Type | Required | Description |
|---|---|---|---|
id | String! | Yes | The template to rename. |
name | String | No | New name. A .pdf suffix is appended automatically; omit it to keep the current name. |
isActive | Boolean | No | Set to false to hide the template from printing and PDF export. |
{
"data": {
"updatePortableDocument": {
"id": "clm4n8qwx000008l0g4oxdqn7",
"name": "Q3 invoice.pdf"
}
}
}Delete a template
Use the deletePortableDocument mutation to permanently remove a template and its rendered page images from storage. It returns Boolean — true on success — so it takes no sub-selection.
mutation DeletePortableDocument {
deletePortableDocument(id: "pdf_123")
}{
"data": {
"deletePortableDocument": true
}
}| Argument | Type | Required | Description |
|---|---|---|---|
id | String! | Yes | The template to delete. |
Fetch one template
Use the portableDocument query to read a single template by ID, including its pages and the overlay fields placed on each page.
query GetPortableDocument {
portableDocument(id: "pdf_123") {
id
name
createdBy {
fullName
}
pages {
id
url
fields {
id
field
positionX
positionY
}
}
}
}| Argument | Type | Required | Description |
|---|---|---|---|
id | String! | Yes | The template to fetch. |
{
"data": {
"portableDocument": {
"id": "clm4n8qwx000008l0g4oxdqn7",
"name": "Q3 invoice.pdf",
"createdBy": { "fullName": "Ada Lovelace" },
"pages": [
{
"id": "clm4n8qwx000208l0p1ghi789",
"url": "pdf_clm4n8qwx000108l0h2abc456/page_1",
"fields": [
{
"id": "clm4n8qwx000508l0f1pqr678",
"field": "title",
"positionX": 72,
"positionY": 540
}
]
}
]
}
}
}List a project’s templates
Use the portableDocuments query to list every template in a workspace, newest-updated first, with offset pagination. It returns a PortableDocumentPagination of { items, pageInfo }.
query ListPortableDocuments {
portableDocuments(filter: { projectId: "project_123" }, skip: 0, take: 20) {
items {
id
name
updatedAt
}
pageInfo {
totalItems
hasNextPage
}
}
}Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
filter | PortableDocumentFilter! | Yes | Scopes the list to one workspace (below). |
skip | Int | No | Items to skip for offset pagination. Defaults to 0. |
take | Int | No | Items per page. Schema default is 20; omitting it falls back to 200. |
PortableDocumentFilter
| Field | Type | Required | Description |
|---|---|---|---|
projectId | String! | Yes | Workspace to list templates from. This filter is matched by project ID. |
{
"data": {
"portableDocuments": {
"items": [
{
"id": "clm4n8qwx000008l0g4oxdqn7",
"name": "Q3 invoice.pdf",
"updatedAt": "2026-05-29T14:02:11.000Z"
},
{
"id": "clm4n8qwx000608l0g5stu901",
"name": "NDA.pdf",
"updatedAt": "2026-05-21T09:18:44.000Z"
}
],
"pageInfo": {
"totalItems": 2,
"hasNextPage": false
}
}
}
}PageInfo
| Field | Type | Description |
|---|---|---|
totalItems | Int | Total templates matching the filter. |
totalPages | Int | Total pages at the current take. |
page | Int | Current page number. |
perPage | Int | Items per page. |
hasNextPage | Boolean! | Whether a further page exists. |
hasPreviousPage | Boolean! | Whether a previous page exists. |
Link a template to record tables
Use the setPortableDocumentDatabaseLinks mutation to bind a template’s overlay fields to the fields of record tables (RecordDatabase). The link maps each template placement to a table field, so a batch PDF export of a table’s records can fill the template from each record.
mutation LinkPortableDocument {
setPortableDocumentDatabaseLinks(
input: {
documentId: "pdf_123"
links: [{ databaseId: "table_123", fieldMappings: { placement_field_1: "field_123" } }]
}
) {
id
databaseLinks {
databaseId
database {
name
}
fieldMappings
}
}
}The call replaces the template’s links. Pass every link you want to keep; pass an empty links list to remove all of them.
SetPortableDocumentDatabaseLinksInput
| Parameter | Type | Required | Description |
|---|---|---|---|
documentId | String! | Yes | The template to link. |
links | [PortableDocumentDatabaseLinkInput!]! | Yes | The full replacement set of table links. |
PortableDocumentDatabaseLinkInput
| Field | Type | Required | Description |
|---|---|---|---|
databaseId | ID! | Yes | A record table to link. It must be a live table in the template’s workspace, and one link per table. |
fieldMappings | JSON! | Yes | Object mapping a template placement id to a table field id ({ placementId: fieldId }). |
Mapping rules
The mapping is validated against the template’s live placements and the table fields you can see:
- Record-intrinsic placements (
workspace,createdBy,completedBy,printedAt,createdAt,updatedAt,completedAt) fill from the record itself and need no mapping. - Every other placement needs a mapping to a mounted field visible to you.
- The mapped field’s type must match the placement. Built-in placements require these field types:
| Placement | Required field type |
|---|---|
title | Single-line text (TEXT_SINGLE) |
description | Multi-line text (TEXT_MULTI) |
assignees | Assignee (ASSIGNEE) |
list | Single select (SELECT_SINGLE) |
tags | Multi select (SELECT_MULTI) |
startedAt | Date (DATE) |
duedAt | Date (DATE) |
- A
customFieldplacement is typed by its custom field. If that custom field no longer exists, the mapped field supplies both the identity and the type, and the type must be one the PDF renderer covers (TEXT_SINGLE,TEXT_MULTI,CHECKBOX,DATE,ASSIGNEE,SELECT_SINGLE,SELECT_MULTI,FILE,NUMBER). - Mapping keys for placements that no longer exist are dropped.
PortableDocumentDatabaseLink
| Field | Type | Description |
|---|---|---|
databaseId | ID! | The linked record table’s id. |
database | RecordDatabase! | The linked record table. |
fieldMappings | JSON! | The normalized placement-to-field map in storage. |
Errors
| Code | When |
|---|---|
PROJECT_NOT_FOUND | createPortableDocument — projectId matches no workspace. |
FILE_NOT_FOUND | createPortableDocument — fileId can’t be resolved to an uploaded file. |
BAD_USER_INPUT | createPortableDocument — the source is not application/pdf, exceeds 10 pages, or conversion times out at 15s. Also setPortableDocumentDatabaseLinks — any mapping-validation failure. |
PORTABLE_DOCUMENT_NOT_FOUND | updatePortableDocument, deletePortableDocument, portableDocument, or setPortableDocumentDatabaseLinks — no template with that id. |
FORBIDDEN | The caller is not a member of the template’s workspace (on delete and single-fetch), or lacks contribute permission (on setPortableDocumentDatabaseLinks). |
Permissions
All Portable Document operations require an authenticated caller. Beyond authentication:
portableDocumentanddeletePortableDocumentadditionally require the caller to be a member of the workspace the template belongs to; otherwise they returnFORBIDDEN.createPortableDocumentresolves the workspace fromprojectIdand attributes the new template to the caller viacreatedBy.setPortableDocumentDatabaseLinksrequires contribute permission on the template’s workspace.