Skip to main content

Module 6 β€’ Lesson 2🧰 Building a Panel / Plugin

A script buried in a menu is a tool only you will ever run. Wrap it in a UXP plugin β€” a real HTML/CSS/JavaScript panel docked in Photoshop β€” and it becomes something a whole team can click. This is how a personal automation becomes a shippable product.

πŸ“š What You'll Learn

By the end of this lesson, you will be able to:

  • Describe a UXP plugin: manifest + HTML panel + JS logic
  • Write a manifest.json declaring the panel and permissions
  • Build a panel UI and wire a button to a Photoshop action
  • Load and debug it with the UXP Developer Tool
  • Do real work with the DOM inside executeAsModal, and know when to reach for batchPlay

⏱️ Estimated Time: 60 minutes

🎯 Project: A minimal working panel with one button that performs a real edit on the active document.

In This Lesson

πŸ‘€ The Goal: A Tool With a Face

Same logic, two front ends. On the right, a script you run by hunting through File β–Έ Scripts β–Έ Browse every time. On the left, a docked panel with a labeled button anyone on the team can click without knowing a line of code. The panel is the difference between a personal hack and a shippable tool. Drag the handle:

Panel Script file
A diagram of the principle. The logic is identical; the panel just gives it a permanent, discoverable home. That packaging is what turns "my script" into "our tool".

🧠 Mental Model: Three Files

A minimal UXP plugin is essentially three things: a manifest.json that tells Photoshop the plugin exists, what panel it adds, and what permissions it needs; an index.html that is the panel's UI (real HTML with UXP's spectrum widgets); and an index.js that runs when a button is clicked and does the Photoshop work. If you've built a tiny web page, you already know two-thirds of this.

