pdf_split
Select PDF pages using individual numbers or ranges. Write one file per page by
default, or opt into multipage slices with pages_per_file.
Parameters
- pathstringone of
pathorsource_key - File path to the PDF; supports
${ctx.key}interpolation. - source_keystringone of
pathorsource_key - Context key containing a file path, artifact URI, or artifact descriptor.
- output_dirstringrequired
- Directory for output files; supports
${ctx.key}interpolation. - pagesstringdefault "all"
- Page specification:
"all", a single page"3", a range"1-5", or a combination"1-3,7,9-11". Pages are 1-based; supports${ctx.key}interpolation before parsing. - pages_per_fileintegerdefault 1
- Maximum selected pages in each output file, from
1toIRONFLOW_MAX_PDF_SPLIT_PAGES(default1000). Values greater than1enable grouped output. Strings, null, fractions, zero, and negative values are errors. - output_keystringdefault "pdf_split"
- Context key prefix for output values.
Providing both
pathandsource_keyis an error. Artifact inputs are opened and SHA-256 verified inside the tracked blocking worker; PDF parsing consumes that same rewound handle rather than a resolved store pathname.
For example, pages = "${ctx.range}" with range = "31-60" creates 30 one-page
files by default. Add pages_per_file = 30 to produce one 30-page file instead.
Missing or null context values resolve to empty text
and fail the existing page-specification parser. Invalid resolved selectors fail
before creating the output directory. Literal selectors retain their behavior,
including ordering and repeated selections in the default single-page mode.
Grouped output
With pages_per_file > 1, the node selects pages first and groups that sequence
without sorting it. pages = "all" for a 243-page PDF with pages_per_file = 30
creates nine files: eight with 30 pages and the last with three. A selector of
"5,1-3,9" with pages_per_file = 3 creates groups [5,1,2] and [3,9].
Repeated selections, including overlapping ranges, are rejected before creating
the output directory. Multiple selected entries referencing the same source
page object are also rejected in grouped mode.
Grouped filenames are <stem>_part_001.pdf, <stem>_part_002.pdf, and so on,
with a minimum three-digit part number. Part numbers describe selection order,
not original page ranges; use the output metadata for exact source page numbers.
The stem is the source filename without its extension for path inputs, or the
SHA-256 digest for artifact inputs, matching legacy stem selection.
The last group may be smaller than pages_per_file, including one page.
Each group is flushed and synced to a temporary sibling file, then atomically published without overwriting. Existing files, final symlinks (including dangling ones), and nonregular destinations are refused. Destination checks occur per group, with collision protection at publication as well. Directory aliases are resolved using the shared rooted writer; on Unix the resolved directory is pinned by an open handle. Other platforms use the writer's checked-path fallback.
This is per-file atomicity, not an all-or-nothing batch: completed groups remain after a later collision, parse/write failure, or cancellation. Unfinished staging files are removed on normal error/cancellation unwinding, not guaranteed after a process crash. No success output or parts metadata is returned on node failure. Use a fresh output directory for each attempt; an automatic node retry does not resume and will encounter any files already published.
Omitting pages_per_file or setting it to 1 preserves legacy filenames
(<stem>_<source-page>.pdf), repeated file paths for duplicate selections,
existing-file overwrite behavior, direct non-atomic writes, and the three
original output keys. Grouped collision and staging rules do not alter this path.
Limits and cancellation
Page selection is bounded by IRONFLOW_MAX_PDF_SPLIT_PAGES (default 1000).
"all" and explicit or repeated ranges are rejected before the selector
allocates more indices than that ceiling. The source must be a regular file and
is capped by IRONFLOW_MAX_PDF_BYTES (default 100 MiB) before and during reads;
post-open growth cannot cross the cap. Loading, object traversal, and writes run
on a tracked blocking worker with cancellation checkpoints around opaque
lopdf operations and between pages/groups. The source is loaded once; grouped
output does not write or reparse intermediate one-page files. lopdf retains
the bounded source document and one page/group's reachable cloned object graph
while producing that output, so the source byte cap is not an exact RSS ceiling.
A later failure
does not roll back page files already written to output_dir.
Before writing any pages, shared PDF loading enforces
IRONFLOW_MAX_PDF_DECOMPRESSED_STREAM_BYTES (64 MiB) per object/cross-reference
stream and rejects more than IRONFLOW_MAX_PDF_OBJECTS (250000) loaded objects
after parsing. Strict parsing and bounded recovery checks prevent oversized
streams from silently producing partial documents.
Grouped output additionally applies IRONFLOW_MAX_PDF_OBJECTS to each retained
group graph, counting shared resources once plus the new page tree and catalog.
This check runs during graph collection, before cloning beyond that count.
IRONFLOW_MAX_PDF_BYTES also caps each serialized grouped file during staged
writes. It is not a cumulative output-directory byte limit, and grouping is by
page count only: a too-large group fails rather than being automatically divided
again. There is no hardcoded OCR-provider page limit.
Page isolation and fidelity
Each output contains its selected pages and their reachable dependencies, without
copying the source page tree, omitted pages, or their unique resources. Before
reparenting, the node materializes the nearest inherited Resources, MediaBox,
CropBox, and Rotate values. Explicit page values take precedence; null values
are treated as absent. Broken or cyclic parent chains fail before that page/group is
written. Graph collection and remapping include cancellation checkpoints.
References to omitted pages, source page-tree nodes, or the source catalog become PDF nulls, so a cross-page annotation cannot pull an excluded page back into the file. Links between pages retained in the same group and annotation backlinks are remapped normally. Shared fonts and images are retained once per group. Document-level bookmarks, signatures, and source catalog features are not preserved. This is page extraction, not content redaction: retained shared resources, metadata, or streams may contain unused or hidden data and are not sanitized.
Context Output
<output_key>_files(defaultpdf_split_files) — array of output PDF paths in selection/group order.<output_key>_page_count(defaultpdf_split_page_count) — total selected pages, not file count.<output_key>_success(defaultpdf_split_success) —trueon success.<output_key>_parts(defaultpdf_split_parts) — grouped mode only: one object per file, containingpath, ordered 1-based sourcepages, andpage_count.
For example, selecting "5,1-3,9" in groups of three returns:
[
{"path": "/data/parts/document_part_001.pdf", "pages": [5, 1, 2], "page_count": 3},
{"path": "/data/parts/document_part_002.pdf", "pages": [3, 9], "page_count": 2}
]
Example
local flow = Flow.new("split_pdf")
flow:step("select", function(ctx)
return { range = "1-3,5" }
end)
flow:step("split", nodes.pdf_split({
path = "/data/document.pdf",
output_dir = "/data/pages",
pages = "${ctx.range}"
})):depends_on("select")
flow:step("done", nodes.log({
message = "Split into ${ctx.pdf_split_page_count} files"
})):depends_on("split")
return flow
For direct provider-sized slices, use the runnable
pdf_split_grouped.lua
example. It defaults to the bundled three-page fixture and groups of 30. To
test another PDF, run from the repository root:
ironflow run examples/08-extraction/pdf_split_grouped.lua \
--context '{"input_path":"/absolute/path/document.pdf","pages_spec":"all"}'
The example logs source-page metadata and uses a fresh temporary output
directory per run. The same flow works through serve with file and context
in POST /flows/run when its example directory is configured under flows_dir.