Actions Migration

This guide helps you migrate from v1's Photoshop action endpoints to the unified /v2/execute-actions endpoint.

Overview

In v1, Photoshop actions were spread across multiple endpoints. In the v2 API, all action-based operations are consolidated into a single /v2/execute-actions endpoint, including:

The v1 endpoints being consolidated are:

v1 Endpoint
v2 Equivalent
/pie/psdService/photoshopActions
/v2/execute-actions with .atn files
/pie/psdService/actionJSON
/v2/execute-actions with inline actionJSON
/pie/psdService/productCrop
/v2/execute-actions with published action
/pie/psdService/depthBlur
⚠️ Not yet supported (Neural Filters unavailable)
/pie/psdService/splitView
/v2/execute-actions with published action
/pie/psdService/sideBySide
/v2/execute-actions with published action

Benefits of the unified endpoint

Traditional Photoshop actions

Using .atn files

Key Differences:

V1 Approach:

curl -X POST \
  https://image.adobe.io/pie/psdService/photoshopActions \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -H "Content-Type: application/json" \
  -d '{
  "inputs": [
    {
      "href": "<SIGNED_GET_URL>",
      "storage": "external"
    }
  ],
  "options": {
    "actions": [
      {
        "href": "<ACTION_FILE_URL>",
        "storage": "external"
      }
    ]
  },
  "outputs": [
    {
      "href": "<SIGNED_POST_URL>",
      "storage": "external",
      "type": "image/vnd.adobe.photoshop"
    }
  ]
}'

V2 Approach:

curl -X POST \
  https://photoshop-api.adobe.io/v2/execute-actions \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -H "Content-Type: application/json" \
  -d '{
  "image": {
    "source": {
      "url": "<SIGNED_GET_URL>"
    }
  },
  "options": {
    "actions": [
      {
        "source": {
          "url": "<ACTION_FILE_URL>"
        }
      }
    ]
  },
  "outputs": [
    {
      "destination": {
        "url": "<SIGNED_POST_URL>"
      },
      "mediaType": "image/vnd.adobe.photoshop"
    }
  ]
}'

Multiple actions

In the v2 API, you can execute multiple actions in sequence. A maximum of 10 actions allowed per request. Actions execute in the order specified.

curl -X POST \
  https://photoshop-api.adobe.io/v2/execute-actions \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -H "Content-Type: application/json" \
  -d '{
  "image": {
    "source": {
      "url": "<SIGNED_GET_URL>"
    }
  },
  "options": {
    "actions": [
      {
        "source": {
          "url": "<ACTION_1_URL>"
        }
      },
      {
        "source": {
          "url": "<ACTION_2_URL>"
        }
      },
      {
        "source": {
          "url": "<ACTION_3_URL>"
        }
      }
    ]
  },
  "outputs": [
    {
      "destination": {
        "url": "<SIGNED_POST_URL>"
      },
      "mediaType": "image/vnd.adobe.photoshop"
    }
  ]
}'

Using inline ActionJSON

Key Differences:

V1 Approach:

curl -X POST \
  https://image.adobe.io/pie/psdService/actionJSON \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -H "Content-Type: application/json" \
  -d '{
  "inputs": [
    {
      "href": "<SIGNED_GET_URL>",
      "storage": "external"
    }
  ],
  "options": {
    "actionJSON": [
      {
        "_obj": "convertMode",
        "to": {
          "_enum": "colorSpaceMode",
          "_value": "grayscale"
        }
      }
    ]
  },
  "outputs": [
    {
      "href": "<SIGNED_POST_URL>",
      "storage": "external",
      "type": "image/jpeg"
    }
  ]
}'

V2 Approach:

curl -X POST \
  https://photoshop-api.adobe.io/v2/execute-actions \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -H "Content-Type: application/json" \
  -d '{
  "image": {
    "source": {
      "url": "<SIGNED_GET_URL>"
    }
  },
  "options": {
    "actions": [
      {
        "source": {
          "content": "[{\"_obj\":\"convertMode\",\"to\":{\"_enum\":\"colorSpaceMode\",\"_value\":\"grayscale\"}}]",
          "contentType": "application/json"
        }
      }
    ]
  },
  "outputs": [
    {
      "destination": {
        "url": "<SIGNED_POST_URL>"
      },
      "mediaType": "image/jpeg"
    }
  ]
}'

Additional contents in actions

ActionJSON can reference additional contents using placeholders. This is useful when your actions need to composite or reference multiple contents.

How It Works:

  1. Provide additional contents in the options.additionalContents array. A maximum of 25 additional contents are allowed per request.
  2. Reference them in ActionJSON using __ADDITIONAL_CONTENTS_PATH_0__, __ADDITIONAL_CONTENTS_PATH_1__, etc.
  3. The placeholder index corresponds to the array index (0-based).

