Skip to main content

python-pages

python-pages is a Python library for reading and editing modern Apple Pages documents with an object model inspired by python-docx. The distribution name is python-pages; the import name is pages:

from pages import Document

The library edits .pages packages directly. It does not launch Pages, use AppleScript, or automate the GUI. Its runtime is dependency-free and consists of a ZIP container layer, a lossless protobuf-wire layer, an iWork Snappy/IWA layer, and a public document API.

This project is alpha-quality. Keep the source document and inspect important outputs in Pages before using them in production.

Installation

From a local checkout:

python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install --upgrade pip
python3 -m pip install -e .

The editable install provides both import pages and the python-pages command. To run without installing, prefix commands with PYTHONPATH=src.

The project requires Python 3.11 or newer. There are no runtime dependencies.

The requested pages import name is also used by an unrelated PyPI static-site generator. Install python-pages in a dedicated virtual environment and do not co-install that distribution; both projects would otherwise provide the same top-level import name.

Quick start

from pages import (
    Document,
    Inches,
    Pt,
    RGBColor,
    WD_ALIGN_PARAGRAPH,
    WD_COLOR_INDEX,
    WD_ORIENT,
)

document = Document("input.pages")

paragraph = document.paragraphs[0]
run = paragraph.runs[0]
print(paragraph.text, run.text)

# Character formatting uses python-docx-style tri-state values.
run.bold = True
run.italic = False
run.underline = None
run.font.name = "Helvetica-Bold"
run.font.size = Pt(14)
run.font.color.rgb = RGBColor(0x22, 0x66, 0xCC)
run.font.strike = True
run.font.subscript = False
run.font.superscript = True

# Pages stores the observed text background at paragraph granularity.
paragraph.highlight_color = WD_COLOR_INDEX.YELLOW
paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
paragraph.paragraph_format.line_spacing = Pt(24)
paragraph.paragraph_format.space_before = Pt(12)
paragraph.paragraph_format.left_indent = Inches(0.25)

paragraph.add_run(" Appended text")
document.add_paragraph("A new body paragraph")
document.add_heading("A new heading", level=1)
document.add_page_break()

section = document.sections[0]
section.orientation = WD_ORIENT.LANDSCAPE
section.page_width, section.page_height = section.page_height, section.page_width
section.left_margin = Inches(0.75)
section.header.paragraphs[0].text = "Header text"

document.save("output.pages")

Document() without a path starts with a bundled minimal Pages template. The template contains blank body text/previews and a neutral white placeholder for an otherwise unreachable image graph. The input path is never modified; changes are written only by save().

API coverage

The public API intentionally follows python-docx naming where Pages has a reasonable counterpart:

Area python-pages support
Documents Document(path), Document(), save()
Text paragraphs, Paragraph.text, runs, Run.text, insertion and deletion
Fonts bold, italic, underline, strike, name, size, RGB color, subscript, superscript
Paragraphs alignment, line spacing, spacing before/after, indents, tab stops, named styles
Sections one Pages section with page size, margins, orientation, headers, and footers
Tables read rows/cells, edit materialized text cells, clone a blank table with add_table()
Pictures inspect/replace PNG images and clone an inline image with add_picture()
Links and styles inspect/edit existing hyperlinks; enumerate/apply named paragraph and character styles
Properties python-docx-shaped core_properties facade
Comments read comments, replies, authors, anchors, and edit existing comment text

Examples for additional object types:

table = document.tables[0]
print([[cell.text for cell in row.cells] for row in table.rows])
table.cell(1, 1).text = "Updated cell"
new_table = document.add_table(rows=3, cols=4)

picture = document.inline_shapes[0]
print(picture.filename, picture.width, picture.height)
picture.replace("replacement.png", "replacement-256px-thumbnail.png")
new_picture = document.add_picture("new-image.png", width=Inches(4))

hyperlink = document.hyperlinks[0]
hyperlink.text = "Project website"
hyperlink.address = "https://example.com"

for comment in document.comments:
    print(comment.author, comment.anchor_text, comment.text)
    print([reply.text for reply in comment.replies])

Differences from python-docx

