
Enforcing Page Count Rules in InDesign UXP
In some documents, page count isn’t optional. If you’re building a structured layout—like a photobook or anything with print constraints—you need to enforce limits: minimums, maximums, and page increments.
There’s no native way to enforce page count in InDesign UXP plugins—but it’s possible. With the right setup, you can watch the document, block unwanted changes, and keep users within the rules. If you’re just getting started, Adobe’s official UXP plugin guide for InDesign is worth bookmarking.
How I Store Page Limits
I keep page rules in a JSON manifest so they’re easy to update without touching the code.
{
"pageLimits": {
"min": 20,
"max": 120,
"increment": 2
}
}
These values control everything: the add/remove buttons, layout variant logic, and undo triggers.
Watching for Changes
UXP doesn’t give real-time events when the user adds pages manually, so I use a basic interval check.
let previousPageCount = 0;
function watchDocument() {
setInterval(async () => {
const doc = app.activeDocument;
if (!doc) return;
const currentCount = doc.pages.length;
if (currentCount !== previousPageCount) {
handlePageChange(currentCount);
previousPageCount = currentCount;
}
}, 1000);
}
This runs in the background and keeps track of changes.
Undoing Invalid Adds
If a user adds or removes pages outside the allowed range, I roll it back with an undo call.
function handlePageChange(count) {
if (!isValidPageCount(count)) {
evalScript("app.undo();");
showError("Invalid page count. Use the plugin controls.");
return;
}
updateUI(count);
}
It’s not pretty, but it works. You’re not stopping the native UI—you’re just correcting it as soon as it breaks the rules.
Disabling Page Controls at Limits
The plugin controls check the current page count before allowing changes.
if (pageCount >= pageLimits.max) {
disableAddButton();
}
if ((pageCount - pageLimits.min) % pageLimits.increment !== 0) {
showWarning("Pages must be added in increments of 2.");
}
This avoids unnecessary undo loops and makes the limits clear before the user clicks.
Switching Layouts Dynamically (Using JSON)
Layout variants—like different dust jackets or covers—are tied to page ranges and defined directly in the JSON manifest. This keeps everything flexible and centralised.
Example format:
{
"layoutVariants": [
{ "min": 20, "max": 40, "dustJacket": "DJ-A", "cover": "CV-A" },
{ "min": 42, "max": 80, "dustJacket": "DJ-B", "cover": "CV-B" },
{ "min": 82, "max": 100, "dustJacket": "DJ-C", "cover": "CV-C" },
{ "min": 102, "max": 120, "dustJacket": "DJ-D", "cover": "CV-D" }
]
}
When the page count changes, I check which variant matches and prompt the user to apply the updated layout. If they confirm, the plugin swaps out the master spreads using InDesign’s scripting API.
No hardcoded rules. Just clean, structured logic that adapts as the manifest changes.
Step 1: Find the Correct Layout Variant
When the page count changes, the plugin looks up the correct layout variant:
function getLayoutForPageCount(count) {
return layoutVariants.find(variant => count >= variant.min && count <= variant.max);
}
Step 2: Compare With Current Layout
Each master page spread in the document has a name (e.g. “DJ-A”, “CV-B”). I check the current assignments against what the layout variant expects:
function isLayoutMismatch(current, expected) {
return current.dustJacket !== expected.dustJacket || current.cover !== expected.cover;
}
To get the current layout, I check the applied masters for the first dust jacket and cover pages.
Step 3: Prompt the User
If the layout is wrong, I prompt the user to approve the update:
if (isLayoutMismatch(currentLayout, newLayout)) {
showModal({
title: "Update Layout?",
message: `This document should use ${newLayout.dustJacket} and ${newLayout.cover} based on the new page count. Apply now?`,
onConfirm: () => applyLayout(newLayout)
});
}
This avoids surprising the user with sudden visual changes.
Step 4: Apply the New Layout
When confirmed, I update the relevant spreads:
async function applyLayout(layout) {
const doc = app.activeDocument;
const pages = doc.pages;
const djPage = findDustJacketPage(pages);
const coverPage = findCoverPage(pages);
if (djPage && coverPage) {
djPage.appliedMaster = doc.masterSpreads.itemByName(layout.dustJacket);
coverPage.appliedMaster = doc.masterSpreads.itemByName(layout.cover);
}
}
Everything stays clean. The JSON drives the logic. The plugin handles the check. The user stays in control.
One thing to watch
Don’t force layout changes without confirmation—especially if the user’s already done manual tweaks. Always check first, then offer the switch.
TIP: Use your JSON manifest as the single source of truth. If you ever need to change layout variants, page ranges, or rules—you’ll do it in one place, and the plugin will respond automatically.
Summary
UXP doesn’t give you fine-grained control over the document, but you can still enforce strict page rules with a bit of watching and a bit of undoing. The key is:
- Define your rules clearly in JSON
- Keep user actions inside the plugin
- Catch native changes and correct them quickly
- Trigger layout updates from data, not hardcoded logic
It’s solid, reliable, and keeps the document production-safe without relying on the user to get everything right.
Frequently Asked Questions
Can I make different layout variants conditional on document type, not just page count?
Yes. You can add extra fields to your JSON manifest—like type: "square"
or project: "wedding"
—and then filter your layout variants based on both page count and context. Just update your selection logic to take those conditions into account before returning the right layout.
How do I prevent conflicts if multiple plugins are trying to modify master spreads?
You can’t fully block other plugins, but you can reduce the risk. Namespacing your master spreads (e.g. “MyPlugin_DJ-A”) makes it easier to detect if something’s been changed unexpectedly. You can also re-check spread names before applying layout changes and warn the user if anything looks off.
Related Articles
Brad Holmes
Web developer, designer and digital strategist.
Brad Holmes is a full-stack developer and designer based in the UK with over 20 years’ experience building websites and web apps. He’s worked with agencies, product teams, and clients directly to deliver everything from brand sites to complex systems—always with a focus on UX that makes sense, architecture that scales, and content strategies that actually convert.