Example:

curl -X POST \
  https://photoshop-api.adobe.io/v2/execute-actions \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -H "Content-Type: application/json" \
  -d '{
  "image": {
    "source": {
      "url": "<BASE_IMAGE_URL>"
    }
  },
  "options": {
    "additionalContents": [
      {
        "source": {
          "url": "<OVERLAY_IMAGE_URL>"
        }
      },
      {
        "source": {
          "url": "<BACKGROUND_IMAGE_URL>"
        }
      }
    ],
    "actions": [
      {
        "source": {
          "content": "[{\"_obj\":\"placeEvent\",\"null\":{\"_path\":\"__ADDITIONAL_CONTENTS_PATH_0__\",\"_kind\":\"local\"}}]",
          "contentType": "application/json"
        }
      }
    ]
  },
  "outputs": [
    {
      "destination": {
        "url": "<SIGNED_POST_URL>"
      },
      "mediaType": "image/jpeg"
    }
  ]
}'

Additional resources

Actions can reference additional resources like brushes, patterns, and fonts. A maximum of 10 items are allowed for each resource type.

{
  "options": {
    "actions": [...],
    "brushes": [
      {
        "source": {
          "url": "<BRUSH_FILE_URL>"
        }
      }
    ],
    "patterns": [
      {
        "source": {
          "url": "<PATTERN_FILE_URL>"
        }
      }
    ],
    "fontOptions": {
      "additionalFonts": [
        {
          "source": {
            "url": "<FONT_FILE_URL>"
          }
        }
      ],
      "missingFontStrategy": "use_default"
    }
  }
}

Inline content usage

The /v2/execute-actions endpoint supports inline content (embedded content within the API request) for text-based resources only. Understanding which resources support inline content is important for proper API usage.

data-variant=warning
data-slots=header, text
Payload Size Limits:
Keep inline content limited to 50 KB per resource and overall request payload size below 1 MB. For larger resources (scripts, actions, images), use external storage with presigned URLs.

Supported: Actions and UXP scripts

Inline content is allowed for:

Example: Inline ActionJSON

{
  "options": {
    "actions": [
      {
        "source": {
          "content": "[{\"_obj\":\"gaussianBlur\",\"radius\":{\"_unit\":\"pixelsUnit\",\"_value\":5}}]",
          "contentType": "application/json"
        }
      }
    ]
  }
}

Note: The content field must contain stringified JSON (a JSON string), not a direct JSON object or array. The ActionJSON array is converted to a string using JSON.stringify().

Example: Inline UXP Script

{
  "options": {
    "uxp": {
      "source": {
        "content": "const doc = app.activeDocument; doc.flatten();",
        "contentType": "application/javascript"
      }
    }
  }
}

Not supported: Binary resources

Inline content is NOT allowed for:

These binary resources must be provided as external file references using:

Correct Usage: External References

{
  "image": {
    "source": {
      "url": "<SIGNED_IMAGE_URL>"
    }
  },
  "options": {
    "additionalContents": [
      {
        "source": {
          "url": "<ADDITIONAL_IMAGE_URL>"
        }
      }
    ],
    "brushes": [
      {
        "source": {
          "url": "<BRUSH_FILE_URL>"
        }
      }
    ],
    "patterns": [
      {
        "source": {
          "url": "<PATTERN_FILE_URL>"
        }
      }
    ]
  }
}

Incorrect Usage: Attempting Inline Content

{
  "options": {
    "brushes": [
      {
        "content": "base64_encoded_data..."  // NOT SUPPORTED
      }
    ]
  }
}
data-variant=warning
data-slots=text
Attempting to use inline content for binary resources (images, brushes, patterns, fonts) will result in a validation error. Always use external file references for these resource types.

Migrating Convenience APIs

V1 had several convenience APIs that used predefined server-side action files. In v2, these action files are published and available for you to use, customize, and learn from.

Key Benefits:

Available convenience APIs

Each convenience API has a detailed migration guide with complete ActionJSON definitions, customization examples, and use cases:

Convenience API
V1 Endpoint
Key Feature
Detailed Guide
Product Crop
/pie/psdService/productCrop
Auto cutout with customizable padding
Product Crop Migration
Split View
/pie/psdService/splitView
Before/after comparison with center divider
Split View Migration
Side by Side
/pie/psdService/sideBySide
Simple side-by-side comparison layout
Side by Side Migration
data-variant=info
data-slots=text
Split View and Side by Side require two additional images (edited image and logo). Product Crop works with a single input image.

