Execute Actions Workflow
Learn how to automate Photoshop operations using Actions and UXP Scripts with the /v2/execute-actions endpoint.
The execute-actions workflow allows you to execute Photoshop operations programmatically using Actions (JSON-formatted action descriptors and traditional binary .atn files) and UXP Scripts (modern JavaScript automation).
Key capabilities include:
- Actions and UXP Scripts: Execute both traditional Actions (recorded operations) and modern UXP Scripts (JavaScript automation)
- Inline or external: Use inline JSON/JavaScript or reference external
.atn,.json, or.psjsfiles - Dynamic placeholders: Inject file paths at runtime using placeholder syntax
- Multiple actions: Chain up to 10 actions in sequence (UXP limited to 1 script per request)
- Pattern-based outputs: Capture dynamically generated files using glob patterns
Recommendation: Use Actions for image manipulation and effects. Use UXP Scripts when you need conditional logic, data extraction, or complex decision-making.
Prerequisites
Before you begin, ensure you have:
- Authentication token: Follow the Authentication guide to get your OAuth 2.0 Bearer token
- API key: Your Client ID from the Adobe Developer Console
- Image storage: A publicly accessible URL for your input image (S3, Azure, Dropbox, or any pre-signed URL)
Set up your environment variables:
export TOKEN="your_access_token"
export API_KEY="your_client_id"
Request structure
All execute-actions requests follow this pattern:
{
"image": {
"source": {
"url": "<INPUT_IMAGE_URL>"
}
},
"options": {
"actions": [/* Action definitions */],
"uxp": {/* UXP script */},
"additionalContents": [/* Additional images */]
},
"outputs": [
{
"mediaType": "<OUTPUT_FORMAT>",
"destination": {
"validityPeriod": 3600
},
"scriptOutputPattern": "<FILENAME_OR_PATTERN>"
}
]
}
Key fields:
image.source.url: URL to your input image (required)options.actions(array, max 10) oroptions.uxp(single script): At least one is requiredoptions.additionalContents: Optional array of additional images (max 25)outputs.mediaType: Output format (image/jpeg,image/png,image/vnd.adobe.photoshop,application/json)outputs.scriptOutputPattern: Required for UXP scripts that generate filesdestination.validityPeriod: URL validity duration in seconds (default: 3600)
Working with actions
Actions allow you to automate Photoshop operations using JSON-formatted descriptors or traditional .atn files. You can provide actions inline or reference external files.
Example: warm vintage film look
Apply a warm vintage effect with sepia tones and adjusted levels:
curl -X POST "https://photoshop-api.adobe.io/v2/execute-actions" \
-H "Authorization: Bearer $TOKEN" \
-H "x-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": {
"source": {
"url": "https://your-storage.com/input-image.jpg"
}
},
"options": {
"actions": [
{
"source": {
"content": "[{\"_obj\":\"make\",\"_target\":[{\"_ref\":\"adjustmentLayer\"}],\"using\":{\"_obj\":\"adjustmentLayer\",\"type\":{\"_obj\":\"hueSaturation\",\"colorize\":true,\"hue\":{\"_unit\":\"angleUnit\",\"_value\":30},\"saturation\":{\"_unit\":\"percentUnit\",\"_value\":25},\"lightness\":{\"_unit\":\"percentUnit\",\"_value\":0}}}},{\"_obj\":\"make\",\"_target\":[{\"_ref\":\"adjustmentLayer\"}],\"using\":{\"_obj\":\"adjustmentLayer\",\"type\":{\"_obj\":\"levels\",\"adjustment\":[{\"_obj\":\"levelRecord\",\"channel\":{\"_ref\":\"channel\",\"_enum\":\"channel\",\"_value\":\"composite\"},\"inputFloor\":10,\"inputCeiling\":245,\"outputFloor\":15,\"outputCeiling\":255,\"gamma\":1.1}]}}},{\"_obj\":\"make\",\"_target\":[{\"_ref\":\"adjustmentLayer\"}],\"using\":{\"_obj\":\"adjustmentLayer\",\"type\":{\"_obj\":\"brightnessEvent\",\"brightness\":5,\"contrast\":-5}}}]",
"contentType": "application/json"
}
}
]
},
"outputs": [
{
"mediaType": "image/jpeg",
"destination": {
"validityPeriod": 3600
}
}
]
}'
Working with UXP scripts
The execute-actions endpoint supports UXP (Unified Extensibility Platform) scripts, allowing you to leverage modern JavaScript (ES6+) for advanced automation.
Example: flatten document and export
Flatten all layers in a PSD file and export as JPEG:
curl -X POST "https://photoshop-api.adobe.io/v2/execute-actions" \
-H "Authorization: Bearer $TOKEN" \
-H "x-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": {
"source": {
"url": "https://your-storage.com/input.psd"
}
},
"options": {
"uxp": {
"source": {
"content": "const { app } = require(\"photoshop\"); async function main() { await app.activeDocument.flatten(); } main();",
"contentType": "application/javascript"
}
}
},
"outputs": [
{
"mediaType": "image/jpeg",
"destination": {
"validityPeriod": 3600
}
}
]
}'
Sandboxing
UXP scripts run in a sandboxed environment for security. The sandbox restricts file system access (limited to plugin-temp:/ for output and additionalContents for input), blocks network requests, prevents shell command execution, and isolates scripts from system resources.
Restricted operations:
- Cannot access arbitrary file paths on the server
- Cannot make network requests (fetch, HTTP calls)
- Cannot execute shell commands or external processes
Working with files:
To work with files in UXP scripts, use the plugin-temp:/ directory for all file I/O operations. Write any output files (JSON, text, logs, metadata) to plugin-temp:/filename, and they will be automatically captured as outputs when you specify a matching scriptOutputPattern. For reading files, use the additionalContents array with placeholder syntax (__ADDITIONAL_CONTENTS_PATH_0__) to access external files passed to your script.
Example: writing and reading files
This example demonstrates file I/O within the sandbox by writing processing results and reading configuration data:
curl -X POST "https://photoshop-api.adobe.io/v2/execute-actions" \
-H "Authorization: Bearer $TOKEN" \
-H "x-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": {
"source": {
"url": "https://your-storage.com/input.psd"
}
},
"options": {
"additionalContents": [
{
"source": {
"url": "https://your-storage.com/config.json"
}
}
],
"uxp": {
"source": {
"content": "const { app } = require(\"photoshop\"); const fs = require(\"fs\"); async function main() { const configPath = \"__ADDITIONAL_CONTENTS_PATH_0__\"; const config = JSON.parse(fs.readFileSync(configPath, \"utf-8\")); const doc = app.activeDocument; if (config.applyResize && doc.width > config.maxWidth) { await app.batchPlay([{\"_obj\":\"imageSize\",\"width\":{\"_unit\":\"pixelsUnit\",\"_value\":config.maxWidth},\"constrainProportions\":true}], {}); } const report = { originalSize: { width: doc.width, height: doc.height }, configApplied: config, processedAt: new Date().toISOString() }; fs.writeFileSync(\"plugin-temp:/process-report.json\", JSON.stringify(report, null, 2)); } main();",
"contentType": "application/javascript"
}
}
},
"outputs": [
{
"mediaType": "image/jpeg",
"destination": {
"validityPeriod": 3600
}
},
{
"mediaType": "application/json",
"destination": {
"validityPeriod": 3600
},
"scriptOutputPattern": "process-report.json"
}
]
}'
Advanced features
Executing a specific action by name
When an .atn file contains multiple named actions, use actionName to execute only one of them. If actionName is omitted, all actions in the file run in order.
{
"options": {
"actions": [
{
"source": {
"url": "https://your-storage.com/my-actions.atn"
},
"actionName": "Vignette Effect"
}
]
}
}
You can mix targeted and full-execution actions in the same request:
{
"options": {
"actions": [
{
"source": {
"url": "https://your-storage.com/my-actions.atn"
},
"actionName": "Resize to Web"
},
{
"source": {
"url": "https://your-storage.com/color-grading.atn"
}
}
]
}
}
In the example above, only "Resize to Web" runs from the first file, while all actions run from the second file.
Multiple actions in sequence
Execute up to 10 actions one after another:
{
"options": {
"actions": [
{
"source": {
"content": "[{\"_obj\":\"imageSize\",\"width\":{\"_unit\":\"pixelsUnit\",\"_value\":800}}]",
"contentType": "application/json"
}
},
{
"source": {
"url": "https://your-storage.com/color-correction.json"
}
},
{
"source": {
"url": "https://your-storage.com/add-watermark.atn"
}
}
]
}
}
Combining actions with UXP scripts
You can combine multiple actions with one UXP script in a single request. Actions execute first, then the UXP script runs.
data-variant=info
data-slots=text
{
"options": {
"actions": [
{
"source": {
"content": "[{\"_obj\":\"imageSize\",\"width\":{\"_unit\":\"pixelsUnit\",\"_value\":1920}}]",
"contentType": "application/json"
}
}
],
"uxp": {
"source": {
"content": "const app = require('photoshop').app; const doc = app.activeDocument; /* custom UXP code */",
"contentType": "application/javascript"
}
}
}
}
Using additional contents
Composite multiple images using placeholders. Use __ADDITIONAL_CONTENTS_PATH_{index}__ in your action JSON or UXP script where {index} is the zero-based index of the additionalContents array.
{
"image": {
"source": {
"url": "https://your-storage.com/background.jpg"
}
},
"options": {
"additionalContents": [
{
"source": {
"url": "https://your-storage.com/logo.png"
}
},
{
"source": {
"url": "https://your-storage.com/overlay.png"
}
}
],
"actions": [
{
"source": {
"content": "[{\"_obj\":\"placeEvent\",\"null\":{\"_kind\":\"local\",\"_path\":\"__ADDITIONAL_CONTENTS_PATH_0__\"}}]",
"contentType": "application/json"
}
}
]
}
}
Script output patterns
Capture files created by UXP scripts using glob patterns:
{
"outputs": [
{
"mediaType": "image/png",
"destination": {
"validityPeriod": 3600
},
"scriptOutputPattern": "layer-*.png"
}
]
}
Loading custom resources
Fonts:
{
"options": {
"fontOptions": {
"additionalFonts": [
{
"source": {
"url": "https://your-storage.com/custom-font.ttf"
}
}
]
}
}
}
Brushes and patterns:
{
"options": {
"brushes": [
{
"source": {
"url": "https://your-storage.com/custom-brush.abr"
}
}
],
"patterns": [
{
"source": {
"url": "https://your-storage.com/custom-pattern.pat"
}
}
]
}
}
Common issues
Issue: invalid content type
Error: Invalid contentType for inline action
Solution: Always specify "contentType": "application/json" for actions and "contentType": "application/javascript" for UXP scripts.
Issue: placeholder not replaced
Error: File path contains literal __ADDITIONAL_CONTENTS_PATH_0__
Solution: Ensure you've provided additionalContents array in options and the index matches (0-based).
Issue: script output not found
Error: No files matching pattern
Solution: Verify your UXP script writes to plugin-temp:/filename and scriptOutputPattern matches the filename.