To rearrange PDF pages for booklet printing or other physical output, you need more than swapping two slides in a presentation deck. While screen PDFs are arranged in sequential reader spreads (1, 2, 3, 4, 5, 6, 7, 8), duplex printers, saddle-stitch booklets, and single-pass document scanners operate on physical sheets where four logical pages share a single folded leaf or where odd and even sides are captured in separate passes.

How PDF Page Order and Page Labels Actually Work

In the PDF specification (ISO 32000-1, Section 7.7.3), pages are not ordered by their object ID numbers or byte positions in the file. Instead, the document catalog points to a /Pages tree whose /Kids array lists indirect object references (4 0 R, 9 0 R, etc.) in display sequence. When you rearrange pages in a PDF, a compliant tool only reorders that /Kids reference array or copies the /Page dictionaries into a new /Kids list—the underlying font subsets, vector paths, and compressed image streams are never re-encoded.

To verify how /Kids references and viewer page labels behave when pages move, we can create a 6-page US Letter (612 x 792 pt) manual (manual_6p.pdf) with Roman numeral front-matter labels (i, ii) on the first two pages and Arabic labels (1–4) on the remaining four pages. On both macOS (Homebrew Python) and Ubuntu/Debian Linux, PEP 668 blocks system-wide pip installs by default, so create a virtual environment first:

python3 -m venv .venv
.venv/bin/pip install pypdf pymupdf

Run inspect_page_tree.py to generate manual_6p.pdf, move Page 6 to the front in place using PyMuPDF (pymupdf), and compare the /Kids array and /PageLabels:

import pymupdf
from pypdf import PdfReader

doc = pymupdf.open()
titles = [
    "Page 1: Front Cover",
    "Page 2: Inside Front Cover & TOC",
    "Page 3: Setup & Wiring Diagram",
    "Page 4: Calibration Procedure",
    "Page 5: Troubleshooting Table",
    "Page 6: Back Cover & Support",
]
for i, title in enumerate(titles, start=1):
    page = doc.new_page(width=612, height=792)
    page.insert_text((72, 100), title, fontsize=22, fontname="helv")
    page.insert_text((72, 140), f"Original sequential page {i} of 6 (612 x 792 pt US Letter)", fontsize=12, fontname="helv")
    page.draw_rect(pymupdf.Rect(54, 54, 558, 738), color=(0.2, 0.2, 0.2), width=1.5)

doc.set_page_labels([
    {"startpage": 0, "prefix": "", "style": "r", "firstpagenum": 1},
    {"startpage": 2, "prefix": "", "style": "D", "firstpagenum": 1},
])
doc.save("manual_6p.pdf", garbage=4, deflate=True)

# Move index 5 (Page 6) to index 0 in-place
doc.move_page(5, 0)
doc.save("reordered_movepage.pdf", garbage=4, deflate=True)
doc.close()

for fname in ["manual_6p.pdf", "reordered_movepage.pdf"]:
    r = PdfReader(fname)
    kids = [f"{ref.idnum} 0 R" for ref in r.trailer["/Root"]["/Pages"]["/Kids"]]
    print(f"{fname:24s} /Kids = [{', '.join(kids)}]")

r_move = PdfReader("reordered_movepage.pdf")
for i, p in enumerate(r_move.pages):
    print(f"  Slot {i + 1} (viewer label={r_move.page_labels[i]!r:<4}): {p.extract_text().splitlines()[0]}")

Output:

manual_6p.pdf            /Kids = [4 0 R, 9 0 R, 12 0 R, 15 0 R, 18 0 R, 21 0 R]
reordered_movepage.pdf   /Kids = [21 0 R, 4 0 R, 9 0 R, 12 0 R, 15 0 R, 18 0 R]
  Slot 1 (viewer label='i' ): Page 6: Back Cover & Support
  Slot 2 (viewer label='ii'): Page 1: Front Cover
  Slot 3 (viewer label='1' ): Page 2: Inside Front Cover & TOC
  Slot 4 (viewer label='2' ): Page 3: Setup & Wiring Diagram
  Slot 5 (viewer label='3' ): Page 4: Calibration Procedure
  Slot 6 (viewer label='4' ): Page 5: Troubleshooting Table

