crdtuxpPHXS

crdtuxpPHXS

Creative Developer Tools (CRDT) is a growing suite of tools aimed at script developers
and plug-in developers for the Adobe Creative Cloud eco-system.

This module provides functions that are specific to Adobe Photoshop.

Started (2026) with a per-document config-layer API - a dedicated layer group used as an INI-style settings store inside the PSD itself, since Photoshop (unlike InDesign) has no pasteboard and most of its own preferences are global rather than per-document. See PXConfig (https://github.com/zwettemaan/PXConfig) for the first consumer.

Intended to grow into a general collection of Photoshop-specific UXP/UXPScript helpers over time, the way crdtuxpIDSN.js did for InDesign - not just the config-layer API.
Source:

Methods

(static) applyConfigFromGroup(doc, groupNameMarker, callback)

Find a config layer group, read it as INI data, and hand that data to a caller-supplied callback - the "apply" part is always specific to whatever product built the config layer (which sections/keys it supports, what each one means), so that logic lives in the calling script, not here. This function only owns the generic, reusable part: find the group, extract the data, dispatch it.
Source:
Parameters:
Name Type Description
doc Document a Photoshop UXP `Document`
groupNameMarker string substring identifying the config layer group (e.g. `"[photoshop-config]"`)
callback function `function(configData, group, doc)`, called once the group is found and its content parsed; `configData` is whatever `readINI` returns (may be `undefined` if the group had no readable INI content - callbacks should handle that). May be `async` and/or return a Promise; whatever it returns/resolves to is returned by `applyConfigFromGroup` in turn. Mutating the document from inside the callback still needs `crdtuxpPHXS.runModal`, same as any other document edit.
Returns:
the callback's return value, or `undefined` if no matching group was found (callback is never called in that case) or the arguments are invalid

(static) collectTextLayersSortedByX(group) → {Array.<Layer>}

Collect all text layers directly inside a layer group, sorted left-to-right by the left edge of their bounds.
Source:
Parameters:
Name Type Description
group Layer a Photoshop UXP layer group
Returns:
Type:
Array.<Layer>
text layers, sorted by `bounds.left` ascending (empty array if `group` has no text layers or is falsy)

(static) concatenateTextLayerContents(textLayers) → {string}

Concatenate the text contents of a list of text layers, one per line, in the order given (see `collectTextLayersSortedByX` for left-to-right column ordering).
Source:
Parameters:
Name Type Description
textLayers Array.<Layer> array of Photoshop UXP text layers
Returns:
Type:
string
contents joined with `"\n"` (empty string if `textLayers` is empty or falsy)

(static) createLayerGroup(doc, name) → {Layer}

Create a new, empty top-level layer group. Not independently verified against a real Photoshop install (no local precedent to crib from when this was written), but `Document.createLayerGroup` is Adobe's documented, commonly-shown UXP API for this - higher confidence than `createTextLayer`'s `textItem` styling guesses below. Must be called from inside `crdtuxpPHXS.runModal`.
Source:
Parameters:
Name Type Description
doc Document a Photoshop UXP `Document`
name string group name
Returns:
Type:
Layer
the new layer group, or `undefined` on failure

(static) createTextLayer(doc, parentGroup, name, contents, optionsopt) → {Layer}

Create a text layer inside a group, with position and basic style. UNVERIFIED AGAINST A REAL PHOTOSHOP INSTALL - the overall shape (`document.createLayer` with `LayerKind.TEXT`, then set `.textItem` properties) matches Adobe's documented UXP Photoshop API, but the exact `textItem` property names used for font/size/color have not been confirmed live. Treat this as a first draft to verify/fix in one place (both NewConfig and CleanConfig call this), not a proven implementation. Must be called from inside `crdtuxpPHXS.runModal`.
Source:
Parameters:
Name Type Attributes Description
doc Document a Photoshop UXP `Document`
parentGroup Layer group to create the layer inside
name string layer name
contents string text contents
options object <optional>
`{ left, top, fontName, fontSizePt, color: {r,g,b} }`, all optional
Returns:
Type:
Layer
the new text layer, or `undefined` on failure

(static) deleteTextLayersInGroup(group, preserveNameSubstringopt) → {boolean}

Delete all text layers directly inside a group, optionally preserving one whose name contains `preserveNameSubstring` (e.g. a border/frame layer that isn't part of the text content). Must be called from inside `crdtuxpPHXS.runModal`.
Source:
Parameters:
Name Type Attributes Description
group Layer a Photoshop UXP layer group
preserveNameSubstring string <optional>
substring identifying a layer to leave alone (e.g. `"[config-border]"`)
Returns:
Type:
boolean
true on success

(static) deselectAllLayers(doc) → {boolean}

Deselect all layers in a document. Shorthand for `selectLayers(doc, [])`.
Source:
Parameters:
Name Type Description
doc Document a Photoshop UXP `Document`
Returns:
Type:
boolean
true on success

(static) findLayerGroupByNameSubstring(doc, nameSubstring) → {Layer}

Find the first top-level layer group in a document whose name contains `nameSubstring`.
Source:
Parameters:
Name Type Description
doc Document a Photoshop UXP `Document` (e.g. `app.activeDocument`)
nameSubstring string substring to look for in top-level group names
Returns:
Type:
Layer
the matching layer group, or `undefined` if not found

(static) getBooleanFromINI(value) → {boolean}

Interpret a string as a boolean, the way an INI value would be: `y`, `n`, `yes`, `no`, `true`, `false`, `t`, `f`, `0`, `1` (case-insensitive, only the first character is actually examined, matching CRDT_ES's `crdtes.getBooleanFromINI`).
Source:
Parameters:
Name Type Description
value string ini value
Returns:
Type:
boolean
value

(static) getPhotoshop() → {object}

Get the Photoshop UXP `photoshop` module's `app`/`constants` objects. Centralises the `require("photoshop")` call so every function below goes through the same spot.
Source:
Returns:
Type:
object
`{ app, constants }`, or `undefined` if not running inside Photoshop's UXP host (e.g. required from a non-Photoshop context).

(static) moveLayerToBeginning(layer) → {boolean}

Move a layer to the top of its own stack (within its parent group, or within the document if it's a top-level layer).
Source:
Parameters:
Name Type Description
layer Layer a Photoshop UXP layer (or layer group); must have a `.parent` (a top-level layer's parent is the document itself)
Returns:
Type:
boolean
true on success

(static) moveLayerToEnd(layer) → {boolean}

Move a layer to the bottom of its own stack (within its parent group, or within the document if it's a top-level layer).
Source:
Parameters:
Name Type Description
layer Layer a Photoshop UXP layer (or layer group); must have a `.parent` (a top-level layer's parent is the document itself)
Returns:
Type:
boolean
true on success

(static) parseHexColor(hexString) → {object}

Parse a `#RRGGBB` or `RRGGBB` hex color string (leading `#` optional).
Source:
Parameters:
Name Type Description
hexString string e.g. `"#AACCFF"` or `"AACCFF"`
Returns:
Type:
object
`{ r, g, b }` as integers 0-255, or `undefined` if `hexString` is not a valid 6-digit hex color

(static) readConfigFromGroup(doc, groupNameMarker) → {object}

Find a config layer group in a document, read its text layers and parse them as INI data, in one call. Equivalent to: `readINI(concatenateTextLayerContents(collectTextLayersSortedByX(findLayerGroupByNameSubstring(doc, groupNameMarker))))`
Source:
Parameters:
Name Type Description
doc Document a Photoshop UXP `Document`
groupNameMarker string substring identifying the config layer group (e.g. `"[photoshop-config]"`)
Returns:
Type:
object
the parsed INI object, or `undefined` if no matching group was found or it had no readable INI content

(static) readINI(text) → {object}

Parse INI-formatted text into a nested object, one property per section, one property per key within each section. Direct port of CRDT_ES's `crdtes.readINI` (character-by-character state machine) - keep both in sync if either changes. Behaviour:

- `[section-name]` starts a section; everything until the next `[section]` line belongs to it.
- `key = value` sets an attribute (spaces around `=` optional).
- Section/attribute names are lowercased, de-spaced, and stripped down to `[-a-zA-Z0-9_$]` (section names additionally keep `:`).
- The original, un-normalised section name is kept on the section object as `__rawSectionName`.
- A value wrapped in matching straight or curly quotes (`"..."`, `'...'`) has the quotes stripped; internal spaces are preserved.
- Duplicate section or attribute names are suffixed `_2`, `_3`, etc.
- A line starting with `#` is a comment. Any other line that isn't a `[section]` or `key = value` line (including one starting with `;`) is silently ignored - it never reaches a `=`, so nothing is stored.
Source:
Parameters:
Name Type Description
text string raw text, which might or might not contain some INI-formatted data mixed with normal text
Returns:
Type:
object
the parsed INI data, or `undefined` if `text` is empty/ not a string, or contains no recognisable `[section]`

(static) reformatINILines(text) → {Array.<string>}

Reformat raw text as a tidy, consistent set of INI lines, WITHOUT parsing it into an object - unlike `readINI`, comments and blank lines are preserved so this is safe to use on content a human has annotated. Per line:

- Blank lines are kept as-is.
- Lines starting with `;` or `#` (comments) are kept as-is.
- `[section]` headers are normalised the same way `readINI` normalises section names (lowercased, de-spaced, stripped to `[-a-zA-Z0-9_$:]`).
- `key = value` lines have their spacing normalised to `key = value` (the key itself is trimmed but NOT otherwise normalised/case-folded, unlike `readINI` - this is a display reformat, not a re-parse).
- Any other line is discarded.
Source:
Parameters:
Name Type Description
text string raw text to reformat
Returns:
Type:
Array.<string>
the reformatted lines (empty array if `text` is empty/not a string)

(static) runModal(callback, commandName) → {Promise}

Run a callback inside Photoshop's required `executeAsModal` wrapper - needed for any document-mutating call (layer create/move/visibility/ selection, etc.), which Photoshop's UXP API rejects outside of one. Centralised here so every PXConfig-style script uses the same wrapper instead of each reimplementing it.
Source:
Parameters:
Name Type Description
callback function function to run inside the modal scope; may be `async` and/or return a Promise
commandName string label shown in Photoshop's Undo history
Returns:
Type:
Promise
resolves with `callback`'s return value, rejects if `callback` throws or this isn't running inside Photoshop

(static) selectLayers(doc, layers) → {boolean}

Set the document's selected layers.
Source:
Parameters:
Name Type Description
doc Document a Photoshop UXP `Document`
layers Array.<Layer> layers to select; pass an empty array (or `undefined`) to deselect everything
Returns:
Type:
boolean
true on success

(static) setLayerGroupExpanded(group, expanded) → {boolean}

Expand or collapse a layer group's twirl-down in the Layers panel. NOT YET IMPLEMENTED - the Layers-panel expand/collapse state has no documented direct UXP DOM property (same gap exists in classic ExtendScript), and likely needs `batchPlay` - not yet researched/ verified. Exists as a named stub so callers (ShowConfig/HideConfig) have one place to call and one place to fix later, instead of each reimplementing/skipping this independently. Note: selecting a layer inside a collapsed group auto-expands it in the UI as a side effect, so a caller that only needs the group's content visible/selectable (e.g. ShowConfig selecting its first text layer via `selectLayers`) may not need this at all.
Source:
Parameters:
Name Type Description
group Layer a Photoshop UXP layer group
expanded boolean true to expand, false to collapse
Returns:
Type:
boolean
always `false` currently

(static) setLayerVisible(layer, visible) → {boolean}

Show or hide a layer.
Source:
Parameters:
Name Type Description
layer Layer a Photoshop UXP layer (or layer group)
visible boolean true to show, false to hide
Returns:
Type:
boolean
true on success

(static) splitLinesIntoColumns(lines, linesPerColumn) → {Array.<string>}

Split a list of lines into column-sized chunks, each chunk joined back into a single string with `"\n"` - the inverse of `concatenateTextLayerContents`/`collectTextLayersSortedByX`.
Source:
Parameters:
Name Type Description
lines Array.<string> lines to split
linesPerColumn number max lines per column (must be > 0)
Returns:
Type:
Array.<string>
one string per column, left-to-right order