
Automating Pages
- 20 installs
- 39 repo stars
- Updated January 14, 2026
- spillwavesolutions/automating-mac-apps-plugin
Helps with ai & agent building tasks during AI-assisted development.
About
automating-pages is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- automating-pages
- AI & Agent Building
- AI-coding skill
Automating Pages by the numbers
- 20 all-time installs (skills.sh)
- Ranked #10,442 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/spillwavesolutions/automating-mac-apps-plugin --skill automating-pagesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 20 |
|---|---|
| repo stars | ★ 39 |
| Last updated | January 14, 2026 |
| Repository | spillwavesolutions/automating-mac-apps-plugin ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Automating Pages (JXA-first, AppleScript discovery)
Relationship to the macOS automation skill
- Standalone for Pages, aligned with
automating-mac-appspatterns. - Use
automating-mac-appsfor permissions, shell, and UI scripting guidance. - PyXA Installation: To use PyXA examples in this skill, see the installation instructions in
automating-mac-appsskill (PyXA Installation section).
Core framing
- Pages dictionary is AppleScript-first; discover there.
- JXA provides the logic and data handling.
- Objects are specifiers: References to Pages elements that require methods for reads (e.g.,
doc.body.text()) and assignments for writes (e.g.,doc.body.text = 'new text').
Example: Create Document
const pages = Application('Pages');
const doc = pages.Document({templateName: 'Blank'});
pages.documents.push(doc);
doc.body.text = "Hello World";Workflow (default)
1) Discover: Open Script Editor > File > Open Dictionary > Pages. 2) Prototype: Write minimal AppleScript to verify the command works. 3) Port to JXA: Convert AppleScript syntax to JXA objects.
- Example:
make new documentbecomespages.documents.push(pages.Document()). - Add error handling (try/catch blocks).
4) Optimize: Use batch text operations when possible to avoid performance penalties. 5) Fallback: Use AppleScript bridge or UI scripting for dictionary gaps (e.g., specific layout changes).
Image Insertion (Critical Difference from Keynote)
IMPORTANT: Pages does NOT support direct image insertion like Keynote does:
// THIS WORKS IN KEYNOTE:
Keynote.Image({ file: Path("/path/to/image.png"), position: {x: 100, y: 100} });
// THIS DOES NOT WORK IN PAGES!
Pages.Image({ file: Path("/path/to/image.png") }); // ❌ Will failSolution: Use ObjC Pasteboard bridging (see pages-advanced.md for details):
ObjC.import('AppKit');
const nsImage = $.NSImage.alloc.initWithContentsOfFile("/path/to/image.png");
const pb = $.NSPasteboard.generalPasteboard;
pb.clearContents;
pb.setDataForType(nsImage.TIFFRepresentation, $.NSPasteboardTypeTIFF);
// Then use System Events to paste (Cmd+V)Example Script: See automating-pages/scripts/insert_images.js for a complete working example.
Common Pitfalls
- Image insertion: Pages lacks a native
Imageconstructor unlike Keynote. Use ObjC Pasteboard method. - Dictionary gaps: Some features (like sophisticated layout adjustments) aren't in the dictionary. Use the AppleScript bridge or UI scripting.
- Permissions: Ensure 'Accessibility' settings are enabled for UI scripting.
- Saving: Always use
doc.save({in: file_path})with a valid path object.
Validation Checklist
- [ ] Document opens without errors
- [ ] Text insertion and formatting succeeds
- [ ] Save operations complete with valid path objects
- [ ] Template application works if used
- [ ] Export to target format produces valid output (PDF, Word)
- [ ] Error handling covers missing files and permissions
When Not to Use
- Cross-platform document automation (use python-docx or pandoc)
- AppleScript alone suffices (skip JXA complexity)
- Web-based documents (Google Docs API)
- Non-macOS platforms
- Complex page layout requiring manual design tools
What to load
Level 1: Basics
- JXA Pages basics:
automating-pages/references/pages-basics.md(Core objects and document lifecycle)
Level 2: Recipes & Common Tasks
- Recipes (templates, export, text):
automating-pages/references/pages-recipes.md(Standard operations) - Export options matrix:
automating-pages/references/pages-export-matrix.md(PDF, Word, ePub formats) - Template strategy:
automating-pages/references/pages-template-strategy.md(Managing custom templates)
Level 3: Advanced
- Advanced patterns (tables, images, AppleScript bridge):
automating-pages/references/pages-advanced.md(Complex integrations) - UI scripting patterns:
automating-pages/references/pages-ui-scripting.md(Fallbacks) - Dictionary translation table:
automating-pages/references/pages-dictionary.md(AppleScript to JXA mapping) - PyXA (Python) alternative:
automating-pages/references/pages-pyxa.md
Example Scripts
- Image insertion:
automating-pages/scripts/insert_images.js(ObjC Pasteboard method for inserting images)
Pages JXA advanced patterns
Image Insertion (ObjC Pasteboard Method)
IMPORTANT: Unlike Keynote, Pages does NOT support direct image insertion via a constructor like:
// THIS DOES NOT WORK IN PAGES!
Pages.Image({ file: Path("/path/to/image.png"), position: {x: 100, y: 100} });The Pages image class requires image binary data which cannot be easily instantiated from files.
Recommended: ObjC Pasteboard Method
The most reliable way to insert images into Pages is using ObjC bridging with NSImage and NSPasteboard:
ObjC.import('AppKit');
/**
* Insert an image into Pages at current cursor position.
* @param {string} imagePath - Absolute path to image file (PNG, JPEG, TIFF)
*/
function pasteImageFromFile(imagePath) {
// Load image using NSImage
const nsImage = $.NSImage.alloc.initWithContentsOfFile(imagePath);
if (!nsImage) {
throw new Error("Failed to load image: " + imagePath);
}
// Get pasteboard and clear it
const pasteboard = $.NSPasteboard.generalPasteboard;
pasteboard.clearContents;
// Set TIFF data to pasteboard (universal format Pages accepts)
pasteboard.setDataForType(nsImage.TIFFRepresentation, $.NSPasteboardTypeTIFF);
// Paste into Pages
const Pages = Application('Pages');
Pages.activate();
delay(0.3);
// Simulate Cmd+V
const se = Application('System Events');
se.keystroke('v', { using: 'command down' });
delay(1);
}
// Usage
pasteImageFromFile("/Users/you/images/diagram.png");Complete Example: Insert Multiple Images
#!/usr/bin/env osascript -l JavaScript
'use strict';
ObjC.import('AppKit');
const Pages = Application('Pages');
Pages.includeStandardAdditions = true;
const images = [
"/path/to/image1.png",
"/path/to/image2.png"
];
function run() {
Pages.activate();
delay(1);
const doc = Pages.documents[0]; // Use front document
// Move to end of document
const se = Application('System Events');
se.keystroke('e', { using: ['command down', 'shift down'] });
delay(0.3);
se.keyCode(124); // Right arrow to deselect
se.keystroke('\r'); // New line
// Insert each image
for (const imgPath of images) {
const nsImage = $.NSImage.alloc.initWithContentsOfFile(imgPath);
const pb = $.NSPasteboard.generalPasteboard;
pb.clearContents;
pb.setDataForType(nsImage.TIFFRepresentation, $.NSPasteboardTypeTIFF);
Pages.activate();
delay(0.3);
se.keystroke('v', { using: 'command down' });
delay(1);
se.keystroke('\r');
se.keystroke('\r');
}
doc.save();
return "Images inserted successfully";
}Why This Works
1. ObjC.import('AppKit') - Enables access to macOS AppKit framework 2. NSImage - Native macOS image class that can load any image format 3. NSPasteboard - System pasteboard for copy/paste operations 4. TIFF format - Universal bitmap format that Pages reliably accepts 5. System Events keystroke - Simulates user pressing Cmd+V
Supported Image Formats
NSImage supports: PNG, JPEG, TIFF, GIF, BMP, PDF, EPS, and more.
Limitations
- Images are inserted at cursor position (no direct x,y positioning)
- Requires System Events accessibility permission
- Images default to text-wrap mode; adjust manually if needed
- Cannot insert images directly into table cells
---
AppleScript bridge for gaps
For features not in JXA dictionary, use the AppleScript bridge:
function runAppleScript(code) {
const app = Application.currentApplication();
app.includeStandardAdditions = true;
return app.runScript(code, { language: "AppleScript" });
}
const as = `
tell application "Pages"
tell front document
make new table with properties {column count:4, row count:10}
end tell
end tell
`;
runAppleScript(as);---
Table population (pre-made template)
1. Create a template document with a table 2. Open template, select the table, then write cell values:
const Pages = Application('Pages');
const doc = Pages.documents[0];
const table = doc.tables[0];
// Set cell values
table.cells["A1"].value = "Header 1";
table.cells["B1"].value = "Header 2";
table.cells["A2"].value = "Data 1";
table.cells["B2"].value = "Data 2";---
Keynote vs Pages Image Comparison
| Feature | Keynote | Pages |
|---|---|---|
| Direct file insertion | ✅ Keynote.Image({file: Path(...)}) | ❌ Not supported |
| Position on creation | ✅ position: {x, y} | ❌ Not supported |
| Width/Height on creation | ✅ width: 800 | ❌ Not supported |
| ObjC Pasteboard method | ✅ Works | ✅ Works (only option) |
| Binary data insertion | ⚠️ Complex | ⚠️ Complex |
Recommendation: For Pages, always use the ObjC Pasteboard method shown above.
Pages JXA basics
Bootstrapping
'use strict';
const pages = Application("Pages");
pages.includeStandardAdditions = true;
if (!pages.running()) pages.launch();
pages.activate();Create from template
const template = pages.templates["Blank"];
const docSpec = pages.Document({ documentTemplate: template, name: "Automated Report" });
const doc = docSpec.make();Open and save
const doc = pages.open(Path("/Users/you/Documents/Report.pages"));
doc.save({ in: Path("/Users/you/Documents/Report_Final.pages") });Pages dictionary translation table
AppleScript JXA
----------------------------------- ------------------------------------------
front document pages.documents[0]
body text doc.bodyText
text of body text doc.bodyText()
paragraphs of body text doc.bodyText.paragraphs
export front document as PDF pages.export(doc, { to: Path("..."), as: "PDF" })
make new document with template pages.Document({ documentTemplate: pages.templates["Blank"] }).make()Notes:
- Properties become camelCase.
- Methods use
()for reads, assignment for writes.
Pages export options matrix
Common export targets
- Microsoft Word (.docx)
- EPUB
Example (PDF)
pages.export(doc, {
to: Path("/Users/you/Desktop/Export.pdf"),
as: "PDF",
withProperties: {
imageQuality: "Best",
includeComments: false
}
});Example (Word)
pages.export(doc, {
to: Path("/Users/you/Desktop/Export.docx"),
as: "Microsoft Word"
});Example (EPUB)
pages.export(doc, {
to: Path("/Users/you/Desktop/Export.epub"),
as: "EPUB"
});Pages Automation with PyXA (Python)
PyXA provides a Pythonic interface to control Apple Pages on macOS via Apple's Scripting Bridge.
Installation
pip install pyxaBootstrapping
import PyXA
# Launch or activate Pages
pages = PyXA.Application("Pages")
pages.activate()Document Management
Creating Documents
# Create new document
doc = pages.documents.new()
# Create with specific template
doc = pages.documents.new(template="Blank")Opening Documents
# Open existing file
doc = pages.open("/Users/me/Documents/Report.pages")Saving Documents
# Save to new location
doc.save("/Users/me/Desktop/Output.pages")
# Save existing
doc.save()Content Manipulation
Working with Body Text
# Access document body
body = doc.body_text
# Insert text at end
body.insert("First paragraph\n", at=body.end())
body.insert("Second paragraph.\n", at=body.end())
# Insert at specific position
body.insert("Introduction\n\n", at=0)Working with Paragraphs
# Access paragraphs
first_para = doc.paragraphs[0]
all_paras = doc.paragraphs
# Get text content
text = first_para.text
# Set paragraph text
first_para.text = "Updated content"Formatting
Paragraph Styles
# Apply predefined style
body.paragraphs[0].style_name = "Heading"
body.paragraphs[1].style_name = "Body"
body.paragraphs[2].style_name = "Title"Text Formatting
# Font properties
body.paragraphs[0].font_size = 24
body.paragraphs[0].font_name = "Helvetica Neue"
body.paragraphs[0].bold = True
# Colors
body.paragraphs[0].font_color = PyXA.XAColor.blue
# Alignment
body.paragraphs[0].alignment = PyXA.PG_ParagraphAlignment.centerBulk Formatting
# Format multiple paragraphs
body.paragraphs[0:3].bold = True
body.paragraphs[0:3].font_size = 14Images
# Insert image
image = doc.images.new(
path="/Users/me/Assets/diagram.png",
bounds=(100, 100, 500, 300) # x, y, width, height
)
# Adjust position
image.position = (200, 150)
image.width = 400 # Height auto-scalesTables
# Create table
table = doc.tables.new(
rows=4,
columns=3
)
# Populate data
data = [
["Name", "Role", "Department"],
["Alice", "Engineer", "R&D"],
["Bob", "Designer", "UX"],
["Carol", "Manager", "Ops"]
]
for r, row in enumerate(data):
for c, value in enumerate(row):
table.rows[r].cells[c].value = value
# Format header row
table.rows[0].bold = True
table.rows[0].background_color = PyXA.XAColor.light_grayExport
PDF Export
doc.export(
"/Users/me/Desktop/Document.pdf",
export_format=PyXA.PG_ExportFormat.pdf
)Word Export
doc.export(
"/Users/me/Desktop/Document.docx",
export_format=PyXA.PG_ExportFormat.word
)Plain Text Export
doc.export(
"/Users/me/Desktop/Document.txt",
export_format=PyXA.PG_ExportFormat.plain_text
)Complete Example
import PyXA
from datetime import date
def create_pages_document(output_path, title, sections):
"""Create a formatted Pages document with PyXA."""
pages = PyXA.Application("Pages")
pages.activate()
# Create document
doc = pages.documents.new()
body = doc.body_text
# Add title
body.insert(f"{title}\n\n")
doc.paragraphs[0].style_name = "Title"
doc.paragraphs[0].alignment = PyXA.PG_ParagraphAlignment.center
# Add date
body.insert(f"Generated: {date.today()}\n\n", at=body.end())
# Add sections
for section_title, content in sections:
# Section heading
body.insert(f"{section_title}\n", at=body.end())
para_idx = len(doc.paragraphs) - 1
doc.paragraphs[para_idx].style_name = "Heading"
# Section content
body.insert(f"{content}\n\n", at=body.end())
# Export to PDF
doc.export(output_path, export_format=PyXA.PG_ExportFormat.pdf)
doc.close()
return {"success": True, "path": output_path}
# Usage
sections = [
("Overview", "This document provides a summary of Q4 results."),
("Key Findings", "Revenue increased 25% year over year."),
("Recommendations", "Continue current strategy with focus on growth.")
]
create_pages_document("/Users/me/Desktop/Report.pdf", "Quarterly Report", sections)Template-Based Document
import PyXA
def fill_template(template_path, output_path, replacements):
"""Fill a Pages template with data."""
pages = PyXA.Application("Pages")
pages.activate()
# Open template
doc = pages.open(template_path)
# Replace placeholders
for placeholder, value in replacements.items():
doc.body_text.replace(placeholder, value)
# Save as new document
doc.save(output_path)
doc.close()
# Usage
replacements = {
"{{NAME}}": "John Smith",
"{{DATE}}": "January 14, 2026",
"{{COMPANY}}": "Acme Corp"
}
fill_template(
"/Users/me/Templates/Letter.pages",
"/Users/me/Desktop/Letter_Filled.pages",
replacements
)PyXA vs JXA Comparison
| Feature | PyXA | JXA |
|---|---|---|
| Insert text | body.insert("text") | Limited API |
| Styles | para.style_name = "..." | Limited support |
| Export | doc.export(path, format) | doc.export({...}) |
| API Coverage | Higher-level | Lower-level |
Notes
- Pages has more limited scripting support than Word
- Template-based approach often works better than programmatic creation
- PyXA provides a cleaner API than JXA for Pages
- Style names must match document's available styles
- Test available styles with
doc.character_stylesanddoc.paragraph_styles
Pages JXA recipes
Export PDF
const doc = pages.documents[0];
pages.export(doc, { to: Path("/Users/you/Desktop/Export.pdf"), as: "PDF" });Replace placeholders (simple)
const doc = pages.documents[0];
const text = doc.bodyText();
const updated = text.replace(/\{\{CLIENT\}\}/g, "Acme");
doc.bodyText = updated;Paragraph styling by match
const paras = doc.bodyText.paragraphs;
const texts = paras.objectText();
texts.forEach((t, i) => {
if (t.includes("CONFIDENTIAL")) {
const p = paras[i];
p.color = [65535, 0, 0];
p.size = 14;
}
});Pages template strategy
Recommended workflow
1) Create a template document with pre-built tables, charts, and styles. 2) Save it as a .pages file in a known path. 3) Open template, duplicate it, and inject data.
Rationale
- JXA creation of complex objects is brittle.
- Templates provide reliable layout and styles.
Template copy pattern
const templatePath = Path("/Users/you/Templates/Report.pages");
const doc = pages.open(templatePath);
// Modify content...
const outPath = Path("/Users/you/Desktop/Report_Final.pages");
doc.save({ in: outPath });Pages UI scripting patterns
When to use
- Features missing from the dictionary (inspector toggles, advanced layout).
Basic pattern
const se = Application("System Events");
const pagesProc = se.processes.byName("Pages");
Application("Pages").activate();
delay(0.2);
// Example: open Format sidebar (path varies by version)
// pagesProc.windows[0].toolbars[0].buttons.byName("Format").click();Notes
- Use Accessibility Inspector to find stable paths.
- Prefer named elements over index paths.
- Add waits around UI element discovery.
#!/usr/bin/env osascript -l JavaScript
/**
* insert_images.js
* Inserts images into an Apple Pages document using ObjC pasteboard bridging.
*
* IMPORTANT: Unlike Keynote, Pages does NOT support direct image insertion via
* Pages.Image({file: Path(...)}). This script uses the ObjC pasteboard method
* which is the only reliable way to insert images programmatically into Pages.
*
* Usage:
* osascript -l JavaScript insert_images.js
*
* Or make executable and run:
* chmod +x insert_images.js
* ./insert_images.js
*
* Modify the IMAGES array and DOC_PATH constants for your use case.
*/
'use strict';
// Import AppKit for NSImage and NSPasteboard
ObjC.import('AppKit');
// ============ CONFIGURATION ============
// Modify these paths for your use case
const DOC_PATH = "/path/to/your/document.pages";
const IMAGES = [
"/path/to/image1.png",
"/path/to/image2.png",
"/path/to/image3.png"
];
// ============ MAIN SCRIPT ============
const Pages = Application('Pages');
Pages.includeStandardAdditions = true;
/**
* Paste an image from a file path into the current cursor position in Pages.
* Uses ObjC bridging to load image via NSImage and paste via NSPasteboard.
*
* @param {string} imagePath - Absolute path to the image file (PNG, JPEG, TIFF, etc.)
* @returns {string} Status message
*/
function pasteImageFromFile(imagePath) {
// Load image using NSImage
const nsImage = $.NSImage.alloc.initWithContentsOfFile(imagePath);
if (!nsImage) {
return "ERROR: Failed to load image: " + imagePath;
}
// Get the general pasteboard and clear it
const pasteboard = $.NSPasteboard.generalPasteboard;
pasteboard.clearContents;
// Set TIFF representation of the image to pasteboard
// TIFF is used because it's a universal format that Pages accepts
pasteboard.setDataForType(nsImage.TIFFRepresentation, $.NSPasteboardTypeTIFF);
// Ensure Pages is frontmost
Pages.activate();
delay(0.3);
// Use System Events to simulate Cmd+V paste
const systemEvents = Application('System Events');
systemEvents.keystroke('v', { using: 'command down' });
delay(1); // Wait for image to be inserted
// Add line breaks after image for spacing
systemEvents.keystroke('\r');
systemEvents.keystroke('\r');
delay(0.3);
return "SUCCESS: Pasted image: " + imagePath;
}
/**
* Move cursor to end of document using keyboard shortcuts.
*/
function moveCursorToEnd() {
const systemEvents = Application('System Events');
// Cmd+End or Cmd+Shift+End to go to end of document
systemEvents.keystroke('e', { using: ['command down', 'shift down'] });
delay(0.3);
// Press right arrow to deselect and position cursor at end
systemEvents.keyCode(124);
delay(0.3);
// Add some line breaks
systemEvents.keystroke('\r');
systemEvents.keystroke('\r');
}
/**
* Main execution function.
*/
function run() {
try {
// Activate Pages
Pages.activate();
delay(1);
// Open the document (or use frontmost document)
let doc;
if (DOC_PATH !== "/path/to/your/document.pages") {
doc = Pages.open(Path(DOC_PATH));
} else {
// Use frontmost document if no path specified
doc = Pages.documents[0];
}
delay(1);
// Move cursor to end of document
moveCursorToEnd();
// Insert each image
const results = [];
for (const imagePath of IMAGES) {
if (imagePath !== "/path/to/image1.png") { // Skip placeholder paths
const result = pasteImageFromFile(imagePath);
results.push(result);
console.log(result);
}
}
// Save the document
doc.save();
return "Completed. Results:\n" + results.join('\n');
} catch (error) {
return "ERROR: " + error.message;
}
}
// Execute
run();