Look closely at the two details in the output above:

  1. Object IDs do not change: Page 6 is object 21 0 R. Moving it to the front simply shifts 21 0 R to index 0 of the /Kids array ([21 0 R, 4 0 R, 9 0 R, 12 0 R, 15 0 R, 18 0 R]).
  2. The /PageLabels trap: Because the /PageLabels number tree in the /Root catalog maps 0-based physical indices (0 -> 'i', 1 -> 'ii', 2 -> '1') rather than object references, moving 21 0 R to index 0 in place causes PDF viewers to label Page 6: Back Cover & Support as Roman numeral 'i' and Page 2: Inside Front Cover & TOC as page '1'. Copying pages into a fresh PDF document (via pdf-lib or pypdf’s PdfWriter) drops the stale /PageLabels tree so viewer page numbers match the new physical sequence (1–6). PyMuPDF’s doc.select() reorders in place, but in PyMuPDF 1.28 it also removes /PageLabels, with the same result.

Reader Spreads vs. Printer Spreads: The Math of Booklet Imposition

When you fold a stack of duplex-printed sheets in half along the spine (saddle-stitch binding), each physical sheet of paper contains four booklet pages: two on the front side (recto) and two on the back side (verso).

Consequently, every saddle-stitch booklet obeys two structural rules:

  1. The total page count N must be a multiple of 4: A 6-page PDF requires ((6 + 3) // 4) * 4 = 8 page slots across two physical sheets.
  2. Outer and inner pages pair across the fold: On Sheet k (for k = 0 to N/4 - 1, 0-indexed), the 1-based page pairings are:
    • Front of sheet (Recto): Left half = N - 2*k, Right half = 1 + 2*k
    • Back of sheet (Verso): Left half = 2 + 2*k, Right half = N - 1 - 2*k

For an 8-page booklet (N = 8), this produces the printer-spread sequence [8, 1, 2, 7, 6, 3, 4, 5]:

flowchart LR
    subgraph Seq["Reader Order (1 to 8)"]
        R["Pages 1, 2, 3, 4, 5, 6, 7, 8"]
    end

    subgraph S1["Physical Sheet 1 (Outer Leaf)"]
        S1F["Front (Recto): Left = Page 8 | Right = Page 1"]
        S1B["Back (Verso): Left = Page 2 | Right = Page 7"]
    end

    subgraph S2["Physical Sheet 2 (Inner Leaf, Nested & Folded)"]
        S2F["Front (Recto): Left = Page 6 | Right = Page 3"]
        S2B["Back (Verso): Left = Page 4 | Right = Page 5"]
    end

    R --> S1
    R --> S2

Here is the exact printer-spread page sequence for standard 4-, 8-, 12-, and 16-page saddle-stitched booklets:

Booklet SizeDuplex Sheets RequiredSheet 1 (Front / Back)Sheet 2 (Front / Back)Full Printer-Spread Sequence (1-Based)
4 pages1 sheet[4, 1] / [2, 3]—4, 1, 2, 3
8 pages2 sheets[8, 1] / [2, 7][6, 3] / [4, 5]8, 1, 2, 7, 6, 3, 4, 5
12 pages3 sheets[12, 1] / [2, 11][10, 3] / [4, 9] (+ Sheet 3: [8, 5] / [6, 7])12, 1, 2, 11, 10, 3, 4, 9, 8, 5, 6, 7
16 pages4 sheets[16, 1] / [2, 15][14, 3] / [4, 13] (+ Sheets 3–4)16, 1, 2, 15, 14, 3, 4, 13, 12, 5, 6, 11, 10, 7, 8, 9

Method 1: Visual Page Reordering in the Browser

If you already have a 4-page or 8-page brochure, program, or packet where you need to move pages into printer-spread order—or if a cover page, appendix, or signature sheet is sitting in the wrong slot before duplex printing—you can reorder the pages visually in Organize PDF. The tool runs in your browser, so the PDF is not uploaded to a server.

Here is how the browser organizer works and what to expect:

  1. Thumbnail Grid with Source Page Badges: When you load a PDF, pdf.js renders each page as a thumbnail. Each card shows its current position (#1, #2, etc.), and once a page has moved away from its original slot, an amber orig #N badge shows which page of the uploaded file it is. To arrange an 8-page booklet, reorder the cards until the badges read 8, 1, 2, 7, 6, 3, 4, 5 from left to right.
  2. Drag-and-Drop and Arrow Controls: Drag a card to a new slot, or use its Move Left / Move Right buttons to shift it one position. Each card also has rotate and remove buttons, while the toolbar offers batch rotation, Reverse Order, Undo, and a Reset button that restores the initial page order and any removed pages.
  3. Blank Padding Pages: Add blank page appends an empty page, sized like the last page, to the end of the grid; drag it into position. For a 6-page document, add two blanks and drag them just before the back cover to get the [1, 2, 3, 4, 5, Blank, Blank, 6] sequence explained in Method 2.
  4. Page-Tree Rebuild via pdf-lib: Clicking Download PDF creates a fresh PDF and copies the pages in their new order with copyPages(), adding blank pages where you inserted them.

The browser tool outputs single pages in your chosen order rather than placing two pages onto one landscape sheet. For true 2-up imposition, use the PyMuPDF method below.

Method 2: Booklet Reordering and Back-Cover Padding in Python (pypdf)

If you plan to print using your printer driver’s “2 pages per sheet” setting, you only need a single-page PDF whose pages are reordered into the saddle-stitch sequence and padded to a multiple of 4.

There is a catch when padding a document like our 6-page manual_6p.pdf to 8 pages:

  • End-padding ([1, 2, 3, 4, 5, 6, Blank, Blank]): Appending two blank pages at the very end puts Page 8 (Blank) on the outside back cover (Sheet 1 Front, Left) and buries your real Page 6: Back Cover & Support inside the booklet on Sheet 2 Front, Left (facing Page 3).
  • Back-cover-preserving padding ([1, 2, 3, 4, 5, Blank, Blank, 6]): Inserting the two blank padding pages immediately before the final page keeps Page 6 at logical slot 8 so it prints on the outside back cover opposite Page 1: Front Cover, while the two blank pages become the inside back cover (slot 7) and its facing page (slot 6).

Here is the pypdf script that pads a document while preserving its outside back cover and rearranges the pages into saddle-stitch printer spreads:

from pypdf import PdfReader, PdfWriter

reader = PdfReader("manual_6p.pdf")
n = len(reader.pages)
padded = ((n + 3) // 4) * 4
pad_count = padded - n

# Insert blank padding slots (None) right before the final back-cover page
logical_pages = list(reader.pages[:-1]) + [None] * pad_count + [reader.pages[-1]]

# Build 0-based saddle-stitch printer-spread order
order = []
for k in range(padded // 4):
    order.extend([padded - 1 - 2 * k, 2 * k])      # Sheet k+1 Front (Recto)
    order.extend([2 * k + 1, padded - 2 - 2 * k])  # Sheet k+1 Back  (Verso)

width = float(reader.pages[0].mediabox.width)
height = float(reader.pages[0].mediabox.height)
writer = PdfWriter()

for idx in order:
    page = logical_pages[idx]
    if page is not None:
        writer.add_page(page)
    else:
        writer.add_blank_page(width=width, height=height)

with open("manual_booklet_cover_preserved.pdf", "wb") as f:
    writer.write(f)

check = PdfReader("manual_booklet_cover_preserved.pdf")
for s in range(0, len(check.pages), 2):
    sheet_num = s // 4 + 1
    side = "Front (Recto)" if (s // 2) % 2 == 0 else "Back  (Verso)"
    left_txt = (check.pages[s].extract_text().splitlines() or ["[Blank Inside Padding]"])[0]
    right_txt = (check.pages[s + 1].extract_text().splitlines() or ["[Blank Inside Padding]"])[0]
    print(f"Sheet {sheet_num} {side}: Left='{left_txt}' | Right='{right_txt}'")

Output:

Sheet 1 Front (Recto): Left='Page 6: Back Cover & Support' | Right='Page 1: Front Cover'
Sheet 1 Back  (Verso): Left='Page 2: Inside Front Cover & TOC' | Right='[Blank Inside Padding]'
Sheet 2 Front (Recto): Left='[Blank Inside Padding]' | Right='Page 3: Setup & Wiring Diagram'
Sheet 2 Back  (Verso): Left='Page 4: Calibration Procedure' | Right='Page 5: Troubleshooting Table'

Method 3: Building True 2-Up Imposed Landscape Sheets (PyMuPDF)

Relying on an operating system’s “2 pages per sheet” print dialog often introduces extra printer margins or flips the back side of each sheet along the wrong edge. To eliminate print-driver guesswork, you can use PyMuPDF’s Page.show_pdf_page() to place two portrait pages side by side onto true landscape sheets (792 x 612 pt for US Letter or 841.89 x 595.28 pt for A4) as vector Form XObjects. The script reuses Method 2’s back-cover-preserving padding, so the blank slots land inside the booklet:

import pymupdf

src = pymupdf.open("manual_6p.pdf")
n = len(src)
padded = ((n + 3) // 4) * 4

# Same back-cover-preserving padding as Method 2: blank slots go right before the last page
logical = list(range(n - 1)) + [None] * (padded - n) + [n - 1]

order = []
for k in range(padded // 4):
    order.extend([padded - 1 - 2 * k, 2 * k])
    order.extend([2 * k + 1, padded - 2 - 2 * k])

# Swap source width (612) and height (792) to create a 792x612 pt landscape sheet
sheet_w = src[0].rect.height
sheet_h = src[0].rect.width
half_w = sheet_w / 2.0
halves = [pymupdf.Rect(0, 0, half_w, sheet_h), pymupdf.Rect(half_w, 0, sheet_w, sheet_h)]

out = pymupdf.open()
for s_idx in range(0, len(order), 2):
    sheet = out.new_page(width=sheet_w, height=sheet_h)
    for rect, slot in zip(halves, order[s_idx:s_idx + 2]):
        if logical[slot] is not None:
            sheet.show_pdf_page(rect, src, logical[slot])

out.save("manual_booklet_2up.pdf", garbage=4, deflate=True)
out.close()
src.close()

check_2up = pymupdf.open("manual_booklet_2up.pdf")
print(f"2-up imposed PDF: {len(check_2up)} landscape sheets ({check_2up[0].rect.width:.0f}x{check_2up[0].rect.height:.0f} pt)")
for s_idx, sheet in enumerate(check_2up):
    side = "Front (Recto)" if s_idx % 2 == 0 else "Back  (Verso)"
    left, right = ((sheet.get_text(clip=r).splitlines() or ["[Blank]"])[0] for r in halves)
    print(f"  Physical Sheet {s_idx // 2 + 1} {side}: Left='{left}' | Right='{right}'")
check_2up.close()

Output:

2-up imposed PDF: 4 landscape sheets (792x612 pt)
  Physical Sheet 1 Front (Recto): Left='Page 6: Back Cover & Support' | Right='Page 1: Front Cover'
  Physical Sheet 1 Back  (Verso): Left='Page 2: Inside Front Cover & TOC' | Right='[Blank]'
  Physical Sheet 2 Front (Recto): Left='[Blank]' | Right='Page 3: Setup & Wiring Diagram'
  Physical Sheet 2 Back  (Verso): Left='Page 4: Calibration Procedure' | Right='Page 5: Troubleshooting Table'

[!TIP] Short-Edge vs. Long-Edge Duplex Flip: Landscape 2-up sheets are meant to be printed with Two-Sided: Flip on Short Edge. If your office printer driver locks duplex printing to Flip on Long Edge, every verso sheet (Physical Sheet 1 Back, Physical Sheet 2 Back, etc.) will print upside down. You can compensate directly in the PyMuPDF loop above by adding if (s_idx // 2) % 2 == 1: sheet.set_rotation(180) before saving, or by rotating every even sheet 180° in Rotate PDF Pages.

Method 4: Re-Interleaving a Two-Pass Simplex ADF Scan

Another common reason to rearrange PDF pages before printing happens when you scan a double-sided packet on a single-sided (simplex) Automatic Document Feeder:

  1. Pass 1 scans the front (odd) sides in forward order: 1, 3, 5, 7.
  2. You flip the stack over and feed it back through for Pass 2, which scans the back (even) sides in reverse order: 8, 6, 4, 2.
  3. The scanner saves a single 8-page PDF ordered [1, 3, 5, 7, 8, 6, 4, 2].

Instead of moving pages one by one, split the page array at the midpoint in pypdf, reverse the second half with [::-1], and interleave the pairs with zip():

import pymupdf
from pypdf import PdfReader, PdfWriter

# Simulate a two-pass simplex scan: fronts 1, 3, 5, 7, then backs 8, 6, 4, 2
scan = pymupdf.open()
for n in [1, 3, 5, 7, 8, 6, 4, 2]:
    scan.new_page(width=612, height=792).insert_text((72, 100), f"Scanned Sheet Page {n}", fontsize=18)
scan.save("simplex_scan_8p.pdf")

reader = PdfReader("simplex_scan_8p.pdf")
half = len(reader.pages) // 2
odds = reader.pages[:half]          # [Page 1, Page 3, Page 5, Page 7]
evens = reader.pages[half:][::-1]   # [Page 8, Page 6, Page 4, Page 2] -> [2, 4, 6, 8]

writer = PdfWriter()
for odd_page, even_page in zip(odds, evens):
    writer.add_page(odd_page)
    writer.add_page(even_page)

with open("simplex_scan_reordered.pdf", "wb") as f:
    writer.write(f)

fixed = PdfReader("simplex_scan_reordered.pdf")
print("Reordered simplex scan:", [p.extract_text().strip() for p in fixed.pages])

Output:

Reordered simplex scan: ['Scanned Sheet Page 1', 'Scanned Sheet Page 2', 'Scanned Sheet Page 3', 'Scanned Sheet Page 4', 'Scanned Sheet Page 5', 'Scanned Sheet Page 6', 'Scanned Sheet Page 7', 'Scanned Sheet Page 8']

You can compare the file sizes before and after rearranging and imposing using stat (stat -f "%N: %z bytes" on macOS BSD stat, or stat -c "%n: %s bytes" on Linux GNU coreutils):

# macOS (BSD stat)
stat -f "%N: %z bytes" manual_6p.pdf manual_booklet_cover_preserved.pdf manual_booklet_2up.pdf simplex_scan_8p.pdf simplex_scan_reordered.pdf

# Linux (GNU coreutils)
# stat -c "%n: %s bytes" manual_6p.pdf manual_booklet_cover_preserved.pdf manual_booklet_2up.pdf simplex_scan_8p.pdf simplex_scan_reordered.pdf

Output:

manual_6p.pdf: 4024 bytes
manual_booklet_cover_preserved.pdf: 5000 bytes
manual_booklet_2up.pdf: 5184 bytes
simplex_scan_8p.pdf: 3460 bytes
simplex_scan_reordered.pdf: 3581 bytes

Comparing Ways to Rearrange PDF Pages for Booklet Printing

MethodCan Insert Blank Padding (N mod 4)?Output LayoutPreserves /PageLabels & /Outlines?Best Use Case
Organize PDF (pdf-lib)Yes (Add blank page, then drag into place)1-up single pagesNo (rebuilds clean PDFDocument)Visually reordering and padding short booklets or fixing misplaced cover/insert sheets
Python pypdf (PdfWriter)Yes (writer.add_blank_page())1-up single pages in printer-spread orderNo (resets to 1-based numbering)Padding to a multiple of 4 ([8, 1, 2, 7, 6, 3, 4, 5]) or re-interleaving 2-pass simplex scans
Python PyMuPDF (show_pdf_page())Yes (leaves empty half-sheets on padded slots)True 2-up landscape sheets (792 x 612 pt)No (new imposed document)Full saddle-stitch imposition without relying on printer driver “2 pages per sheet” scaling
Python PyMuPDF (doc.move_page() / select())No (new_page() required separately)1-up single pagesmove_page() keeps raw /PageLabels indices; select() resets themIn-place page moves on large PDFs

Before you impose or rearrange a document into printer spreads, make sure you stamp any visible footer numbers first while the PDF is still in sequential reader order (1, 2, 3...)—otherwise page numbers applied afterward will label your outside back cover as “Page 1.” You can add sequential footers with PDF Page Numbers (see our guide on PDF page number placement and cover pages). If you are going the opposite direction—converting a scanned two-up booklet back into individual sequential pages—see our guide on splitting two-page book scans into single PDF pages, or clean up stray sheets beforehand with Remove PDF Pages and our guide to rotating upside-down scanned PDF pages permanently.