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/text
/v2/execute-actions with inline actionJSON or UXP scripts

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
Depth Blur ⚠️
/pie/psdService/depthBlur
Neural filter depth-of-field with 11+ parameters
Depth Blur Migration - Not yet supported in V2
Text Layer Operations
/pie/psdService/text
Edit text layers (font, size, color) via ActionJSON or UXP scripts
Text Layer Operations Migration
data-variant=warning
data-slots=text
Depth Blur is not currently supported in V2 as Neural Filters are not yet available. Continue using the V1 endpoint for this feature.

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": {
        "validityPeriod": 3600
      },
      "mediaType": "image/jpeg",
      "scriptOutputPattern": "final_composite.jpg"
    }
  ]
}

Example - Multiple Files with Glob Pattern:

{
  "outputs": [
    {
      "destination": {
        "embedded": "json"
      },
      "mediaType": "application/json",
      "scriptOutputPattern": "*.json"
    },
    {
      "destination": {
        "validityPeriod": 3600
      },
      "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": {
        "validityPeriod": 3600
      },
      "mediaType": "application/json",
      "scriptOutputPattern": "result.json"
    },
    {
      "destination": {
        "validityPeriod": 3600
      },
      "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": {
        "validityPeriod": 3600
      },
      "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: Write output files to plugin-temp:/filename (e.g., plugin-temp:/result.json)
  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 storage (validityPeriod) as destination (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.

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"
  }
}

Validation caveats: stochastic filters

Several Photoshop filter actions use an internal random seed that differs between the V1 and V2 rendering engines. Even when ActionJSON is correctly migrated, pixel-diff comparison against V1 reference outputs is not a valid acceptance test for these filters — expect 50–80% pixel differences due to different noise patterns, not a migration error.

Known stochastic filter actions (_obj values): addNoise, grain, reticulation, mezzotint, pointillize, clouds, differenceClouds, spatter, sprayedStrokes, colorHalftone

Note on colorHalftone: This filter is deterministic in principle, but its dot-pattern generation and anti-aliasing differ between the V1 and V2 rendering engines, producing pixel differences of ~15–20% on typical images even for correctly migrated ActionJSON. Treat it as stochastic for validation purposes.

Alternative validation approaches for stochastic filters:

JPEG quality and pixel fidelity

The V1 and V2 APIs use different JPEG encoders. Even with semantically equivalent quality settings, sub-1% pixel differences between V1 and V2 JPEG outputs are expected and are not a migration error. Use "photoshop_max" as the baseline quality setting when pixel fidelity is critical. Accept sub-1% diffs as normal when comparing V1 reference outputs to V2 outputs across worker versions.

Complete V1 parity reference

For a field-level table comparison of every V1 field versus its V2 equivalent — including all breaking changes, removed options, and new V2-only features — see:

Execute Actions — Complete V1 Migration Reference

This reference covers all five V1 endpoints replaced by /v2/execute-actions: photoshopActions, actionJSON, and productCrop.

Next steps