Script-based output discovery

When using UXP scripts that generate files, use the scriptOutputPattern parameter to specify which files should be captured as outputs. This supports both exact filenames and glob patterns for dynamic output discovery.

Supported Destination Types:

data-variant=warning
data-slots=text
scriptOutputPattern only works with embedded and hosted destinations. External storage (presigned URLs, Dropbox, Azure) is not supported for script-generated outputs.

Example - Single File:

{
  "outputs": [
    {
      "destination": {
        "hosted": true
      },
      "mediaType": "image/jpeg",
      "scriptOutputPattern": "final_composite.jpg"
    }
  ]
}

Example - Multiple Files with Glob Pattern:

{
  "outputs": [
    {
      "destination": {
        "embedded": "json"
      },
      "mediaType": "application/json",
      "scriptOutputPattern": "*.json"
    },
    {
      "destination": {
        "hosted": true
      },
      "mediaType": "image/png",
      "scriptOutputPattern": "result-*.png"
    }
  ]
}

How It Works:

  1. UXP scripts generate files to the plugin temporary directory
  2. The system matches files using the pattern and filters by mediaType
  3. Each matched file becomes a separate output entry
  4. Files are uploaded to the specified destination

Glob Pattern Examples:

data-variant=info
data-slots=text
When scriptOutputPattern is specified, output parameters like quality, compression, width, and height are ignored. Files are used as-is from the script's output directory. Keep glob patterns simple and use standard glob syntax (e.g., *.json, result-*.png, layer-[0-9].jpg).

UXP scripts (limited availability)

UXP scripts provide powerful automation capabilities with modern JavaScript. This section is for approved users only.

UXP Script Basics

UXP scripts can be provided as external .psjs (Photoshop JavaScript) file or inline content:

External UXP Script:

{
  "options": {
    "uxp": {
      "source": {
        "url": "<UXP_SCRIPT_URL>"
      }
    }
  }
}

Inline UXP Script:

{
  "options": {
    "uxp": {
      "source": {
        "content": "const app = require('photoshop').app; app.activeDocument.flatten();",
        "contentType": "application/javascript"
      }
    }
  }
}

UXP output configuration

UXP scripts can only access the UXP plugin temporary directory:

How It Works:

  1. Write to Plugin Temp Directory:

    • Use the literal path plugin-temp:/filename.ext in your UXP script
    • Or use the UXP module's storage.localFileSystem object to access plugin temporary directory
    • It automatically maps plugin-temp:/ to the correct temporary directory
  2. Specify Output Pattern:

    • Define scriptOutputPattern in your output configuration to specify which files to capture
    • Supports exact filenames (e.g., "result.json") or glob patterns (e.g., "*.png", "layer-*.jpg")
    • Only files matching both the pattern and mediaType are captured as outputs
  3. File Discovery and Upload:

    • After UXP execution completes, the worker scans the plugin temp directory
    • Matches files against your scriptOutputPattern and filters by mediaType
    • Each matched file becomes a separate output entry in the job result
    • Files are uploaded to the specified destination (hosted, embedded, or external storage)

Example UXP Script:

const { app } = require("photoshop");
const fs = require("fs");
const path = require("path");

// using plugin-temp: to access plugin temporary directory
const outputFile = 'plugin-temp:/generated.json';

async function main() {

  // Generate your output data
  const result = {
    documentName: app.activeDocument.name,
    layers: app.activeDocument.layers.length,
    timestamp: new Date().toISOString(),
  };

  // Save output files to the UXP temp folder
  fs.writeFileSync(outputFile, JSON.stringify(result, null, 2));

  console.log("Output saved to:", outputFile);
}

main().catch((err) => {
  console.error("Error:", err);
  process.exit(1);
});

Using scriptOutputPattern with UXP:

curl -X POST \
  https://photoshop-api.adobe.io/v2/execute-actions \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -H "Content-Type: application/json" \
  -d '{
  "image": {
    "source": {
      "url": "<SIGNED_GET_URL>"
    }
  },
  "options": {
    "uxp": {
      "source": {
        "url": "<UXP_SCRIPT_URL>"
      }
    }
  },
  "outputs": [
    {
      "destination": {
        "hosted": true
      },
      "mediaType": "application/json",
      "scriptOutputPattern": "result.json"
    },
    {
      "destination": {
        "hosted": true
      },
      "mediaType": "image/jpeg",
      "scriptOutputPattern": "processed_image.jpg"
    }
  ]
}'

Additional contents in UXP scripts

UXP scripts can reference additional contents using placeholders, similar to ActionJSON. This is useful when your scripts need to access or composite multiple images.

