| name | adobe-plugin-development |
| description | Use when building, modifying, or debugging Adobe Photoshop UXP plugins or Lightroom Classic plugins — manifest.json/Info.lua setup, panel and dialog UI with Spectrum sp-* widgets, batchPlay action descriptors, executeAsModal, imaging.getPixels pixel access, UXP file I/O and settings persistence (storage.localFileSystem, session tokens, secureStorage), .psjs scripts, LrView data binding, LrDevelopController develop-slider observation, export/publish services, LrCatalog/LrPhoto metadata, develop presets, memory management in long-lived LrC processes, hybrid C++ native bridges, and UXP HTML/CSS/Canvas limits |
| license | MIT |
| metadata | {"version":"1.2.0"} |
Adobe Plugin Development
Reference for writing Photoshop UXP plugins (JavaScript/HTML) and Lightroom Classic plugins (Lua). Covers what the SDKs allow, what they don't, and the non-obvious pitfalls that burn hours.
API baseline: Photoshop API changelog 27.4 (Feb 2026) / UXP 9.2.0 / Manifest v5 — Adobe versions the plugin API by changelog entry, not app release, so this stays current for later 27.x builds until the API changelog advances. (PS 27.8 began replatforming UXP plugin rendering — no documented API changes.) LrC SDK 15.3 (latest published) / Lua 5.1.5. Last verified: 2026-07.
When to Use
Use when:
- Writing or editing a UXP panel/command plugin (
manifest.json, entrypoints.setup, Spectrum sp-* widgets) or a one-off .psjs script
- Writing or editing a Lightroom Classic plugin (
Info.lua, LrView, LrDevelopController)
- Reading pixel data from Photoshop (
imaging.getPixels)
- Reading/writing files or persisting settings and secrets from UXP (
storage.localFileSystem, tokens, secureStorage)
- Wiring develop-slider observation in LrC
- Displaying generated images inside a UXP panel (no HTML canvas pixel ops!)
- Bridging LrC to a native binary for pixel processing
- Debugging memory leaks in long-lived LrC floating dialogs
- Using batchPlay for Photoshop action descriptors
- Manipulating Document/Layer DOM (create, modify, filter, composite)
- Recording or replaying Photoshop actions programmatically
Don't use for:
- General web UI — UXP is NOT a browser; different rules apply
- ExtendScript / CEP (legacy Adobe plugin systems) — this covers UXP only
- Lightroom Cloud REST APIs — those are separate from the LrC desktop SDK
Core Capability Matrix
| Capability | Photoshop UXP | Lightroom Classic |
|---|
| Language | JavaScript (HTML/CSS) | Lua 5.1.5 |
| Pixel read | imaging.getPixels() | None — must export rendition + decode externally |
| Pixel write | imaging.putPixels() | None |
| UI | HTML/CSS/JS + Spectrum sp-* | LrView declarative widgets only |
| Canvas/drawing | Limited 2D (no drawImage/getImageData/toDataURL) | None |
| Document DOM | Full: 36 Document props + 29 methods, 28 Layer props + 55 methods (38 filter + 17 other) | Catalog-centric: LrCatalog, LrPhoto |
| Selection API | Full Selection class (v25.0+) | LrSelection namespace (rating, flag, label, nav) |
| AI features | generativeUpscale (v27.2+) | denoise, reflection removal, distraction detection |
| Develop events | action.addNotificationListener | LrDevelopController.addAdjustmentChangeObserver |
| File I/O | uxp.storage.localFileSystem — permission-gated pickers, plugin folders, tokens | Full Lua io library |
| Plugin types | Panel, Command, .psjs script | Export, Publish, Metadata, Filter, Web Gallery, Library menu |
| Develop-panel UI | Panels dock anywhere | Cannot add Develop-right-panel UI — only floating dialog |
| Action recording | recordAction API (v25.0+) | Not available |
Fundamental constraint: LrC cannot read pixels from Lua. Any LrC plugin needing pixel access must export a thumbnail or rendition and hand it to a compiled external binary (C++/Rust).
Critical Pitfalls
UXP: <canvas> has no pixel ops
<canvas> exists (v7.0.0+) but has no drawImage, getImageData, putImageData, toDataURL, toBlob.
- To display a generated image: build pixel buffer ->
imaging.createImageDataFromBuffer -> imaging.encodeImageData({ base64: true }) -> set <img src="data:image/jpeg;base64,...">.
- Lossless alternative (v7.0+, PS-only):
new ImageBlob(buffer, { width, height, components, componentSize, pixelFormat, colorSpace, hasAlpha }) -> img.src = URL.createObjectURL(blob) — skips the JPEG round-trip; accepts a PhotoshopImageData directly as options.
- Windows extra sharp edges:
createLinearGradient, createRadialGradient, clearRect may fail entirely.
- Real-render options: software renderer in JS, embedded WebView, or hybrid C++ plugin.
UXP: Imaging API gotchas
- 16-bit range is 0-32768 by default, not 0-65535. Pass
{ fullRange: true } to getData() for full range.
- Always call
imageData.dispose() after getData(). UDT warns at 600MB.
- Pass
targetSize to leverage Photoshop's pyramid cache — dramatically faster than full-res reads.
encodeImageData only supports RGB (not Lab, not Grayscale).
getPixels() usually works without executeAsModal, but any pixel write requires it.
- Default layout is "chunky" (interleaved
RGBRGB); planar is RRGGBB.
UXP: Layout and CSS limits
- No CSS Grid, no
float, no transition/@keyframes, no text-transform, no font shorthand, no position: sticky.
box-shadow, transform-origin, scaleX/scaleY, translate require the CSSNextSupport feature flag in manifest. Without it, CSS silently ignores these properties.
window.devicePixelRatio always returns 1 — cannot detect HiDPI.
hide panel lifecycle callback never fires; show fires only once (PS-57284). Don't rely on them for refresh gating.
<label for="id"> doesn't work — wrap the label around the control instead.
<option> needs an explicit value attribute.
UXP: CSSNextSupport feature flag
Enable in manifest.json to unlock additional CSS properties:
{
"featureFlags": { "CSSNextSupport": true }
}
Unlocks: box-shadow, transform-origin, scaleX, scaleY, translate. Also accepts a granular array (["boxShadow", "transformFunctions", "transformProperties"]). Without the flag these properties are silently ignored — no error, no warning — except that "enableSWCSupport": true implicitly enables CSSNextSupport.
UXP: localStorage and sessionStorage
Both are available as global storage APIs — contrary to some older references listing them as unavailable. However, they are not available inside WebView when loading local content. Never store secrets in them — use storage.secureStorage (encrypted; getItem returns a Uint8Array; treat it as a re-obtainable cache, not durable storage).
UXP: DOM file APIs take Entries; batchPlay takes session tokens
document.saveAs.*() and app.open() take UXP storage File entries — never path strings. batchPlay descriptors take neither: convert with fs.createSessionToken(entry) and pass it as _path. For ask-once access across restarts, store a createPersistentToken(entry) and re-prompt when redemption fails.
UXP: WebView availability
WebView works in panels since UXP 6.4 / PS 24.1 (not just modal dialogs). Local HTML files supported since UXP 8.0. Domain restrictions optional since UXP 9.0. The permissions.webview.allow field was removed in UXP 9.1 (PS 27.4) — configure with domains only.
UXP: executeAsModal anti-patterns
- Never swallow exceptions:
try { await batchPlay(...) } catch(e) {} prevents automatic cancellation termination. Let exceptions propagate.
timeOut option (v25.10): modal collisions now retry for timeOut duration instead of immediate error.
updateUI() (v26.0) has no effect outside tracking contexts (slider handlers).
UXP: batchPlay error handling
batchPlay execution errors resolve (not reject) with error objects in the result array. Always check return values:
const results = await action.batchPlay([descriptor], {});
if (results[0].message) {
}
LrC: Long-running plugins leak catastrophically
A floating dialog running for hours in LrC's sandbox is unforgiving. Non-negotiable rules:
- Frame alternation is mandatory.
f:picture must receive a different path each update. Writing the same path repeatedly causes LrC to cache every version internally without releasing — this caused a 40GB leak in chromascope. Alternate scope_0.jpg / scope_1.jpg.
- Guard
requestJpegThumbnail callbacks. After done = true, subsequent callbacks must return immediately. Nil out jpegData after writing. Must hold reference to the returned request object or it may be garbage collected.
- Debounce async tasks with a version counter. Slider drags spawn hundreds of coroutines without debouncing. Use a
_settleVersion / _adjustVersion counter and drop stale callbacks.
- Busy-guard + pending flag coalescing. If a render is in progress, set a pending flag instead of spawning another. When render completes, check pending and re-run once.
- No unbounded module-level state. Only fixed-size vars: busy flag, frame index, pending flag, settings hash. Never grow a list.
- Clean up temp files on dialog open. LrC sandbox blocks
collectgarbage — there's no GC escape hatch.
LrC: Develop module can't have custom panels
LrC SDK exposes Library menu items, export dialogs, metadata fields — never a Develop-right-panel UI. The only realistic develop-time feedback loop is a floating dialog (LrDialogs.presentFloatingDialog) launched from a Library menu item.
LrC: Observing "active photo changed"
addAdjustmentChangeObserver covers slider changes only. In a floating dialog, pass selectionChangeObserver (and sourceChangeObserver) to LrDialogs.presentFloatingDialog — LrC calls it when the selected photos/videos change; then read catalog:getTargetPhoto(). Outside a floating dialog there is no event — poll catalog:getTargetPhoto() in a LrTasks.startAsyncTask loop with coalescing.
LrC: requestJpegThumbnail reference retention
Must hold a reference to the returned request object. If the return value is not stored, Lua's garbage collector may collect it and the callback never fires.
LrC: getDevelopSettings has typos in official API
Known typos in parameter names returned by getDevelopSettings: HueAdjustmentMagenha (should be Magenta), LuminanceAdjustmentAque (should be Aqua), Parametriclights (inconsistent casing). Use exact typo'd names when reading these values.
LrC: processRenderedPhotos task context
processRenderedPhotos runs in a task LrC creates — do not create your own LrTasks.startAsyncTask inside it. Doing so nests tasks incorrectly.
LrC: LrDialogs unavailable during shutdown
The LrDialogs namespace is not available during LrShutdownApp. Do not attempt to show dialogs in shutdown scripts.
LrC: withWriteAccessDo undo coalescing (Mac)
On Mac, successive withWriteAccessDo calls without user interaction coalesce into a single undo event. Design accordingly if undo granularity matters.
UXP Quick Reference
Read active document pixels
const { core, app, imaging } = require('photoshop');
await core.executeAsModal(async () => {
const doc = app.activeDocument;
const result = await imaging.getPixels({
documentID: doc.id,
targetSize: { width: 256, height: 256 },
colorSpace: "RGB",
componentSize: 8
});
const pixels = await result.imageData.getData();
result.imageData.dispose();
}, { commandName: "Analyze" });
Display a software-rendered image
const imageData = await imaging.createImageDataFromBuffer(buffer, {
width, height, components: 3, colorSpace: "RGB"
});
const jpegBase64 = await imaging.encodeImageData({ imageData, base64: true });
img.src = "data:image/jpeg;base64," + jpegBase64;
imageData.dispose();
Document and layer operations
const doc = app.activeDocument;
await core.executeAsModal(async (ctx) => {
await doc.suspendHistory(async () => {
const layer = await doc.createLayer();
await layer.applyGaussianBlur(5.0);
}, "My Edit");
}, { commandName: "Edit Layer" });
batchPlay basics
const { action } = require('photoshop');
const result = await action.batchPlay([{
_obj: "get",
_target: [{ _property: "opacity" },
{ _ref: "layer", _enum: "ordinal", _value: "targetEnum" },
{ _ref: "document", _enum: "ordinal", _value: "targetEnum" }],
_options: { dialogOptions: "silent" }
}], {});
multiGet for bulk reads (more efficient than multiple get calls):
const result = await action.batchPlay([{
_obj: "multiGet",
_target: [{ _ref: "document", _enum: "ordinal", _value: "targetEnum" }],
extendedReference: [["width", "height", "resolution", "mode"]]
}], {});
Discovery: Use Photoshop's "Copy As JavaScript" from the Actions panel to capture descriptors. Use action.addNotificationListener to observe descriptors fired by manual operations.
Spectrum UXP component catalog
| Component | Key attributes / events |
|---|
sp-action-button | quiet, selected, disabled, click |
sp-action-group | compact, justified, quiet — SWC-originated, needs enableSWCSupport |
sp-action-menu | placement, quiet, change — menu with sp-menu-item children; SWC-originated, needs enableSWCSupport |
sp-body | Typography body text |
sp-button | variant (cta default/primary/secondary/warning/overBackground), quiet (not with cta), disabled, click |
sp-button-group | Container for button sets — SWC-originated, needs enableSWCSupport |
sp-checkbox | checked, indeterminate, disabled, change |
sp-detail | Typography detail text |
sp-divider | size (small/medium/large) |
sp-dropdown | placeholder, quiet, change — wraps sp-menu |
sp-heading | Typography heading text |
sp-icon | name, size (xxs/xs/s/m/l/xl/xxl) — 36 built-in icons |
sp-label | Typography label text |
sp-link | href, quiet |
sp-menu | Wraps sp-menu-item and sp-menu-divider |
sp-menu-item | selected, disabled |
sp-progressbar | max, value, value-label, show-value, size — label via <sp-label slot="label"> |
sp-radio | checked, disabled — listen for change on the group |
sp-radio-group | column, change (read evt.target.value) |
sp-slider | min, max, value, disabled, variant="filled", show-value, input/change — no step |
sp-textfield | placeholder, quiet, type, disabled, input/change |
sp-textarea | placeholder, quiet, disabled, input/change |
SWC (Spectrum Web Components): npm-wrapped packages for advanced Spectrum UI. Requires enableSWCSupport: true in manifest (which implicitly enables CSSNextSupport). Locked to v0.37.0 in UXP 8.0. Provides components not available as sp-* (picker, number-field, toast, tabs, accordion, table, etc.).
CSS support summary
Supported: flexbox (all properties), box model (margin, padding, border, width/height, overflow), typography (font-family, font-size, font-weight, line-height, text-align, color, text-decoration, white-space, word-wrap), backgrounds (background-color, background-image incl. linear-gradient/radial-gradient since v2.0 — v8.1 aligned gradient start/end syntax with the web), opacity, calc(), CSS custom properties (--var), pseudo-classes (:hover, :nth-child, :first-child, …) and ::before/::after (v3.0+).
With CSSNextSupport flag: box-shadow, transform-origin, scaleX, scaleY, translate.
NOT supported: Grid, transition, animation/@keyframes, float, text-transform, font shorthand, position: sticky.
Theme CSS variables (auto-update on theme change):
--uxp-host-background-color, --uxp-host-text-color, --uxp-host-text-color-secondary, --uxp-host-border-color, --uxp-host-link-text-color, --uxp-host-link-hover-text-color, --uxp-host-label-text-color, --uxp-host-widget-hover-background-color, --uxp-host-widget-hover-text-color, --uxp-host-widget-hover-border-color. Font sizes: --uxp-host-font-size, --uxp-host-font-size-smaller, --uxp-host-font-size-larger. Also supports @media (prefers-color-scheme: dark|light). Spectrum sp-* widgets auto-theme without extra CSS.
Panel lifecycle and entrypoints
const { entrypoints } = require("uxp");
entrypoints.setup({
plugin: { create() {}, destroy() {} },
panels: {
myPanel: {
create() { },
show() { },
hide() { },
destroy() { },
menuItems: [
{ id: "reload", label: "Reload", enabled: true, checked: false }
],
invokeMenu(menuId) {
if (menuId === "reload") refresh();
}
}
},
commands: {
myCommand: { run() { } }
}
});
- Panel
show/hide are unreliable (PS-57284). Use document.visibilitychange if available.
- 300ms timeout on panel
create — defer heavy init with setTimeout.
Manifest v5 essentials
{
"manifestVersion": 5,
"id": "com.example.myplugin",
"name": "My Plugin",
"version": "1.0.0",
"main": "index.html",
"host": { "app": "PS", "minVersion": "24.0.0",
"data": { "apiVersion": 2, "loadEvent": "use" } },
"requiredPermissions": {
"localFileSystem": "fullAccess",
"network": { "domains": ["https://api.example.com"] },
"clipboard": "readAndWrite",
"webview": { "domains": ["https://example.com"] }
},
"featureFlags": { "enableSWCSupport": true, "CSSNextSupport": true }
}
apiVersion: 2 required for modern imaging API
loadEvent: "use" (lazy) vs "startup" (eager)
enableMenuRecording: true — makes the plugin's menu commands recordable as Action steps ("Plugin Menu Command"); the separate recordAction API needs no flag
File I/O and persistence
const { storage } = require('uxp');
const fs = storage.localFileSystem;
const file = await fs.getFileForOpening({ types: storage.fileTypes.images });
const out = await fs.getFileForSaving("out.png", { types: ["png"] });
const data = await fs.getDataFolder();
await out.write(buffer, { format: storage.formats.binary });
const token = fs.createSessionToken(file);
await action.batchPlay([{ _obj: "open", target: { _path: token, _kind: "local" } }], {});
Where to persist: prefs → localStorage or a plugin-data:/ file; secrets → storage.secureStorage (encrypted; getItem returns Uint8Array; can be lost — treat as cache); remembered file access → createPersistentToken. Full API in ps-uxp-sdk.md (File I/O & Storage).
Event system
const { action, core } = require('photoshop');
await action.addNotificationListener(
['set', 'select', 'make', 'delete', 'open'],
(name, desc) => scheduleRefresh()
);
await core.addNotificationListener('UI', ['userIdle'], refresh);
executeAsModal
await core.executeAsModal(async (executionContext) => {
const hostControl = executionContext.hostControl;
const suspensionID = await hostControl.suspendHistory({ documentID: doc.id, name: "My Op" });
await hostControl.resumeHistory(suspensionID);
}, {
commandName: "My Operation",
});
- PS only creates a history state if document was actually modified
- Never swallow exceptions — prevents cancel propagation
- Nested
executeAsModal calls are allowed
LrC Quick Reference
LrC plugin lifecycle
Info.lua → LrInitPlugin → on-demand script loading (or LrForceInitPlugin at startup)
LrShutdownApp returns { LrShutdownFunction = function(doneFunc, progressFunc) } with 10-second timeout
LrDialogs is NOT available during LrShutdownApp
- Installation paths:
- macOS:
~/Library/Application Support/Adobe/Lightroom/Modules/
- Windows:
%APPDATA%\Adobe\Lightroom\Modules\
- Auto-loaded plugins can't be removed, only disabled
- Debugging:
LrLogger with enable('print') or enable('logfile')
- Mac logs:
~/Library/Logs/Adobe/Lightroom/LrClassicLogs/
- Windows logs:
%LOCALAPPDATA%\Adobe\Lightroom\Logs\LrClassicLogs\
Observe develop-slider changes
LrFunctionContext.callWithContext("observer", function(context)
LrDevelopController.addAdjustmentChangeObserver(context, {}, function()
scheduleRerender()
end)
end)
Export thumbnail and pipe to external tool
local done = false
local request = photo:requestJpegThumbnail(w, h, function(jpegData, errorMsg)
if done then return end
if not jpegData then return end
done = true
LrFileUtils.writeFile(tempJpegPath, jpegData)
jpegData = nil
LrTasks.execute(binaryPath .. " decode --input " .. tempJpegPath .. " ...")
end)
Live-updating picture display (alternating paths)
local frameIndex = 0
local function nextScopePath()
frameIndex = (frameIndex + 1) % 2
return scopeDir .. "/scope_" .. frameIndex .. ".jpg"
end
viewFactory:picture { value = LrView.bind("scopePath"), width = 256, height = 256 }
Debounced async rerender with busy-guard
local _settleVersion = 0
local _busy = false
local _pending = false
local function scheduleRerender()
_settleVersion = _settleVersion + 1
local myVersion = _settleVersion
LrTasks.startAsyncTask(function()
LrTasks.sleep(0.1)
if myVersion ~= _settleVersion then return end
if _busy then _pending = true; return end
_busy = true
_busy = false
if _pending then _pending = false; scheduleRerender() end
end)
end
Info.lua manifest fields (full 23-field table with types/SDK versions in lrc-sdk.md §1)
- Required:
LrSdkVersion, LrToolkitIdentifier (reverse-domain ID), LrPluginName (2.0+)
- Menus:
LrLibraryMenuItems (Library > Plug-in Extras), LrExportMenuItems (File > Plug-in Extras), LrHelpMenuItems
- Providers:
LrExportServiceProvider (export/publish), LrExportFilterProvider, LrMetadataProvider, LrMetadataTagsetFactory
- Lifecycle:
LrInitPlugin, LrForceInitPlugin (4.0 — init at startup), LrEnablePlugin/LrDisablePlugin (3.0), LrShutdownPlugin (3.0), LrShutdownApp (4.0 — LrDialogs NOT available)
- Plugin Manager:
LrPluginInfoProvider, LrPluginInfoUrl; Misc: LrSdkMinimumVersion, URLHandler (4.0 — script file, string), LrAlsoUseBuiltInTranslations, LrLimitNumberOfTempRenditions (Boolean throttle — plugin must delete each temp rendition), VERSION { major, minor, revision (numbers), build (string), display (string?) }
Data binding patterns
f:edit_field { value = LrView.bind("myKey") }
f:static_text { title = LrView.bind { key = "tempC", transform = function(value)
return string.format("%.1f F", value * 9/5 + 32)
end } }
f:push_button { enabled = LrBinding.keyEquals("mode", "advanced") }
f:edit_field { value = LrView.bind { bind_to_object = otherTable, key = "sharedKey" } }
local updating = false
props:addObserver("inputKey", function(t, k, v)
if updating then return end
updating = true
t.outputKey = v * 2
updating = false
end)
Develop parameters & LrDevelopController — full catalogs in lrc-sdk.md §7
lrc-sdk.md is the single source of truth for: every develop parameter by panel (adjustPanel, tonePanel, mixerPanel, colorGradingPanel, detailPanel, effectsPanel, lensCorrectionsPanel, calibratePanel, lensBlurPanel, plus straightenAngle), local-adjustment parameters by process version (v2: 8 · v3/v4: 18 · v5: 25 · v6: 27), plugin develop presets, and all 95 LrDevelopController functions with exact signatures (get/set + tracking, tools, masks SDK 11+, spots SDK 14.1+, AI/enhance SDK 14.5+, point color SDK 13.2+, lens blur SDK 13.3+, color grading, process version, remove panel, navigation/reset).
Most-used calls (parameter names are case-sensitive):
LrDevelopController.getValue(param)
LrDevelopController.setValue(param, value)
LrDevelopController.getRange(param)
LrDevelopController.addAdjustmentChangeObserver(context, {}, callback)
LrDevelopController.revealPanel("adjustPanel")
LrDevelopController.selectTool("masking")
Common Mistakes
| Mistake | Fix |
|---|
Calling canvas.toDataURL() in UXP | Not supported. Use imaging.encodeImageData({ base64: true }). |
Skipping imageData.dispose() | Leaks native memory. UDT warns at 600MB. |
Writing LrC f:picture to same path repeatedly | LrC caches every version — massive leak. Alternate two paths. |
Running collectgarbage() in LrC | Blocked by sandbox. Design around it (fixed-size state, path alternation). |
| No debounce on LrC develop observer | Drags spawn hundreds of coroutines. Use version-counter pattern. |
CSS Grid / transform / transition in UXP | Grid and transitions not supported. Transforms require CSSNextSupport flag. |
z-index for overlays in UXP | Supported, but nothing can overlay a text-editing widget. Prefer DOM/flex order for overlays. |
| 16-bit UXP pixels expecting 0-65535 | Default is 0-32768. Pass { fullRange: true } to getData(). |
| Trying to add LrC UI to Develop panel | Not a supported plugin type. Use floating dialog from Library menu. |
Polling inside requestJpegThumbnail callback without done guard | Callback may fire more than once. Set and check done. |
Using <label for="id"> in UXP | Not supported. Wrap the label around the control. |
| Swallowing batchPlay exceptions in try/catch | Prevents cancel propagation. Let exceptions from executeAsModal propagate. |
Using CSS transforms without CSSNextSupport flag | Properties silently ignored. Add featureFlags: { "CSSNextSupport": true } to manifest. |
Creating tasks inside processRenderedPhotos | LrC already runs it in a task. Nesting tasks causes incorrect behavior. |
Calling LrDialogs during LrShutdownApp | Namespace unavailable during app shutdown. Use LrShutdownPlugin instead. |
Not holding requestJpegThumbnail return value | May be garbage collected, callback never fires. Store in a local variable. |
Using break in LrC directoryEntries/files/recursiveFiles loops | Not safe. Collect results into a table or use a flag + early return. |
Modifying nested tables in LrPrefs expecting auto-save | Only top-level key reassignment triggers persistence. Reassign the root key after mutating nested values. Use prefs:pairs() not Lua pairs(prefs). |
Passing a path string to saveAs/open/batchPlay in UXP | DOM methods take storage File entries; batchPlay _path takes fs.createSessionToken(entry). |
Storing secrets in UXP localStorage | Use storage.secureStorage — but treat it as a re-obtainable cache; getItem returns a Uint8Array. |
Deeper References (in this skill directory)
lrc-sdk.md — Full Lightroom Classic SDK reference: all plugin types (export, publish, filter, metadata), Info.lua manifest, packaging (.lrplugin/.lrdevplugin), LrView widgets and data binding, LrDevelopController (95 functions) + develop parameter catalogs, plugin develop presets, LrCatalog method inventory, programmatic export (LrExportSession + destination keys), publish-service callback catalog, full module catalog (45+ modules), LrC memory-leak prevention details, external communication (LrSocket, LrHttp, Controller SDK), and the processor CLI spec for LrC-to-Rust bridging.
ps-uxp-sdk.md — Full Photoshop UXP reference: Manifest v5, Document/Layer DOM (36+29 Document, 28+55 Layer properties/methods, 38 filter methods), Selection class, Imaging API full signatures + ImageBlob display, File I/O & storage (pickers, plugin folders, session/persistent tokens, secureStorage), batchPlay and action system (5 reference forms, action recording), executeAsModal details, .psjs scripts, photoshop.constants, UDT workflow, XMP metadata, programmatic path creation, localization, Spectrum UXP + SWC component catalogs, HTML/CSS support and limitations, Canvas API limits, event system, text/typography API, color management, external communication (WebView, hybrid C++ bridge, network, shell), and the known-issues catalog.
When to load deep references:
ps-uxp-sdk.md — when using batchPlay descriptors, Document/Layer DOM methods, Selection class, filter methods, file I/O and tokens, SWC components, text/typography APIs, or debugging known issues
lrc-sdk.md — when building export/publish services (callbacks, programmatic export), working with catalog queries (findPhotos, custom metadata, keywords, collections), applying develop presets, building UIs with LrDialogs.presentFloatingDialog, using LrSocket/Controller SDK, or needing the full module API reference
The quick-ref snippets above cover common cases. Load deep references only for non-trivial implementations.