Brad Holmes web developer, designer and digital strategist.

Dad, husband and dog owner. If you’re here, you either found something I built on Google or you’re just being nosey. Either way, this is me — the work, the thinking, and the bits in between.

Brought to you by Brad Holmes

Mimeo Photos photobooks website

Building a Custom UXP Plugin for Mimeo Photos in InDesign

Brad Holmes By Brad Holmes
11 min read

Have you tried ever tried building a UXP plugin for InDesign, you’ll know it’s not as simple as dragging a few panels together. Between the newer UXP framework, the quirks of InDesign’s scripting API, and the challenge of keeping a polished UI inside a limited space, it’s a different beast to web dev—or even Photoshop plugins.

This post walks through the full process of designing and developing a custom InDesign UXP plugin for Mimeo Photos. It started as a practical job—make it easier for users to build photobooks—but quickly became something deeper: a modular, rules-based system that enforces page limits, updates layouts dynamically, and integrates real-time feedback inside the InDesign environment.

This is the foundation of what will become a full series on how I created my first UXP Plugin. Here, you’ll see the big picture: how the plugin works, how it was structured, and what challenges came up. Future posts will go deeper into individual parts—like how to manage layout variants, watch document changes, or enforce file preset rules.

This isn’t theory—it’s all based on a real working plugin. I’ll share lessons from both the technical side and the UX side, with honest takes on what worked (and what didn’t).

Project Requirements

The plugin was built for users designing photobooks inside InDesign. It needed to:

  • Load a JSON-based gallery of available book files
  • Let users browse and open them with filters by category
  • Enforce strict rules for page counts (min, max, increments)
  • Detect and undo any invalid page changes—even if made natively
  • Dynamically update layouts like the cover and dust jacket based on total pages
  • Guide the user through initial setup, including checking for a specific .joboptions file

There were also some real-world constraints:

  • No evalScript or CEP-style JSX injection—everything had to be handled inside UXP
  • The UI had to be intuitive for non-technical users (drag-and-drop wasn’t an option)
  • Layout logic had to respond in real time without slowing down the editor

On top of that, I wanted it to be maintainable, and scalable—not just a pile of procedural JavaScript. This meant breaking out logic, using stateful updates, and keeping DOM/UI code separate from the core enforcement engine.

From Idea to Architecture

Once the requirements were clear, I started mapping out how the plugin would work under the hood. UXP gives you a decent foundation—HTML/CSS/JS with access to the Adobe APIs—but it’s not a typical web environment. There’s no window, no fetch polyfills, limited storage, and restricted file system access. So, architecture matters.

The core approach was to separate responsibilities cleanly:

  • Plugin UI: Built with vanilla HTML and core css, kept in its own index.html and main.js.
  • State + Logic: A core JavaScript module that managed layout rules, enforced page count logic, and handled user actions like add/remove.
  • Manifest + Config: The gallery of photobooks, page limits, and layout variants all lived in a Supabase-hosted JSON file. This kept the plugin flexible and updatable without shipping new code.

I also introduced a simple document watcher—a loop that checks page count at intervals and compares it to the last known state. If a user adds pages outside the allowed increment (say, by using the native InDesign UI), the plugin tries to undo the change and restore the valid state. It’s a bit of a hacky workaround, but with evalScript off the table, it’s the only reliable enforcement method right now.

The key goal was to make the plugin feel smart, not brittle. It needed to actively protect layout rules without getting in the user’s way.


UI + UX Inside UXP

Designing inside UXP is like working in a stripped-down browser—you have the basics, but none of the luxuries. I used a core css build to manage layout and spacing, with vanilla.js for state toggles and event handling. The plugin UI is made up of collapsible blocks: gallery browser, project info, FAQ, setup wizard, and layout rules.

Each section serves a clear purpose:

  • The Gallery lets users browse available photobook designs by category, preview them, and open them directly into InDesign.
  • The Setup Wizard walks users through installing the .joboptions preset, which is required for export. It automatically detects if the preset is installed—no manual confirmation needed.
  • The Layout & Page Control Panel handles adding and removing pages in increments, with guardrails to stop invalid actions.
  • The FAQ and Support Block offers live help and links for troubleshooting without leaving the plugin.

UX-wise, the aim was clarity. Most users don’t know (or care) how InDesign scripting works—they just want guardrails that stop them making mistakes, and buttons that do what they say. So the design avoids cleverness and focuses on predictable interaction. Icons are used sparingly, spacing is generous, and labels are direct.

The result is a plugin that doesn’t feel intrusive, but quietly enforces rules in the background while offering just enough control.

Working with InDesign APIs

Working with InDesign via UXP is very different from what you might be used to with ExtendScript or Photoshop plugins. The scripting API for InDesign is still JSX-based under the hood, and UXP doesn’t expose most of it directly. That means no evalScript, no bridge to JSX, and no shortcuts. You’re limited to what UXP gives you natively—and in InDesign, that’s not much.

So instead of direct scripting, I had to get creative. Here’s how I approached it:

Watching the Document

Since I couldn’t intercept native actions directly, I used a polling loop to watch the number of pages in the document every few hundred milliseconds. This let me detect if a user added or removed a page outside the plugin.

If the plugin detects a change that breaks the rules (like going from 24 to 25 pages when only even increments are allowed), it immediately calls app.undo() to roll it back. This might seem fragile, but in testing it was surprisingly consistent—as long as the user didn’t stack multiple changes too fast.

