Embed Photopea in web apps using photopea.js. Covers embedding, file I/O, scripting, exporting, layers, text, filters, and the full Photoshop-compatible API.
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
Embed Photopea in web apps using photopea.js. Covers embedding, file I/O, scripting, exporting, layers, text, filters, and the full Photoshop-compatible API.
// Font
const buf = await (await fetch("https://example.com/MyFont.otf")).arrayBuffer();
await pea.loadAsset(buf);
// Now usable in textItem.font
// Brush
await pea.loadAsset(await (await fetch("Nature.ABR")).arrayBuffer());
// Gradient
await pea.loadAsset(await (await fetch("Gradients.GRD")).arrayBuffer());
Step 6 — Plugin Mode
// Your page is inside Photopea's sidebar iframe
const pea = new Photopea(window.parent);
const out = await pea.runScript(`app.echoToOE(app.activeDocument.width);`);
console.log("Width:", out[0]);
// Load an asset from your plugin
const buf = await (await fetch("https://my-assets.com/sticker.png")).arrayBuffer();
await pea.loadAsset(buf);
All code in this section runs inside pea.runScript("...") strings.
Photopea implements the Adobe Photoshop CC 2015 JavaScript scripting interface.
Any Photoshop script targeting that version should work in Photopea.
Photopea extension — clear undo history to free RAM
exportDocument
(file, exportType, options)
Export to filesystem (triggers ZIP). ExportType: SAVEFORWEB
paste
(intoSelection)
Paste clipboard into document
suspendHistory
(historyName, callback)
Wrap multiple ops in one history state
Practical examples:
var doc = app.activeDocument;
// Resize image to 1920×1080 at 72dpi bicubic
doc.resizeImage(1920, 1080, 72, ResampleMethod.BICUBIC);
// Expand canvas to 2000px wide, keeping content centered
doc.resizeCanvas(2000, doc.height, AnchorPosition.MIDDLECENTER);
// Crop to a region
doc.crop([100, 100, 900, 600]);
// Trim transparent edges
doc.trim(TrimType.TRANSPARENT, true, true, true, true);
// Flip horizontal
doc.flipCanvas(Direction.HORIZONTAL);
// Change to grayscale
doc.changeMode(ChangeMode.GRAYSCALE);
// One undo step for many operations
doc.suspendHistory("Batch Edit", "action");
// (Inside Photopea, all ops become one history state)
// Export PNG to filesystem (triggers ZIP download)
var opts = new ExportOptionsSaveForWeb();
opts.format = SaveDocumentType.PNG;
opts.PNG8 = false;
opts.quality = 100;
doc.exportDocument(new File("/output.png"), ExportType.SAVEFORWEB, opts);
// Close without saving
doc.close(SaveOptions.DONOTSAVECHANGES);
Layers / ArtLayers / LayerSets Collections
These collections exist on Document, LayerSet (groups within groups), and can be iterated.
var doc = app.activeDocument;
// Access
doc.layers // all top-level (art + groups)
doc.artLayers // top-level art layers only
doc.layerSets // top-level group layers only
// By index (0 = topmost)
doc.layers[0]
doc.layers[doc.layers.length - 1] // bottommost
// By name (throws if not found)
doc.layers.getByName("Background")
doc.artLayers.getByName("Logo")
doc.layerSets.getByName("Header Group")
// Add
var newLayer = doc.artLayers.add(); // new blank art layer
var newGroup = doc.layerSets.add(); // new group
var innerLayer = newGroup.artLayers.add(); // layer inside a group
// Remove
doc.artLayers.getByName("Temp").remove();
// Iterate all layers recursively
function walkLayers(parent) {
for (var i = 0; i < parent.layers.length; i++) {
var l = parent.layers[i];
if (l.typename === "LayerSet") walkLayers(l);
else /* ArtLayer */ processLayer(l);
}
}
walkLayers(doc);
ArtLayer — Individual Layer
Properties
Property
Type
R/W
Description
name
string
R/W
Layer name
visible
boolean
R/W
Layer visibility
opacity
number
R/W
Layer opacity 0–100
fillOpacity
number
R
Fill opacity 0–100
blendMode
BlendMode
R/W
Blend mode (see enum below)
kind
LayerKind
R/W
Layer type (can set to LayerKind.TEXT on empty layer)
textItem
TextItem
R
Text object (only when kind === LayerKind.TEXT)
bounds
array
R
[left, top, right, bottom] in current ruler units
parent
Document/LayerSet
R
Containing object
typename
string
R
Always "ArtLayer"
selected
boolean
R
Photopea extension — is layer highlighted in panel
isBackgroundLayer
boolean
R
Is this the locked background layer
grouped
boolean
R
Is clipping mask applied
pixelsLocked
boolean
R
Pixels locked
positionLocked
boolean
R
Position locked
transparentPixelsLocked
boolean
R
Transparent pixels locked
layerMaskDensity
number
R
Layer mask density 0–100
layerMaskFeather
number
R
Layer mask feather 0–250
vectorMaskDensity
number
R
Vector mask density 0–100
vectorMaskFeather
number
R
Vector mask feather 0–250
Transform Methods
Method
Signature
Description
translate
(deltaX, deltaY)
Move layer by offset
rotate
(angle, anchor)
Rotate by degrees. AnchorPosition optional (default center)
var layer = app.activeDocument.activeLayer;
// Move to absolute position (layer.bounds[0] = current left edge)
layer.translate(200 - layer.bounds[0], 100 - layer.bounds[1]);
// Rotate 45° around center
layer.rotate(45);
// Scale to 50% keeping center
layer.resize(50, 50, AnchorPosition.MIDDLECENTER);
// Gaussian blur radius 10
layer.applyGaussianBlur(10);
// Unsharp mask
layer.applyUnSharpMask(50, 2, 0);
// Levels: input 0–200, gamma 1.2, output 0–255
layer.adjustLevels(0, 200, 1.2, 0, 255);
// Brightness +20, Contrast +10
layer.adjustBrightnessContrast(20, 10);
// Invert
layer.invert();
// Rasterize text
layer.rasterize(RasterizeType.TEXTCONTENTS);
// Duplicate layer
var copy = layer.duplicate();
copy.name = "Layer Copy";
// Move layer below another
var target = doc.layers.getByName("Background");
layer.move(target, ElementPlacement.PLACEAFTER);
LayerSet — Group Layer
A LayerSet is a folder/group in the Layers panel. It has the same layer management methods as Document.
Properties
Property
Type
R/W
Description
name
string
R/W
Group name
visible
boolean
R/W
Group visibility
opacity
number
R/W
Group opacity 0–100
blendMode
BlendMode
R/W
Group blend mode
bounds
array
R
Bounding box [left,top,right,bottom]
layers
Layers
R
All layers inside this group
artLayers
ArtLayers
R
Art layers inside this group
layerSets
LayerSets
R
Sub-groups inside this group
parent
Document/LayerSet
R
Parent container
typename
string
R
Always "LayerSet"
Methods
Same as Document for layer management: layers.add(), artLayers.add(), layerSets.add(), .getByName(), plus duplicate(), remove(), move().
// Create group with layers inside
var group = doc.layerSets.add();
group.name = "Product Card";
var bgLayer = group.artLayers.add(); bgLayer.name = "Background";
var textLayer = group.artLayers.add(); textLayer.kind = LayerKind.TEXT;
textLayer.textItem.contents = "Buy Now";
textLayer.textItem.size = 36;
// Collapse/expand group (Photopea specific, not in standard DOM)
// Use visibility as workaround
// Get specific layer inside a group
var innerLayer = doc.layerSets.getByName("Header Group").artLayers.getByName("Title");
TextItem — Text Layer Content
Access via layer.textItem on any layer with layer.kind === LayerKind.TEXT.
Core Properties (most commonly used)
Property
Type
R/W
Description
contents
string
R/W
The actual text content
font
string
R/W
Font PostScript name (e.g. "ArialMT", "Verdana-Bold")
size
number
R/W
Font size in points
color
SolidColor
R/W
Text color
position
array
R/W
[x, y] origin of text (point text) or bounding box top-left
JSON array — the affine transform matrix of the text
Methods
Method
Description
convertToShape()
Convert text to a filled shape layer with text as clipping path
createPath()
Create work path from text outlines
Practical examples:
var layer = doc.layers.getByName("Headline");
var text = layer.textItem;
// Set content
text.contents = "Hello World";
// Style
text.font = "Verdana-Bold";
text.size = 72;
text.color.rgb.hexValue = "FF0000"; // red
// Position point text at (50, 100)
text.position = [50, 100];
// Center align
text.justification = Justification.CENTER;
// Paragraph text with bounding box
text.kind = TextType.PARAGRAPHTEXT;
text.width = new UnitValue("400 pixels");
text.height = new UnitValue("200 pixels");
// Letter spacing
text.tracking = 100; // 10% spacing
// Scale text horizontally to 80%
text.horizontalScale = 80;
// Warp arc
text.warpStyle = WarpStyle.ARC;
text.warpBend = 30;
// Read all text styles as JSON (Photopea extension)
var styles = JSON.parse(text.totalTextStyle);
app.echoToOE(JSON.stringify(styles));
Creating a Text Layer from Scratch
app.preferences.rulerUnits = Units.PIXELS;
var layer = doc.artLayers.add();
layer.kind = LayerKind.TEXT; // Convert blank layer to text
layer.name = "My Title";
var text = layer.textItem;
text.contents = "Welcome";
text.font = "ArialMT";
text.size = 48;
text.justification = Justification.CENTER;
text.position = [doc.width / 2, 100];
var color = new SolidColor();
color.rgb.red = 255;
color.rgb.green = 255;
color.rgb.blue = 255;
text.color = color;
SolidColor — Color Object
// RGB (most common in Photopea)
var c = new SolidColor();
c.rgb.red = 255; // 0–255
c.rgb.green = 128;
c.rgb.blue = 0;
c.rgb.hexValue = "FF8000"; // Set via hex string (no #)
// CMYK
var c2 = new SolidColor();
c2.cmyk.cyan = 0; // 0–100
c2.cmyk.magenta = 50;
c2.cmyk.yellow = 100;
c2.cmyk.black = 0;
// Grayscale
var c3 = new SolidColor();
c3.gray.gray = 50; // 0–100
// HSB
var c4 = new SolidColor();
c4.hsb.hue = 30; // 0–360
c4.hsb.saturation = 100; // 0–100
c4.hsb.brightness = 100; // 0–100
// Lab
var c5 = new SolidColor();
c5.lab.l = 50; // 0–100
c5.lab.a = 20; // -128–127
c5.lab.b = 40; // -128–127
// Set as foreground color
app.foregroundColor = c;
// Use with selection fill
doc.selection.selectAll();
doc.selection.fill(c);
doc.selection.deselect();
Selection — Selection Object
Access via doc.selection.
Properties
Property
Type
Description
bounds
array
[left, top, right, bottom] bounding rectangle
solid
boolean
Whether selection is a solid rectangle
Methods
Method
Signature
Description
selectAll
()
Select entire document
deselect
()
Remove selection
invert
()
Invert the selection
select
(region, type, feather, antiAlias)
Select polygon region. Region is array of [x,y] points. SelectionType: REPLACE, ADD, SUBTRACT, INTERSECT
feather
(radius)
Feather the selection edges
contract
(radius)
Contract (shrink) selection
expand
(radius)
Expand selection
grow
(tolerance, antiAlias)
Grow selection to similar adjacent pixels
similar
(tolerance, antiAlias)
Select similar pixels throughout document
smooth
(radius)
Smooth selection edges
selectBorder
(width)
Select only the border of the current selection
resize
(widthPct, heightPct, anchor)
Resize selection boundary
rotate
(angle, anchor)
Rotate selection boundary
translate
(deltaX, deltaY)
Move selection boundary
fill
(fillWith, mode, opacity, preserveTransparency)
Fill selection with color or content. fillWith is SolidColor or string
Stroke selection border. StrokeLocation: INSIDE, OUTSIDE, CENTER
copy
(merged)
Copy selection to clipboard
cut
()
Cut selection to clipboard
clear
()
Delete selection content
load
(from, type, invert)
Load selection from channel
store
(into, type)
Save selection as channel
makeWorkPath
(tolerance)
Convert to work path
Practical examples:
var sel = doc.selection;
// Rectangle select (top-left to bottom-right)
sel.select([[0,0],[500,0],[500,300],[0,300]]);
// Select all
sel.selectAll();
// Add to existing selection
sel.select([[600,0],[900,0],[900,300],[600,300]], SelectionType.ADD);
// Feather 10px
sel.feather(10);
// Contract by 5px
sel.contract(5);
// Fill with red
var red = new SolidColor();
red.rgb.red = 255; red.rgb.green = 0; red.rgb.blue = 0;
sel.fill(red);
// Stroke selection with black, 3px, inside
var black = new SolidColor();
black.rgb.hexValue = "000000";
sel.stroke(black, 3, StrokeLocation.INSIDE);
// Copy, paste as new layer
sel.copy();
doc.paste();
// Invert and delete (remove background)
sel.invert();
sel.clear();
sel.deselect();
BlendMode Enum — All Values
Used in layer.blendMode (string form in Photopea) and BlendMode constant (standard):
BlendMode Constant
Photopea String
Name
BlendMode.NORMAL
"norm"
Normal
BlendMode.DISSOLVE
"diss"
Dissolve
BlendMode.DARKEN
"dark"
Darken
BlendMode.MULTIPLY
"mul "
Multiply
BlendMode.COLORBURN
"idiv"
Color Burn
BlendMode.LINEARBURN
"lbrn"
Linear Burn
BlendMode.DARKERCOLOR
"dkCl"
Darker Color
BlendMode.LIGHTEN
"lite"
Lighten
BlendMode.SCREEN
"scrn"
Screen
BlendMode.COLORDODGE
"div "
Color Dodge
BlendMode.LINEARDODGE
"lddg"
Linear Dodge (Add)
BlendMode.LIGHTERCOLOR
"lgCl"
Lighter Color
BlendMode.OVERLAY
"over"
Overlay
BlendMode.SOFTLIGHT
"sLit"
Soft Light
BlendMode.HARDLIGHT
"hLit"
Hard Light
BlendMode.VIVIDLIGHT
"vLit"
Vivid Light
BlendMode.LINEARLIGHT
"lLit"
Linear Light
BlendMode.PINLIGHT
"pLit"
Pin Light
BlendMode.HARDMIX
"hMix"
Hard Mix
BlendMode.DIFFERENCE
"diff"
Difference
BlendMode.EXCLUSION
"smud"
Exclusion
BlendMode.SUBTRACT
"fsub"
Subtract
BlendMode.DIVIDE
"fdiv"
Divide
BlendMode.HUE
"hue "
Hue
BlendMode.SATURATION
"sat "
Saturation
BlendMode.COLOR
"colr"
Color
BlendMode.LUMINOSITY
"lum "
Luminosity
BlendMode.PASSTHROUGH
"pass"
Pass Through (groups only)
// Use either form:
layer.blendMode = BlendMode.SCREEN; // constant
layer.blendMode = "scrn"; // string (Photopea internal form)
LayerKind Enum
Constant
Description
LayerKind.NORMAL
Regular pixel layer
LayerKind.TEXT
Text layer
LayerKind.SMARTOBJECT
Smart Object / linked layer
LayerKind.SOLIDFILL
Solid color fill layer
LayerKind.GRADIENTFILL
Gradient fill layer
LayerKind.PATTERNFILL
Pattern fill layer
LayerKind.BRIGHTNESSCONTRAST
Brightness/Contrast adjustment layer
LayerKind.CURVES
Curves adjustment layer
LayerKind.LEVELS
Levels adjustment layer
LayerKind.HUESATURATION
Hue/Saturation adjustment layer
LayerKind.COLORBALANCE
Color Balance adjustment layer
LayerKind.CHANNELMIXER
Channel Mixer adjustment layer
LayerKind.GRADIENTMAP
Gradient Map adjustment layer
LayerKind.INVERSION
Invert adjustment layer
LayerKind.POSTERIZE
Posterize adjustment layer
LayerKind.THRESHOLD
Threshold adjustment layer
LayerKind.SELECTIVECOLOR
Selective Color adjustment layer
LayerKind.PHOTOFILTER
Photo Filter adjustment layer
LayerKind.EXPOSURE
Exposure adjustment layer
LayerKind.VIBRANCE
Vibrance adjustment layer
LayerKind.COLORLOOKUP
Color Lookup adjustment layer
LayerKind.LAYER3D
3D layer (not generally useful in Photopea)
LayerKind.VIDEO
Video layer
// Identify layer type
var layer = doc.activeLayer;
if (layer.kind === LayerKind.TEXT) /* text layer */;
if (layer.kind === LayerKind.SMARTOBJECT) /* smart object */;
if (layer.typename === "LayerSet") /* group */;
// Filter: collect all text layers recursively
var textLayers = [];
function collectText(parent) {
for (var i = 0; i < parent.layers.length; i++) {
var l = parent.layers[i];
if (l.typename === "LayerSet") collectText(l);
else if (l.kind === LayerKind.TEXT) textLayers.push(l);
}
}
collectText(doc);
AnchorPosition Enum
Constant
Position
AnchorPosition.TOPLEFT
Top left
AnchorPosition.TOPCENTER
Top center
AnchorPosition.TOPRIGHT
Top right
AnchorPosition.MIDDLELEFT
Middle left
AnchorPosition.MIDDLECENTER
Center
AnchorPosition.MIDDLERIGHT
Middle right
AnchorPosition.BOTTOMLEFT
Bottom left
AnchorPosition.BOTTOMCENTER
Bottom center
AnchorPosition.BOTTOMRIGHT
Bottom right
ElementPlacement Enum
Used with layer.move(relativeObject, placement):
Constant
Effect
ElementPlacement.PLACEBEFORE
Above the target layer in the panel
ElementPlacement.PLACEAFTER
Below the target layer in the panel
ElementPlacement.PLACEATBEGINNING
Top of the layer stack
ElementPlacement.PLACEATEND
Bottom of the layer stack
ElementPlacement.INSIDE
Into a LayerSet (makes layer a child)
ResampleMethod Enum
Used with doc.resizeImage():
Constant
Description
ResampleMethod.BICUBIC
High quality, good for smooth gradients
ResampleMethod.BICUBICSHARPER
Best for reduction
ResampleMethod.BICUBICSMOOTHER
Best for enlargement
ResampleMethod.BILINEAR
Medium quality
ResampleMethod.NEARESTNEIGHBOR
No anti-aliasing, fastest
ResampleMethod.NONE
No resampling (change resolution only)
SaveOptions Enum
Used with doc.close(saveOption):
Constant
Meaning
SaveOptions.DONOTSAVECHANGES
Discard all changes and close
SaveOptions.SAVECHANGES
Save then close
SaveOptions.PROMPTTOSAVECHANGES
Show dialog (may block in headless)
ExportOptionsSaveForWeb — Export to Filesystem
Used with doc.exportDocument() to write files that Photopea packages into a ZIP.
Used for operations not exposed in the standard DOM (adjustments applied as adjustment layers,
Smart Object editing, etc.). Takes the Photoshop Action Manager approach.
// Open Smart Object for editing
var l = doc.layers.getByName("SmartObj");
doc.activeLayer = l;
executeAction(stringIDToTypeID("placedLayerEditContents"));
// Smart Object is now the active document
doc.activeLayer.rotate(90);
doc.save();
doc.close();
// Apply Hue/Saturation as destructive adjustment
var desc = new ActionDescriptor();
var list = new ActionList();
var channel = new ActionDescriptor();
channel.putEnumerated(stringIDToTypeID("presetKind"), stringIDToTypeID("presetKindType"), stringIDToTypeID("presetKindDefault"));
channel.putInteger(stringIDToTypeID("hue"), 20); // hue shift
channel.putInteger(stringIDToTypeID("saturation"), 30); // saturation
channel.putInteger(stringIDToTypeID("lightness"), 0);
list.putObject(stringIDToTypeID("hueSaturationAdjustmentV2Layer"), channel);
desc.putList(stringIDToTypeID("adjustment"), list);
executeAction(stringIDToTypeID("hueSaturation"), desc, DialogModes.NO);
// Select a layer by name using AM
function selectLayerByName(name) {
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putName(charIDToTypeID("Lyr "), name);
desc.putReference(charIDToTypeID("null"), ref);
desc.putBoolean(charIDToTypeID("MkVs"), false);
executeAction(charIDToTypeID("slct"), desc, DialogModes.NO);
}
Complete Practical Script Examples
1. Rename all text layers based on their contents
app.preferences.rulerUnits = Units.PIXELS;
var doc = app.activeDocument;
function processLayers(parent) {
for (var i = 0; i < parent.layers.length; i++) {
var l = parent.layers[i];
if (l.typename === "LayerSet") processLayers(l);
else if (l.kind === LayerKind.TEXT) {
l.name = l.textItem.contents.substring(0, 30);
}
}
}
processLayers(doc);
app.echoToOE("done");
2. Export each layer as a separate PNG
app.preferences.rulerUnits = Units.PIXELS;
var doc = app.activeDocument;
for (var i = 0; i < doc.layers.length; i++) {
// Hide all layers
for (var j = 0; j < doc.layers.length; j++) doc.layers[j].visible = false;
// Show only this layer
doc.layers[i].visible = true;
// Export
var opts = new ExportOptionsSaveForWeb();
opts.format = SaveDocumentType.PNG;
opts.PNG8 = false;
opts.quality = 100;
doc.exportDocument(
new File("/" + doc.layers[i].name + ".png"),
ExportType.SAVEFORWEB, opts
);
}
// Restore visibility
for (var i = 0; i < doc.layers.length; i++) doc.layers[i].visible = true;
3. Find and replace text across all text layers
var searchText = "2024";
var replaceText = "2025";
function findReplaceText(parent) {
for (var i = 0; i < parent.layers.length; i++) {
var l = parent.layers[i];
if (l.typename === "LayerSet") findReplaceText(l);
else if (l.kind === LayerKind.TEXT) {
var t = l.textItem;
if (t.contents.indexOf(searchText) !== -1) {
t.contents = t.contents.split(searchText).join(replaceText);
}
}
}
}
findReplaceText(app.activeDocument);
app.echoToOE("Find & Replace complete");
4. Grid of duplicate layers
app.preferences.rulerUnits = Units.PIXELS;
var doc = app.activeDocument;
var layer = doc.activeLayer;
var cols = 4, rows = 3;
var padX = 20, padY = 20;
var w = layer.bounds[2] - layer.bounds[0];
var h = layer.bounds[3] - layer.bounds[1];
for (var r = 0; r < rows; r++) {
for (var c = 0; c < cols; c++) {
if (r === 0 && c === 0) continue; // skip original
var copy = layer.duplicate();
var targetX = layer.bounds[0] + c * (w + padX);
var targetY = layer.bounds[1] + r * (h + padY);
copy.translate(targetX - copy.bounds[0], targetY - copy.bounds[1]);
copy.opacity = 100 - (r * cols + c) * 5;
}
}
5. Apply watermark from URL
app.preferences.rulerUnits = Units.PIXELS;
var doc = app.activeDocument;
// Open watermark as smart object layer
app.open("https://example.com/watermark.png", null, true);
var wm = doc.activeLayer;
// Resize to 20% of document width
var wmW = wm.bounds[2] - wm.bounds[0];
var targetW = doc.width * 0.2;
var scalePct = (targetW / wmW) * 100;
wm.resize(scalePct, scalePct, AnchorPosition.TOPLEFT);
// Move to bottom-right with 20px margin
var wmNewW = wm.bounds[2] - wm.bounds[0];
var wmNewH = wm.bounds[3] - wm.bounds[1];
wm.translate(
doc.width - wmNewW - 20 - wm.bounds[0],
doc.height - wmNewH - 20 - wm.bounds[1]
);
wm.opacity = 60;
app.echoToOE("watermark applied");
6. Get all layer info as JSON
function getLayerInfo(parent, depth) {
depth = depth || 0;
var result = [];
for (var i = 0; i < parent.layers.length; i++) {
var l = parent.layers[i];
var info = {
name: l.name,
type: l.typename,
visible: l.visible,
opacity: l.opacity,
depth: depth
};
if (l.typename === "ArtLayer") {
info.kind = l.kind.toString();
info.bounds = [l.bounds[0], l.bounds[1], l.bounds[2], l.bounds[3]];
if (l.kind === LayerKind.TEXT) {
info.text = l.textItem.contents;
info.font = l.textItem.font;
info.size = l.textItem.size;
}
} else if (l.typename === "LayerSet") {
info.children = getLayerInfo(l, depth + 1);
}
result.push(info);
}
return result;
}
app.echoToOE(JSON.stringify(getLayerInfo(app.activeDocument)));
Common Mistakes & Gotchas
Problem
Cause
Fix
createEmbed never resolves
Container has no size
Add width + height CSS to the container <div>
runScript returns ["done"] with no data
No echoToOE in script
Add app.echoToOE(value) for anything you want back
result[0] is "done", not the expected value
echoToOE not reached
Check script logic for early exit or errors
Images won't load (network error)
CORS
Server must respond with Access-Control-Allow-Origin: *
openFromURL(url, true) layer not ready
Async loading lag
Use addImageAndWait utility
exportImage only PNG/JPG
exportImage limitation
Use runScript("saveToOE('webp:0.85')") for other formats
Pixel coordinates behave unexpectedly
Wrong ruler units
Always set app.preferences.rulerUnits = Units.PIXELS first
Text size set but looks different
Wrong type units
Set app.preferences.typeUnits = TypeUnits.PIXELS
Layer not found by name
Wrong layer level
Layers are scoped; use recursive search for nested layers
layer.bounds[0] returns a UnitValue, not number
Ruler units issue
Force Units.PIXELS before reading bounds
Smart Object edit hangs
Missing doc.save(); doc.close()
Always save + close when done editing SO
React double-mount in dev
Strict Mode
Use if (peaRef.current) return guard in useEffect
Limitations
This skill covers host-page integration patterns; it does not replace Photopea's own terms, API documentation, or licensing guidance.
Remote URL loading depends on browser CORS behavior, network availability, and the user's Photopea account/session state.
runScript executes scripts inside the embedded Photopea document context. Only run scripts you understand and only with user-approved files.
Serialize dynamic values with JSON.stringify before embedding them in a runScript string. Never concatenate user-provided URLs, layer names, or text directly into Photopea script source.
Export behavior can vary by document size, browser memory limits, and the formats supported by the active Photopea runtime.