The one Photoshop-specific rule: changes to the document must run inside executeAsModal (UXP's way of saying "I'm about to modify the doc, hold everything"). Inside that, you use the DOM (Lesson 6.1) or batchPlay. Everything else is ordinary front-end web development.

manifest.jsondeclares the panel+ permissions index.htmlthe panel UI(buttons, inputs) index.jsthe logic(does the work)
Figure 1: Three files. If you can write a web page with a button and a click handler, the only new idea is wrapping document edits in executeAsModal.

πŸ“„ The Manifest & the Panel

The manifest is the plugin's ID card. It names the plugin, declares a panel entrypoint, and requests permissions. Here's a minimal one, saved as manifest.json (JSON allows no comments, so copy only the braces and what's inside). "manifestVersion": 5 matters: without it Photoshop treats the file as an older manifest and ignores requiredPermissions. The one permission requested, localFileSystem: "request", is exactly what the export button needs to show a Save dialog, and nothing more:

{
  "manifestVersion": 5,
  "id": "com.ray.deliverykit",
  "name": "Delivery Kit",
  "version": "1.0.0",
  "main": "index.html",
  "host": { "app": "PS", "minVersion": "24.0.0" },
  "entrypoints": [
    { "type": "panel", "id": "main", "label": { "default": "Delivery Kit" },
      "minimumSize": { "width": 180, "height": 240 } }
  ],
  "requiredPermissions": { "localFileSystem": "request" }
}

The panel itself is just HTML. UXP provides Spectrum widgets (sp-button, sp-slider) that look native:

<!-- index.html -->
<sp-heading>Delivery Kit</sp-heading>
<sp-button id="exportBtn" variant="cta">Export web sizes</sp-button>
<script src="index.js"></script>

⚠️ Declare only the permissions you use

UXP is sandboxed on purpose: a plugin can't touch the file system or network unless the manifest asks for it, and the user approves. Request the minimum you need β€” over-asking is a security smell that will get a plugin rejected from the marketplace and distrusted by users.

πŸ”Œ Wiring a Button

The logic file listens for the click and does the work inside executeAsModal. This handler bumps a document to a web size and exports it β€” the panel's whole job, in a dozen lines:

// index.js
const { app, core, action } = require("photoshop");
const { localFileSystem } = require("uxp").storage;

document.getElementById("exportBtn").addEventListener("click", async () => {
  // Ask where to save first. A file picker is a dialog, so keep it outside the modal scope.
  const file = await localFileSystem.getFileForSaving("web.jpg", { types: ["jpg"] });
  if (!file) { return; }                             // user cancelled

  await core.executeAsModal(async () => {            // required to modify the doc
    const doc = app.activeDocument;
    if (!doc) { return; }

    const longEdge = 2000;
    const scale = longEdge / Math.max(doc.width, doc.height);   // width/height are in pixels
    await doc.resizeImage(Math.round(doc.width * scale), Math.round(doc.height * scale));

    // DOM save: JPEG quality 0–12; the final true = save as a copy
    await doc.saveAs.jpg(file, { quality: 10 }, true);
  }, { commandName: "Export web size" });
});

Note that this resizes the open document itself; run it on a flattened copy, or undo afterward. When the DOM doesn't cover a command, you drop to batchPlay inside the same executeAsModal callback. The descriptor below is a stub, not runnable as-is: paste in a real one captured with the free Alchemist plugin while you perform the command by hand.

// STUB: replace the placeholder with a descriptor captured in Alchemist
await action.batchPlay([
  { /* _obj: "…", …captured descriptor… */ }
], {});

βœ… Debug live with the UXP Developer Tool

Adobe's free UXP Developer Tool loads your plugin folder straight into Photoshop, reloads it every time you save once you turn on Watch (in the plugin's β€’β€’β€’ menu), and opens a Chrome-style devtools console for your panel. You build a UXP plugin the way you'd build a small web app: edit, save, see it update, read the console. That fast loop is what makes it approachable.

πŸ› οΈ Guided Build: A One-Button Panel

You'll scaffold the smallest plugin that does real work.

Step 1: Scaffold the files Β· 10 min

  1. Create a folder with manifest.json, index.html, and index.js from the snippets above.

Step 2: Load it Β· 10 min

  1. In Photoshop, turn on Preferences β–Έ Plugins β–Έ Enable Developer Mode and restart Photoshop. The UXP Developer Tool can't load a plugin without it.
  2. Open the UXP Developer Tool, Add Plugin, point it at your folder's manifest.json, and Load. The panel appears in Photoshop's Plugins menu.
  3. From the plugin's β€’β€’β€’ menu, choose Watch so every save reloads the panel.

Step 3: Make the button work Β· 14 min

  1. Wire the click to a simple, verifiable edit first (e.g., set the active layer's opacity to 50 via the DOM) so you know the plumbing works, then swap in the real task.

Step 4: Iterate Β· 6 min

  1. Edit, save, and watch it reload (Watch on). Read the devtools console when something fails. Add a second button. πŸ†

βœ… Project Completion Checklist

  • ☐ manifest.json (version 5) declares a panel + minimal permissions
  • ☐ index.html shows a titled panel with a button
  • ☐ index.js performs a real edit inside executeAsModal
  • ☐ Developer Mode on; loaded and Watch-reloaded via the UXP Developer Tool
  • ☐ Console used to debug at least one issue

πŸ§— Now You: Solo Variation

🌟 Your challenge

  1. Add a slider (sp-slider) that controls a value your button uses β€” e.g., the export long-edge size.
  2. Turn one of your Lesson 6.1 scripts into a panel button so it's one click instead of a menu hunt.
  3. Give the panel two buttons that run different tasks, and a status line that reports success or the error message.

Going further: package the plugin as a .ccx and install it as a real user would, or explore submitting to the Adobe marketplace β€” the path from tool to product.

🍳 Recipe Card: UXP Plugin

Manifest, panel, logic

  1. manifest.json: manifestVersion: 5, id, name, panel entrypoint, minimal permissions
  2. index.html: Spectrum widgets (sp-button, sp-slider)
  3. index.js: click handler β†’ executeAsModal β†’ DOM (batchPlay only where the DOM falls short)
  4. Enable Developer Mode; load with the UXP Developer Tool, turn on Watch; use the console
  5. Verify plumbing with a trivial edit before wiring the real task

Mantra: a panel is a web page that can edit your document.

πŸ““ Learning Journal

Add to your journal after this lesson:

  • Key concepts you learned
  • Techniques that clicked for you
  • Questions or confusion points to revisit
  • Ideas you want to try
  • Your progress and feelings about learning this

✍️ This lesson's prompt: Which of your scripts would most help your team if it had a button? Sketch its panel β€” the buttons, the one slider β€” before you build it.

πŸ“ Lesson Summary

πŸŽ“ Key Takeaways

  • A UXP plugin is three files: manifest.json (version 5, panel, minimal permissions), index.html (the panel UI), and index.js (the logic).
  • Every document edit runs inside core.executeAsModal; dialogs such as a file picker belong outside it.
  • Use the DOM first (resizeImage, saveAs.jpg); drop to batchPlay with an Alchemist-captured descriptor only where the DOM falls short.
  • Turn on Developer Mode, load with the UXP Developer Tool, and use Watch plus the console for a fast edit-save-test loop.

πŸŽ‰ What You've Accomplished

You turned a script into a tool with a face: a docked panel with a real button that resizes and exports the active document, loaded and debugged the way a developer would. That packaging step is what lets your automation reach people who will never open a code file.

❓ Common Questions at This Stage

Do I need to know web development for UXP?

Basic HTML/CSS/JS, yes. A UXP panel is a small web app, though UXP supports only a subset of browser HTML and CSS. If you've built a page with a button and a click handler you're most of the way there; the Photoshop-specific parts (executeAsModal, batchPlay, the DOM) are a small, learnable layer on top.

What is batchPlay actually doing?

Replaying a low-level Photoshop command described as a JSON "descriptor", the same mechanism behind ExtendScript's Action Descriptors. You capture the descriptor with the Alchemist plugin while doing the action once by hand, then paste it into code. It's how you script features the tidy DOM doesn't expose.

My plugin won't load in the UXP Developer Tool. What should I check first?

Three usual suspects: Developer Mode isn't enabled in Photoshop's Plugins preferences (restart after turning it on); the manifest has a JSON error or is missing manifestVersion; or the host.minVersion is newer than your Photoshop. The UDT log and the panel's devtools console usually name the problem.

πŸ”­ Looking Ahead

You can give a tool a face; now you take the human out of the loop entirely. Next: Lesson 6.3: Pipelines β€” Headless Batch, APIs & QA, calling the Photoshop API and gating output with automated QA, the Module 6 deliverable.

βœ… Before the Next Lesson

  • Get the one-button panel loading, reloading with Watch, and exporting a real JPEG.
  • Add a second button or a slider and debug at least one error in the console.
  • Write your Learning Journal entry.

πŸ“š Additional Resources

🌟 Encouragement for the Journey

The first time a teammate clicks your button and it just works, something shifts: you are no longer only the person who does the work, but the person who builds how the work gets done. That is a rare and valuable kind of craft. Keep building.