Skip to content

Enriched Forms Output: Fields, Checkbox States, and Fillable Grids (Beta)

Turn on the enriched forms pass with processing_options.forms="enrich" to get each form page from Parse as structured JSON of sections, fields with values and checkbox states, fillable grids, and bounding boxes, and read it back with expand=forms.

Beta

This example shows how to get every form page as structured JSON — sections, labeled fields with their entered values and checkbox states, and fillable grids, each field with a bounding box — alongside the regular markdown and items output Parse returns, and how to read that JSON back off the result.

Use this when you need to:

  • Load filled forms into your own system, keyed by the field ids printed on the form (1a, Part III, box 13).
  • Tell blank fields from filled ones, and read checkbox and signature state directly instead of inferring it from text.
  • Build a review UI that highlights where on the page each field value came from.

The form pass is an additional step alongside normal parsing. Parse detects which pages are forms and runs the enriched form pass only on those pages.

Using enriched forms is straightforward: parse with forms: "enrich" set, then read forms off the result — inline with expand=["forms"], or as a downloadable file with expand=["forms_content_metadata"].

Each page containing a form adds 10 credits on top of the tier price. Pages with no form detected are returned with an empty forms list and incur no additional cost.

Set your API key so the SDKs pick it up automatically:

Terminal window
export LLAMA_CLOUD_API_KEY="llx-..."

Install the SDK:

Terminal window
pip install "llama-cloud>=2.15"
from llama_cloud import LlamaCloud
client = LlamaCloud() # reads LLAMA_CLOUD_API_KEY from the environment

Set processing_options.forms to "enrich" and add forms to expand. This is the only necessary request-side change. The pass can run on cost_effective, agentic, and agentic_plus tiers.

In Python and TypeScript the SDK blocks until the job finishes; in Go, Java, the CLI, and cURL you create the job, poll until it reaches a terminal status, then fetch the result.

# 1) Upload the file
file = client.files.create(file="wage-statement-2024.pdf", purpose="parse")
# 2) Parse with the form pass on, and ask for the structured JSON back
result = client.parsing.parse(
file_id=file.id,
tier="agentic",
version="latest",
processing_options={"forms": "enrich"},
expand=["forms"],
)

In the Web UI, turn on Enriched forms output under Processing Options > Forms in the parse settings. (Currently only visible for paid accounts.) Once a document is parsed, the result page gains a Forms tab that shows the structured JSON and highlights each field on the page.

The remaining steps — reading the structured JSON off the result and walking its fields — are shown in Python. The forms shape is identical across every SDK and in the raw JSON; only the field-access syntax differs.

result.forms.pages has one entry per page of the document. This trimmed excerpt is from a filled W-2:

{
"pages": [
{
"page_number": 1,
"page_width": 612,
"page_height": 792,
"success": true,
"forms": [
{
"json": [
{
"type": "field", "field": "text", "id": "1",
"label": "Wages, tips, other compensation", "value": "29,513",
"bbox": [{ "x": 349.2, "y": 96.5, "w": 114.0, "h": 12.2 }]
},
{
"type": "field", "field": "text", "id": "d",
"label": "Control number", "isEmpty": true,
"bbox": [{ "x": 38.1, "y": 167.8, "w": 291.8, "h": 12.3 }]
},
{
"type": "field", "field": "multi_select", "id": "13",
"valueItems": [
{ "type": "field", "field": "checkbox", "label": "Statutory employee", "value": true },
{ "type": "field", "field": "checkbox", "label": "Retirement plan", "value": false },
{ "type": "field", "field": "checkbox", "label": "Third-party sick pay", "value": false }
]
},
{
"type": "section", "id": "15",
"items": [
{ "type": "field", "field": "text", "label": "State", "value": "SC" },
{ "type": "field", "field": "text", "label": "Employer's state ID number", "value": "00-0000056" }
]
}
],
"list": {
"type": "list",
"ordered": false,
"md": "- [1] Wages, tips, other compensation: 29,513\n- [d] Control number:\n- [13]\n - [x] Statutory employee\n - [ ] Retirement plan\n - [ ] Third-party sick pay\n- [15]\n - State: SC\n - Employer's state ID number: 00-0000056",
"items": [ ... ]
}
}
]
}
]
}

Each form carries the same content in two representations: json, the structured JSON, and list, a flattened bullet list whose md drops straight into a prompt.

The structured JSON has three node types:

