← LeaderboardThe Skill Leaderboard

photopea-embedded-editor

Skill file plugins/agentic-awesome-skills/skills/photopea-embedded-editor/SKILL.md in sickn33/agentic-awesome-skills. Read the publisher’s upstream source.

Description could not be read from the source metadata.

Published by sickn33 ↗sickn33/agentic-awesome-skills

Try your first task

A source-grounded starter task has not been verified for this skill yet.

SKILL.md

name: photopea-embedded-editordescription: Embed Photopea in web apps using photopea.js. Covers embedding, file I/O, scripting, exporting, layers, text, filters, and selected Photoshop-like scripting patterns.risk: criticalsource: communitysource_repo: yikuansun/PhotopeaAPIsource_type: communitylicense: MITlicense_source: "https://github.com/yikuansun/PhotopeaAPI/blob/master/LICENSE"date_added: 2026-05-20

Photopea Embedded Editor Skill

Detailed Guide

Read the detailed guide before executing this skill. It retains the complete procedure and reference material. Treat its safety, prerequisites, and validation requirements as mandatory. For focused work, load the relevant sections; for end-to-end work, read the guide completely.

When to Use This Skill

Use this skill for every task that involves:

  • Embedding Photopea as an image editor inside a webpage or web app
  • Controlling an embedded Photopea instance from your JavaScript code
  • Automating image editing workflows from a host page (open files, run scripts, export results)
  • Building an image editing feature into your product using Photopea as the engine
  • Writing scripts to manipulate documents, layers, text, selections, filters, colors, and paths

Use a reviewed messaging wrapper or carefully scoped Live Messaging implementation. A wrapper alone does not establish message authenticity: verify received origin and source against the exact iframe/parent, use an exact target origin, serialize operations and bound timeouts. Do not accept arbitrary page messages as export data.


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;

var originalVisibility = [];
for (var i = 0; i < doc.layers.length; i++) originalVisibility.push(doc.layers[i].visible);
try {
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("/layer-" + i + ".png"),
    ExportType.SAVEFORWEB, opts
  );
}

} finally {
  for (var i = 0; i < doc.layers.length; i++) doc.layers[i].visible = originalVisibility[i];
}

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;
if (wmW <= 0) throw new Error("Watermark has no measurable width");
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)));

Integration boundary

Embedding loads a third-party application. Review its terms and privacy/network behavior before loading confidential documents, fonts or assets. Work on an approved duplicate for destructive scripts and preserve a recoverable original. The third-party wrapper source is not bundled; pin and inspect its actual version before enabling document control. Treat URLs and returned strings as data; HTML script embedding also requires safe handling of </script> beyond JavaScript string serialization. Do not expose credentials in remote asset URLs.

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.

photopea-embedded-editor by sickn33 · The Skill Leaderboard