To split a PDF by size in Python, append consecutive pages to a PdfWriter, serialize each candidate to a byte buffer, measure the actual generated bytes, and finalize a part before the next page would push it past a chosen MiB target. The decision must be based on the real output size of the candidate PDF, not on a proportional estimate from the source file, because copied pages carry fonts, images, and metadata that serialize differently each time they are recombined. A naive equal-share guess — for example, dividing the source bytes by page count — routinely produces parts that overshoot the limit or leave the last part mostly empty. The measured-bytes approach mirrors what a careful no-code tool does: keep page order as an unbreakable rule, start a new part only at page boundaries, and flag any single page that already exceeds the target on its own. The same algorithm runs entirely inside the browser in Split PDF by Size, so the result is identical whether you write the loop yourself in Python or paste the source PDF into a local tool.

What Splitting a PDF by Size Actually Measures
A PDF is not a flat byte stream. Each page is a collection of objects: text, vector paths, embedded images, fonts, color profiles, and metadata. When you copy a page from one document into another, the new document rebuilds the cross-reference table and re-serializes the object stream, so the resulting file size depends on the destination structure rather than on the source page's footprint in the original document. Shared resources between pages are common — the same font dictionary, the same image XObject — but once those pages are split into different files, the resources may be copied twice or recompressed differently. PDF resource sharing is also one of the reasons a page taken on its own can be larger than the same page sitting next to a sibling that uses the same font. Any size-based split must therefore re-serialize the candidate and read its actual byte length, not estimate.
Three quantities matter when a planner picks the boundary between two parts: the configured target, the byte length of the candidate that includes the next page, and the byte length of the current group without the next page. The planner accepts the next page into the current group only when the candidate stays at or below the target. When the candidate would cross the target, the planner finalizes the current group as a part and begins a new part with the rejected page as its first member. The target is expressed in MiB, where 1 MiB equals 1,048,576 bytes, and the tool accepts decimal input to three places. The minimum 0.01 MiB exists so a deterministic test can force a multi-part split on a tiny document, and the maximum 25 MiB matches the bounded local-file workflow of the tool.
Python Approach: Split a PDF by Size With pypdf
The most direct Python route is pypdf, which exposes PdfReader for reading the source document and PdfWriter for building new ones. Each candidate part is a fresh PdfWriter, and the size check is performed by writing the candidate to an in-memory BytesIO buffer and reading len(buf.getvalue()) rather than asking the writer object for an estimate. The loop is greedy: it tries to grow the current part one page at a time and only finalizes when the next page would push the candidate over the target. Single pages that already exceed the target are kept in their own part so no page is silently lost.
The full flow is short enough to outline in prose. Import pypdf, BytesIO, and os, then set MAX_BYTES to your target in bytes (multiply your MiB target by 1,048,576). Create OUT_DIR with os.makedirs. Open the source with reader = PdfReader('input.pdf') and an empty current = PdfWriter(), with part_index starting at 0. Define a helper candidate_byte_length(writer, page) that copies the existing writer pages plus the new page into a probe PdfWriter, writes it to BytesIO, and returns the buffer's length. Walk reader.pages one page at a time. If current already has pages and the candidate that includes the next page is longer than MAX_BYTES, write current to part_{part_index:03d}.pdf, increment part_index, and reset current = PdfWriter(). After the loop, write the trailing current to the next numbered file if it still contains pages. MAX_BYTES and OUT_DIR are the only values you need to change for a different document.
To translate a human-readable MiB target into the byte value the comparison uses, multiply the target by 1,048,576. For a target of 2.5 MiB, the calculation is 2.5 × 1,048,576 = 2,621,440 bytes. With that target in force, the planner accepts a candidate whose serialized byte length is less than or equal to 2,621,440 bytes and rejects a candidate that exceeds it. In one observed run, pages 1 through 4 serialized to 1,820,000 bytes — well under the limit — while a candidate that also included page 5 reached 2,950,000 bytes, which is greater than 2,621,440, so pages 1 through 4 became the first part and page 5 began the second. That single concrete sequence captures the entire decision rule without iteration.
Two practical refinements belong alongside the loop. First, if a candidate that contains only the new page already exceeds the target, the page must still be written as its own part and clearly labelled, because a Python loop that retries a smaller subgroup would loop forever or silently drop the page. Second, the target unit is MiB, not MB: 1 MB is 1,000,000 bytes and 1 MiB is 1,048,576 bytes, and confusing them shifts the boundary by roughly five percent on a typical part.
How to Split a PDF by Size in Your Browser
If maintaining a Python environment is more friction than the task deserves, the browser version of the same algorithm is available as Split PDF by Size. The whole job — page copying, serialization, byte measurement, download creation — runs locally in the current tab. The page never sends the source file or the generated parts to a remote server, so the workflow is safe for documents that cannot leave the device.
- Choose a non-empty PDF no larger than 25 MiB from your local file system.
- Enter the maximum generated size per part as a decimal MiB value between 0.01 and 25, with up to three decimal places.
- Run the split, then review the summary for any single page that was kept in its own part because it already exceeded the target.
- Download each numbered part. Changing the source file or the size target invalidates earlier download URLs so an old result cannot be confused with the current settings.
Files that are empty, larger than 25 MiB, encrypted, damaged, mislabeled, or unsupported produce a visible error and do not produce any output. Password protection is not bypassed: the tool does not try to decrypt the file, so removing a password before splitting is a separate workflow.
How the Measured-Bytes Planner Reaches a Decision
Both the Python loop and the browser tool use the same three-step decision per page: serialize the candidate that includes the next page, read the candidate's byte length, and compare it to the target. If the candidate fits, the page joins the current group. If the candidate would overshoot, the current group is finalized and the rejected page begins a new group. The greedy choice is deliberate: a backtracking planner that previews every possible split boundary could in theory pack parts more tightly, but it would also be slower, harder to explain, and would still face the same single-page exception.
Page order is treated as unbreakable, which means the tool never reorders pages, never skips a page, and never duplicates a page. The sum of the page counts shown across all generated parts therefore equals the structural page count of the source PDF. The underlying page-copy and save operations are exposed by the pdf-lib PDFDocument.copyPages API and the PDFDocument.save API, which the browser tool uses to perform the same copying and serialization steps that the Python sketch performs with pypdf.
Limits, Edge Cases, and What Survives the Split
The split workflow is bounded on both ends. The source document must be at most 25 MiB and non-empty, and the per-part target must sit between 0.01 and 25 MiB. Outside those ranges the tool reports a visible error rather than producing a partial result. A target that is too small for the largest page in the document will trigger the single-page exception: that page is kept in its own part, the part is flagged as exceeding the target, and the user is told to choose a larger target or to compress the source first. The decision to keep the oversized page rather than retry prevents silent page loss and prevents an infinite retry loop.
Each output is a fresh PDF built by copying the selected source pages into a new document. Page boxes, rotation, vector artwork, text, and embedded raster content are retained to the extent supported by the copy operation. Advanced document-level structures such as bookmarks, scripts, attachments, portfolio relationships, named destinations, and cross-part links may not preserve their original semantics once pages are separated, so interactive forms and navigation-heavy documents should be inspected before the parts are circulated. If a downstream system rejects an oversized single-page part, the right next step is a compression pass rather than a smaller split target.
Python vs a Local Browser Tool
The two approaches solve the same problem but differ in setup, control, and reproducibility. The table below compares them on the dimensions that matter when you are choosing between them for a given document.
| Dimension | Python loop with pypdf | Split PDF by Size (browser) |
|---|---|---|
| Where it runs | Your local Python environment | The current browser tab |
| Network upload | None, unless your script sends the file out | None — pages, serialization, and downloads stay in the tab |
| Setup cost | Install pypdf, write and test the loop, choose an output directory | Open the page and pick a file |
| Size decision | Serialized candidate byte length via BytesIO | Serialized candidate byte length via pdf-lib save |
| Single oversized page | Kept as its own part if the script follows the same rule | Kept as its own part and visibly flagged |
| Source size cap | Only what your script and disk can hold | 25 MiB, enforced |
| Page order | Preserved as long as pages are appended in source order | Preserved as an unbreakable rule |
| Best fit | Repeatable batch jobs, CI pipelines, custom output naming | One-off splits, sensitive documents, no-code workflows |
Pick Python when the split is part of a larger pipeline, when you want to commit the boundary logic under version control, or when you need a custom output naming scheme. Pick the browser tool when the document cannot leave the device, when you do not want to maintain a Python environment, or when you need a quick split with a clear visual summary of which parts (if any) overshot the target. For deeper coverage of the measured-bytes design, the guide on splitting a PDF by size with MiB targets and page boundaries walks through the same algorithm from a different angle.
Related reading: How to Split PDF Pages in Half Without Acrobat.