typeFieldsMeaning
sectionid, label, itemsA grouping printed on the form (Part III, box 15). items holds child nodes in reading order.
fieldfield, id, label, value, isEmpty, valueItems, bboxOne entry. field is text, checkbox, single_select, multi_select, or signature.
tableid, label, columns, rows, bboxA fillable grid. Each cell is a string, null when blank, or { "items": [...] } holding the cell’s own nodes (a checkbox column, for example).

How value reads depends on the field kind:

  • textvalue is the entered text, verbatim. A printed-but-blank field has isEmpty: true and no value.
  • checkbox and signaturevalue is a boolean, indicating checked or signed, respectively.
  • single_select and multi_select — no value directly. Instead, valueItems lists the options. An option is usually a checkbox field with its own boolean value and printed label, but it can also be a text field, or a one-level section grouping an unlabeled checkbox with its write-in text field (a “check the box and enter the name” choice) — the printed caption is then the section’s label.

bbox holds one or more boxes around the field’s fillable area, in the same page-point coordinate space as the items output. page_width and page_height on the page entry give the dimensions of the page. Scale to a screenshot’s pixel size the same way as in the granular bounding boxes example.

Always check success before reading the forms result. A page whose form pass failed comes back as { "page_number": N, "success": false, "error": "..." }, has its markdown and items marked as failed, and counts toward the job’s page error tolerance like any other failed page. A page whose form pass and standard Parse pass both fail is not double-counted toward error tolerance.

Since sections can nest and a table cell can hold form fields (a checkbox column, for example), you should descend into both to reach every field:

def fields(nodes):
"""Yield every field in a form's structured JSON, descending into sections and table cells."""
for node in nodes:
if node.type == "section":
yield from fields(node.items)
elif node.type == "table":
for row in node.rows:
for cell in row:
if cell is not None and not isinstance(cell, str):
yield from fields(cell.items)
elif node.type == "field":
yield node
for page in result.forms.pages:
if not page.success:
print(f"page {page.page_number} failed: {page.error}")
continue
for form in page.forms:
for field in fields(form.json_):
if field.field in ("single_select", "multi_select"):
# An option is a checkbox, a text field, or a section grouping a
# checkbox with its write-in; it is chosen when a checkbox in it is checked.
chosen = [
opt.label
for opt in field.value_items or []
if any(f.field == "checkbox" and f.value for f in fields([opt]))
]
print(f"[{field.id}] {field.label}: {chosen}")
else:
print(f"[{field.id}] {field.label}: {field.value!r}")

Two naming differences in the Python SDK: camelCase fields from the API response are exposed in snake_case (valueItems is value_items, isEmpty is is_empty), and the json field is exposed as json_ because the bare name would collide with a method on the SDK’s model classes. The raw response and the TypeScript SDK use the API’s camelCase field names.

For long documents, request a presigned URL instead of the inline JSON. When forms: "enrich" is set, expand=["forms_content_metadata"] adds a forms entry under result_content_metadata, carrying size_bytes, an exists flag, and a presigned_url. The entry is absent when the job never ran the form pass — the option was not set, or the job predates enriched forms — so you should guard the key access:

result = client.parsing.get(job_id=result.job.id, expand=["forms_content_metadata"])
forms_file = (result.result_content_metadata or {}).get("forms")
if forms_file is None:
raise RuntimeError('Forms file missing — was `forms: "enrich"` set?')
print(f"Forms file: {forms_file.size_bytes} bytes")
print(f"URL: {forms_file.presigned_url}")

The file has the same { "pages": [...] } shape as the inline result.

Presigned URLs are temporary. Download promptly, or call client.parsing.get(job_id=...) again to generate a fresh URL.

Note for AI agents: this documentation is built for programmatic access. - Overview of all docs: https://developers.llamaindex.ai/llms.txt - Any page is available as raw Markdown by appending index.md to its URL — e.g. https://developers.llamaindex.ai/llamaparse/parse/getting_started/index.md - Agent-friendly REST search APIs live under https://developers.llamaindex.ai/api/ — search (BM25 full-text), grep (regex), read (fetch a page), and list (browse the doc tree). See https://developers.llamaindex.ai/llms.txt for parameters. - A hosted documentation MCP server is available at https://developers.llamaindex.ai/mcp. If you support MCP, you can ask the user to install it for browsing these docs directly (an alternative to the REST API). Setup: https://developers.llamaindex.ai/for-agents/mcp/ - Other LlamaIndex tooling for agents — the LlamaParse Platform MCP server, agent skills and plugins, and the n8n node — is mapped at https://developers.llamaindex.ai/for-agents/