Embed Photopea in web apps using photopea.js. Covers embedding, file I/O, scripting, exporting, layers, text, filters, and the full Photoshop-compatible API.
Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Embed Photopea in web apps using photopea.js. Covers embedding, file I/O, scripting, exporting, layers, text, filters, and the full Photoshop-compatible API.
// Fontconst buf = await (awaitfetch("https://example.com/MyFont.otf")).arrayBuffer();
await pea.loadAsset(buf);
// Now usable in textItem.font// Brushawait pea.loadAsset(await (awaitfetch("Nature.ABR")).arrayBuffer());
// Gradientawait pea.loadAsset(await (awaitfetch("Gradients.GRD")).arrayBuffer());
Step 6 — Plugin Mode
// Your page is inside Photopea's sidebar iframeconst pea = newPhotopea(window.parent);
const out = await pea.runScript(`app.echoToOE(app.activeDocument.width);`);
console.log("Width:", out[0]);
// Load an asset from your pluginconst buf = await (awaitfetch("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 = newExportOptionsSaveForWeb();
opts.format = SaveDocumentType.PNG;
opts.PNG8 = false;
opts.quality = 100;
doc.exportDocument(newFile("/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")
// Addvar newLayer = doc.artLayers.add(); // new blank art layervar newGroup = doc.layerSets.add(); // new groupvar innerLayer = newGroup.artLayers.add(); // layer inside a group// Remove
doc.artLayers.getByName("Temp").remove();
// Iterate all layers recursivelyfunctionwalkLayers(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 layervar copy = layer.duplicate();
copy.name = "Layer Copy";
// Move layer below anothervar 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 insidevar 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 groupvar 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
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 editingvar 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 adjustmentvar desc = newActionDescriptor();
var list = newActionList();
var channel = newActionDescriptor();
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 AMfunctionselectLayerByName(name) {
var desc = newActionDescriptor();
var ref = newActionReference();
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;
functionprocessLayers(parent) {
for (var i = 0; i < parent.layers.length; i++) {
var l = parent.layers[i];
if (l.typename === "LayerSet") processLayers(l);
elseif (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 layersfor (var j = 0; j < doc.layers.length; j++) doc.layers[j].visible = false;
// Show only this layer
doc.layers[i].visible = true;
// Exportvar opts = newExportOptionsSaveForWeb();
opts.format = SaveDocumentType.PNG;
opts.PNG8 = false;
opts.quality = 100;
doc.exportDocument(
newFile("/" + doc.layers[i].name + ".png"),
ExportType.SAVEFORWEB, opts
);
}
// Restore visibilityfor (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";
functionfindReplaceText(parent) {
for (var i = 0; i < parent.layers.length; i++) {
var l = parent.layers[i];
if (l.typename === "LayerSet") findReplaceText(l);
elseif (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 originalvar 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 widthvar 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 marginvar 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
functiongetLayerInfo(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;
}
} elseif (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.