python-pages is API-inspired, not a byte-format adapter for DOCX. Important differences follow from the Pages data model:

  • Pages page geometry is package-wide, so Document.sections contains one section. Multiple DOCX sections and linked-to-previous behavior are not emulated.
  • A Pages header or footer is exposed as its left, center, and right text fragments rather than as a WordprocessingML story part.
  • The observed Pages text-background property belongs to an anonymous paragraph style. Run.font.highlight_color is retained for call-shape compatibility but reads or writes the run's entire containing paragraph.
  • core_properties values without a native Pages counterpart are stored in Metadata/PythonPagesCoreProperties.plist. Documents written by the earlier project name remain readable.
  • add_table() needs an existing Pages-authored table in the target document as a structural template. It supports one tile of up to 256 rows and does not implement cell merge or sparse-cell materialization.
  • add_picture() needs an existing Pages-authored image graph in the target document. New and replacement media are PNG; automatic thumbnails support non-interlaced 8-bit RGB/RGBA PNG.
  • Existing comments and replies can be read and edited, but creating a new comment is intentionally disabled because Pages also writes collaboration and view-state objects whose lifecycle is not yet established.
  • Existing hyperlinks can be edited. Creating a new hyperlink is not yet part of the public API.
  • Pages formats are undocumented and can change between application versions. The implementation preserves unknown protobuf fields where possible, but it cannot guarantee support for every historical or future Pages variant.

The detailed compatibility ledger and reverse-engineering evidence are in REPORT.md.

Command-line interface

python-pages inspect input.pages
python-pages bold input.pages output.pages "unique body substring"

Without installation:

PYTHONPATH=src python3 -m pages.cli inspect input.pages

The bold helper expects an unambiguous substring unless --occurrence is provided. It converts Python string offsets to the UTF-16 offsets used by Pages.

Optional test fixtures are not distributed

The test suite is self-contained and passes from a standalone checkout. Private .pages files used for Pages.app interoperability checks can contain copyrighted or sensitive content, so samples/ and generated output/ packages are excluded from Git and distributions.

To enable the additional real-document tests, place a Pages-authored package at samples/local_fixture.pages. The tests activate automatically with unittest.skipUnless when the file is present. It must contain:

  • at least one inline PNG image;
  • at least one nonempty table;
  • at least one hyperlink; and
  • at least one comment with a reply.

No particular headings, formatted runs, or document text are required by these optional tests. Use only content you are allowed to publish and do not commit the fixture.

To enable the python-docx compatibility-ledger audit, place the upstream source tree at ref/python-docx/. Its tests must be under ref/python-docx/tests/; the ledger test also activates automatically when that directory is present.

Run the suite with:

python3 -m unittest discover -s tests -v

Use the decoded-IWA audit tool for every write path:

PYTHONPATH=src python3 tools/diff_iwa.py before.pages after.pages
unzip -t after.pages

Architecture

  • Low level: snappy.py, protobuf.py, and iwa.py provide lossless wire parsing and 64-KiB iWork frame handling.
  • Middle level: pages.py, metadata.py, stylesheet.py, text.py, and the component-specific modules traverse and mutate Pages object graphs.
  • High level: api.py provides the python-docx-shaped object model exported by pages.

Synthesized styles are registered in the stylesheet, parent/child map, MessageInfo references, PackageMetadata UUID map, and cross-component reference map. Untouched ZIP members and IWA payloads are preserved byte-for-byte; changed members are re-encoded into valid Pages packages.

Project status

The compatibility ledger accounts for all 59 python-docx test files and 662 discovered behavior methods as migrated, partially migrated, or format-specific exclusions. When ref/python-docx/ is present, the optional ledger test verifies that the upstream checkout contains behavior tests. The dependency-free test suite runs standalone; optional fixture and ledger tests activate automatically when their local inputs are present.

python-pages is an independent project and is not affiliated with Apple or the python-docx project.

Metadata

Release files for python-pages 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for python-pages 0.1.0
File Size Uploaded Provenance Actions
python_pages-0.1.0.tar.gz 119.5 kB With provenance Details for python_pages-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for python-pages 0.1.0
File Interpreter ABI Platform Actions
python_pages-0.1.0-py3-none-any.whl Python 3 none any Details for python_pages-0.1.0-py3-none-any.whl

Total release size: 230.1 kB

Release files / python_pages-0.1.0.tar.gz

Download URL python_pages-0.1.0.tar.gz
Size 119.5 kB
Tags Source
SHA-256 checksum
How to use checksums
34c3ba1e4a3c69ec04e7182a372ead6b4e8a199679a516606b736c32522fb167
BLAKE2b-256 checksum
How to use checksums
400dfa51774390f93f281091d408495d1e6ac3902f73b2f7e0ad3ac9990e0ee4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 22, 2026.

Transparency log

Release files / python_pages-0.1.0-py3-none-any.whl

Download URL python_pages-0.1.0-py3-none-any.whl
Size 110.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cc9a0066da9c600795ca8335b6b56105cd9b30c045c99113db63d5dc259c1b4a
BLAKE2b-256 checksum
How to use checksums
5372797e57cd3b295742b568e0f802af96dfacea568fca5ef90fd9d0e7144cd5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 22, 2026.

Transparency log

Security for python-pages 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for python_pages-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 22, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page