How It Works:

  1. Provide additional contents in the options.additionalContents array
  2. Reference them in UXP scripts using __ADDITIONAL_CONTENTS_PATH_0__, __ADDITIONAL_CONTENTS_PATH_1__, etc.
  3. The placeholder index corresponds to the array index (0-based)
  4. The worker automatically replaces these placeholders with actual file paths before execution

Example:

curl -X POST \
  https://photoshop-api.adobe.io/v2/execute-actions \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -H "Content-Type: application/json" \
  -d '{
  "image": {
    "source": {
      "url": "<BASE_IMAGE_URL>"
    }
  },
  "options": {
    "additionalContents": [
      {
        "source": {
          "url": "<OVERLAY_IMAGE_URL>"
        }
      },
      {
        "source": {
          "url": "<LOGO_IMAGE_URL>"
        }
      }
    ],
    "uxp": {
      "source": {
        "content": "const { app } = require(\"photoshop\"); const fs = require(\"fs\"); async function main() { const overlayPath = \"__ADDITIONAL_CONTENTS_PATH_0__\"; const logoPath = \"__ADDITIONAL_CONTENTS_PATH_1__\"; await app.batchPlay([{ _obj: \"placeEvent\", null: { _path: overlayPath, _kind: \"local\" } }], {}); fs.writeFileSync(\"plugin-temp:/result.json\", JSON.stringify({ overlayUsed: overlayPath, logoUsed: logoPath })); } main();",
        "contentType": "application/javascript"
      }
    }
  },
  "outputs": [
    {
      "destination": {
        "hosted": true
      },
      "mediaType": "application/json",
      "scriptOutputPattern": "result.json"
    }
  ]
}'
data-variant=info
data-slots=text
Maximum of 25 additional contents allowed per request. Use the array index to reference each content in your UXP scripts.

Best Practices for UXP Scripts:

  1. File Extension: UXP scripts use .psjs extension (Photoshop JavaScript) to distinguish them from generic JavaScript files
  2. Output Path: Use the __UXP_OUTPUT_PATH__ placeholder to reference the output directory---------
  3. Path Handling: Use path.join() to construct file paths - NEVER use hardcoded separators like "/" or "\\"
  4. Path Utilities: Use path.dirname(), path.basename(), and other Node.js path module functions
  5. Platform-Agnostic: Avoid platform-specific code or APIs (Windows/macOS/Linux)
  6. Descriptive Names: Use descriptive filenames for outputs
  7. Output Patterns: Specify the filename or pattern in scriptOutputPattern parameter
  8. Destinations: Use embedded or hosted destinations (external storage not supported for script outputs)
  9. Media Types: Filter outputs by mediaType - only matching file extensions are included
  10. Error Handling: Handle errors gracefully in your UXP scripts
  11. Testing: Test thoroughly before production use
data-variant=warning
data-slots=text
Never hardcode file paths or use platform-specific path separators in UXP scripts. Always use path.join() for constructing paths (e.g., path.join(outputFolder, "result.json") instead of outputFolder + "/result.json"). This prevents runtime issues across different operating systems.
data-variant=warning
data-slots=text
UXP scripts must be vetted by the Adobe DI ART Service team before use. Contact the team for approval and guidelines.

Multiple outputs

You can specify multiple outputs in different formats. A maximum of 25 outputs are allowed per request.

{
  "outputs": [
    {
      "destination": {
        "url": "<JPEG_URL>"
      },
      "mediaType": "image/jpeg",
      "quality": "maximum"
    },
    {
      "destination": {
        "url": "<PNG_URL>"
      },
      "mediaType": "image/png",
      "compression": "medium"
    },
    {
      "destination": {
        "url": "<PSD_URL>"
      },
      "mediaType": "image/vnd.adobe.photoshop"
    },
    {
      "destination": {
        "embedded": "json"
      },
      "mediaType": "application/json"
    }
  ]
}

Common migration issues

Action file not found

Problem: Using v1-style action reference

"actions": [
  {
    "href": "<ACTION_URL>",
    "storage": "external"
  }
]

Solution: Use v2 source structure

"actions": [
  {
    "source": {
      "url": "<ACTION_URL>"
    }
  }
]

Additional contents not working

Problem: Incorrect placeholder format

"content": "[{\"file\":\"additional_contents_path_0\"}]"

Solution: Use correct placeholder format

"content": "[{\"file\":\"__ADDITIONAL_CONTENTS_PATH_0__\"}]"

Invalid contentType

Problem: Missing or incorrect contentType for inline content

Solution: Always specify contentType for inline content

{
  "source": {
    "content": "...",
    "contentType": "application/json"
  }
}

Next steps

Need help?

Contact the Adobe DI ART Service team for: