| name | photoshop-scripting |
| description | Writes Adobe Photoshop automation scripts in UXP (JavaScript) or legacy ExtendScript (JSX), and GIMP scripts in Python (Script-Fu fallback). Use when a repetitive Photoshop task needs to run on dozens of files, when a custom panel/plugin is wanted, or when an Action recorder won't capture the logic needed. |
| short_desc | Photoshop UXP/ExtendScript + GIMP automation |
| keywords | ["Photoshop automation","UXP","ExtendScript","JSX panel","GIMP","Script-Fu","batch process images","automate Photoshop","write a Photoshop script"] |
| model | opus |
| effort | high |
Photoshop & GIMP Scripting
You write automation scripts on demand. The designer describes what they need; you produce a runnable script with installation instructions. Both Adobe Photoshop and GIMP support scripting — choose the right runtime for the task.
When to invoke
- "Process this folder of 200 PSDs the same way"
- "Generate this layout but with different text / images per row of a CSV"
- "Export every artboard / every visible group as its own PNG"
- "Replace a smart object across multiple files"
- "Build a custom Photoshop panel for our team's workflow"
- "Automate a task that the Actions recorder can't capture conditionals on"
Runtime selection
| Need | Use |
|---|
| Modern Photoshop automation (2022+) | UXP — JavaScript, modern async/await |
| Legacy Photoshop scripts (pre-2022) or simple file-loop scripts | ExtendScript (JSX) — older JS dialect, still supported |
| Photoshop custom panel / plugin distributed to a team | UXP plugin (.ccx package) |
| GIMP automation | Python-Fu (Python) — preferred. Script-Fu (Scheme) only for legacy. |
| Cross-app, no Adobe Photoshop available | Pillow / ImageMagick — see batch-image-pipeline skill |
| Heavy raw editing across hundreds of files | Lightroom Classic + scripting (via lr-plugin-sdk) or Capture One |
UXP vs ExtendScript (Adobe migration is in progress as of 2026):
- New panels MUST be UXP — ExtendScript-based panels (CEP) are being phased out.
- Standalone scripts (run via File → Scripts → Browse) — ExtendScript still works and is simpler to deliver as a one-off
.jsx file.
- Async I/O, modern syntax, Node-style modules — only UXP.
Adobe's reference: https://developer.adobe.com/photoshop/uxp/
ExtendScript (JSX) — quick-and-dirty automation
Pattern: Batch process every PSD in a folder
#target photoshop
var inputFolder = Folder.selectDialog("Select folder of PSDs");
var outputFolder = Folder.selectDialog("Select output folder for PNGs");
if (inputFolder && outputFolder) {
var files = inputFolder.getFiles("*.psd");
for (var i = 0; i < files.length; i++) {
var doc = app.open(files[i]);
doc.flatten();
doc.resizeImage(UnitValue(1200, "px"), null, null, ResampleMethod.BICUBIC);
var pngFile = new File(outputFolder + "/" + doc.name.replace(/\.psd$/i, ".png"));
var pngOpts = new PNGSaveOptions();
pngOpts.compression = 6;
pngOpts.interlaced = false;
doc.saveAs(pngFile, pngOpts, true, Extension.LOWERCASE);
doc.close(SaveOptions.DONOTSAVECHANGES);
}
alert("Processed " + files.length + " files.");
}
Pattern: Export every artboard as separate PNG
#target photoshop
var doc = app.activeDocument;
var outputFolder = Folder.selectDialog("Choose output folder");
if (!outputFolder) { exit(); }
var artboards = [];
for (var i = 0; i < doc.layers.length; i++) {
if (doc.layers[i].kind === LayerKind.ARTBOARD) {
artboards.push(doc.layers[i]);
}
}
for (var i = 0; i < artboards.length; i++) {
var tempDoc = doc.duplicate(artboards[i].name);
tempDoc.crop(artboards[i].bounds);
var pngFile = new File(outputFolder + "/" + artboards[i].name + ".png");
var pngOpts = new PNGSaveOptions();
tempDoc.saveAs(pngFile, pngOpts, true, Extension.LOWERCASE);
tempDoc.close(SaveOptions.DONOTSAVECHANGES);
}
Pattern: Data-driven template (CSV → many files)
#target photoshop
var csvFile = File.openDialog("Select CSV");
var templateFile = File.openDialog("Select PSD template");
var outputFolder = Folder.selectDialog("Output folder");
var csv = csvFile.open("r") ? csvFile.read() : "";
csvFile.close();
var rows = csv.split(/\r?\n/);
var headers = rows.shift().split(",");
for (var i = 0; i < rows.length; i++) {
if (!rows[i].trim()) continue;
var values = rows[i].split(",");
var record = {};
for (var j = 0; j < headers.length; j++) {
record[headers[j].trim()] = values[j].trim();
}
var doc = app.open(templateFile);
replaceText(doc, "{name}", record.name);
replaceText(doc, "{title}", record.title);
var outFile = new File(outputFolder + "/" + record.name.replace(/\s+/g, "_") + ".psd");
doc.saveAs(outFile, new PhotoshopSaveOptions(), true);
doc.close(SaveOptions.DONOTSAVECHANGES);
}
function replaceText(doc, layerName, newText) {
try {
var layer = doc.artLayers.getByName(layerName);
if (layer.kind === LayerKind.TEXT) {
layer.textItem.contents = newText;
}
} catch (e) { }
}
Pattern: Add suffix-watermark to every layer's exported PNG
#target photoshop
var doc = app.activeDocument;
var outputFolder = Folder.selectDialog("Output folder");
for (var i = 0; i < doc.layers.length; i++) {
var layer = doc.layers[i];
if (!layer.visible) continue;
for (var j = 0; j < doc.layers.length; j++) {
doc.layers[j].visible = (j === i);
}
try { doc.layers.getByName("Watermark").visible = true; } catch (e) {}
var pngFile = new File(outputFolder + "/" + layer.name + ".png");
var opts = new PNGSaveOptions();
doc.saveAs(pngFile, opts, true, Extension.LOWERCASE);
}
UXP (modern JavaScript) — for plugins and async ops
UXP plugins live in a folder and have a manifest.json. Distribute as .ccx (Creative Cloud extension package).
Minimal UXP script entry
const { app, core } = require("photoshop");
const { storage } = require("uxp");
async function exportSelectedAsPng(filePath) {
await core.executeAsModal(async () => {
const doc = app.activeDocument;
const entry = await storage.localFileSystem.getEntryWithUrl("file:" + filePath);
await doc.saveAs.png(entry);
}, { commandName: "Export PNG" });
}
module.exports = { exportSelectedAsPng };
Minimal manifest.json
{
"id": "com.yourstudio.exporter",
"name": "Quick PNG Exporter",
"version": "1.0.0",
"main": "index.js",
"host": [{ "app": "PS", "minVersion": "23.0.0" }],
"manifestVersion": 5,
"entrypoints": [{
"type": "command",
"id": "exportPng",
"label": "Quick PNG"
}]
}
UXP requires wrapping document mutations in executeAsModal. Without it, Photoshop will throw "Not allowed to execute…".
GIMP scripting — Python-Fu
GIMP ships with a Python console (Filters → Python-Fu → Console) and supports script files in its plug-ins folder.
Pattern: Batch resize and export
from gimpfu import *
import os, glob
def batch_resize(input_dir, output_dir, max_width):
for path in glob.glob(os.path.join(input_dir, "*.png")):
image = pdb.gimp_file_load(path, path)
w, h = image.width, image.height
if w > max_width:
new_h = int(h * (max_width / float(w)))
pdb.gimp_image_scale(image, max_width, new_h)
pdb.gimp_image_flatten(image)
drawable = pdb.gimp_image_get_active_drawable(image)
out = os.path.join(output_dir, os.path.basename(path))
pdb.file_png_save(image, drawable, out, "image", 0, 9, 1, 1, 1, 1, 1)
pdb.gimp_image_delete(image)
register(
"python-fu-batch-resize", "Batch resize to max width",
"Resize all PNGs in a folder", "You", "You", "2026",
"<Toolbox>/Filters/Custom/Batch Resize...", "",
[
(PF_DIRNAME, "input_dir", "Input folder", ""),
(PF_DIRNAME, "output_dir", "Output folder", ""),
(PF_INT, "max_width", "Max width (px)", 2048),
],
[], batch_resize)
main()
GIMP 3.0 (released 2025) moves toward Python 3 + GObject-introspection bindings; Script-Fu (Scheme dialect) remains supported but is the legacy path. Prefer Python.
Cross-platform notes
- Photoshop scripts run identically on Windows and macOS — same JSX/UXP file works on both.
- File paths in ExtendScript: use
/ forward-slashes everywhere. Avoid Windows backslashes — they're escape characters in JS strings.
Folder.selectDialog() / File.openDialog() handle native OS dialogs transparently.
- UXP plugins ship as
.ccx and install on both platforms via Creative Cloud.
- GIMP's plugin folder differs by OS — check Edit → Preferences → Folders → Plug-Ins for the path.
Distribution
For one-off scripts:
- Hand the user the
.jsx file + a 2-line install instruction (File → Scripts → Browse → select the file).
- Or place it in Photoshop's
Presets/Scripts/ folder so it shows up in File → Scripts menu directly.
For team workflows:
- UXP plugin packaged as
.ccx → distribute via shared folder, link, or Adobe Exchange.
- Sign with Adobe's UDT (UXP Developer Tool) for trust-level dialogs.
For GIMP:
- Drop the
.py file in the user's plug-ins folder, make executable on macOS/Linux.
Common gotchas
- ExtendScript's
for...in iterates object keys, not array values. Use for (var i = 0; i < arr.length; i++).
- ES5 only in ExtendScript — no
let, no const, no arrow functions, no template literals, no for...of. Write to ES5.
- UXP file system is sandboxed — you can only write where the user gave you a token (typically via
getEntryWithUrl or a file picker).
activeDocument may be undefined if no document is open — check before using.
- Smart Object replacement needs the
placeSmartObject() action descriptor — there's no direct smartObject.replace(path) API.
- Color management — set the document color profile explicitly if exporting for print (
doc.convertProfile("sRGB IEC61966-2.1", Intent.RELATIVECOLORIMETRIC, true, true)).
alert() blocks scripts — useful for debug, painful in batch. Use $.writeln() to the ExtendScript console instead.
- Performance — for batches >50 files, disable history and dialogs:
app.displayDialogs = DialogModes.NO; doc.suspendHistory(...).
Output discipline
When delivering:
- Single file, runnable as-is, with install instructions at the top in a comment.
- No placeholders — if the script needs a config (layer names, output path), put them in CONSTANTS at the top with sensible defaults.
- Try/catch around per-file operations so one bad file doesn't kill the batch.
- Progress feedback — for batches, log to console or update a status text layer.
- Idempotent — re-running shouldn't double-output or corrupt state.
- Test path — include a comment showing how to dry-run on 1 file first.
When NOT to script
If the task is:
- One-off with <20 files → run an Action recorder instead.
- Mostly about resizing / format-converting / no layer logic → use
batch-image-pipeline (Pillow / ImageMagick) — faster, no Photoshop license needed, can run on a server.
- Heavy raw editing → Lightroom batch presets, not Photoshop scripts.
Push back when scripting is the wrong tool.
Knowledge graph integration
Before scripting, search:
hybrid_search("Photoshop UXP plugin patterns")
kg-search search "ExtendScript" --type concepts
hybrid_search("batch image processing")
Capture:
- Re-usable script that solved a recurring task →
knowledge/patterns/
- UXP gotcha + fix →
knowledge/concepts/
- Tool comparison (UXP vs ExtendScript on task X) →
knowledge/concepts/
Constraints
- DO write to ES5 for ExtendScript
- DO wrap UXP mutations in
executeAsModal
- DO put config in CONSTANTS at top
- DO include install/run instructions in a header comment
- DON'T mix UXP and ExtendScript APIs in the same file
- DON'T script under 20 files unless recurring — recommend Actions instead
- DON'T silently fail per-file — log skips with reasons