Applying Layout Variants

The plugin also checks the page count against a JSON-defined layout structure. For example:

  • 24–60 pages → use DJ-A and CV-A
  • 62–84 pages → use DJ-B and CV-B
  • 86–100 pages → DJ-C and so on

When a threshold is crossed, the plugin shows a modal: “Would you like to update your cover and dust jacket layout?” If accepted, it automatically replaces the master pages on the relevant spreads. All layout types are stored in the manifest, making the logic reusable and easy to update across projects.

Preset Detection

Another requirement was to check for a specific .joboptions preset (used for export). Since UXP can’t read the actual file system freely, I had to simulate detection by using Adobe’s APIs to check available presets by name.

The plugin loops until it finds the correct one (MimeoPDF.joboptions), then unlocks the next stage of onboarding. No manual “confirm installed” buttons—just passive monitoring.


Solving Real Problems

This wasn’t just a dev experiment—it had to work for real users, in real projects, inside real InDesign workflows. That meant solving the kind of stuff that doesn’t show up in clean API docs.

Undoing Native Page Changes

InDesign doesn’t expose an event for “user just added a page.” So to enforce rules, I had to:

  • Watch the page count regularly
  • Detect disallowed changes
  • Trigger app.undo() as a failsafe

Not elegant, but it works. And users don’t notice—it just quietly stops invalid actions from sticking.

Dynamic Layout Switching

This part felt closer to engineering than scripting. Every time the page count changes, the plugin checks whether the document needs a new layout. If so, it prompts the user and handles the master page swap automatically.

Layouts are tied to ranges in the JSON config, so adding a new layout type (say, DJ-D for 102–120 pages) is just one line of config, no code change.

State Persistence

To keep things smooth, the plugin tracks state using local storage. It remembers if onboarding is complete, which file was opened, and what layout version is applied. This avoids asking the user to repeat steps or re-validate things every session.

Lessons Learned

Every UXP plugin project teaches you something—this one taught me a lot. Here’s what stood out:

What Worked Well

  • Using JSON for config kept things scalable. I could add new photobooks, categories, or layout rules without touching the plugin code.
  • UI simplicity helped adoption. Most users don’t care how clever your code is—they want buttons that work and layouts that don’t break.

What Didn’t Go Smoothly

  • UXP’s API limitations required constant workarounds. No evalScript meant no direct access to legacy scripting power, so enforcement had to be passive.
  • Polling for changes (like page count) felt clunky. It works, but it’s not the future.
  • Debugging layout updates could be frustrating. When users rejected a layout switch, the plugin needed to stay in sync without breaking the ruleset.

What I’d Do Differently

  • Build a custom state manager instead of relying on scattered variables and Alpine bindings.
  • Create a unified plugin context object to track file state, layout version, and page rules in one place.
  • Add more real-time UI feedback to show when rules are enforced or something has been auto-corrected.

What’s Next: The UXP Plugin Series

This post gives you the full context of a real-world UXP plugin—but we’re just getting started. Over the next few weeks, I’ll break out some of the most useful and reusable parts into focused tutorials.

Here’s what’s coming next:

  • Structuring a UXP Plugin: Folder setup, manifests, and scalable architecture
  • Creating a Gallery UI With JSON and Filters: Loading files, showing previews, and opening photobook templates
  • Enforcing Page Count Rules in InDesign UXP: Watching documents and stopping invalid actions
  • Switching Layouts Automatically Based on Page Ranges: Master page logic with layout variants
  • Building a Passive Setup Wizard With Preset Detection: Step-by-step onboarding without user friction
  • Debugging InDesign UXP Plugins (Without evalScript): Practical workarounds and state-sync tips

Final Thoughts

Building a UXP plugin for InDesign isn’t just about getting a panel to appear—it’s about solving real workflow problems inside a constrained environment. This project took a practical need and turned it into a modular, rules-based system that users can trust to do the right thing, even when they push the limits.

If you’re working on a plugin yourself—you’ll know how important structure, clarity, and fail-safes really are. My goal with this series is to make that process easier to understand and easier to build, with examples that go beyond hello-world code.

There’s more to come. This post is the foundation. The next articles will break it apart, piece by piece.

Next up: Getting Started & Sturcturing UXP →


Frequently Asked Questions

What is a UXP plugin, and why is Adobe switching to it?

A UXP (Unified Extensibility Platform) plugin is Adobe’s modern framework for building extensions across apps like InDesign and Photoshop. Unlike older CEP plugins, UXP uses a secure, modern JavaScript runtime and a lightweight HTML/CSS panel environment. Adobe is phasing out CEP, so UXP is now the standard for new plugin development.

Can anyone build a UXP plugin for InDesign?

Yes—with some JavaScript knowledge and time to learn Adobe’s APIs, anyone can build a UXP plugin. You don’t need to be an Adobe insider or have previous experience with ExtendScript. That said, InDesign’s API is less documented than Photoshop’s, so expect to spend time testing and working around quirks.

What are the main challenges of developing UXP plugins for InDesign?

The biggest challenge is limited API access. InDesign doesn’t expose as many native functions in UXP as Photoshop does, which means you often have to build workaround systems (like watchers or state checkers) instead of relying on event hooks. Debugging is also more manual, since UXP doesn’t support direct scripting bridges like ExtendScript’s evalScript.

Related Articles

Brad Holmes

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.

Thanks Brad, I found this really helpful
TOP