Text Layer Operations Migration
This guide helps you migrate text layer operations from V1's /documentCreate and /documentOperations endpoints to V2's unified /create-composite endpoint.
Overview
V1 Endpoints:
/pie/psdService/documentCreate- Create documents with text layers/pie/psdService/documentOperations- Add/edit text layers
V2 Endpoint: /v2/create-composite with edits.layers containing text layer operations
Creating text layers
V1 approach: documentCreate
curl -X POST \
https://image.adobe.io/pie/psdService/documentCreate \
-H "Authorization: Bearer $token" \
-H "x-api-key: $apiKey" \
-H "Content-Type: application/json" \
-d '{
"options": {
"document": {
"width": 1000,
"height": 1000,
"resolution": 72,
"fill": "white",
"mode": "rgb"
},
"layers": [
{
"type": "textLayer",
"name": "My Title",
"text": {
"content": "Hello World",
"characterStyles": [
{
"fontSize": 48,
"fontName": "Arial-BoldMT",
"fontColor": {
"rgb": {
"red": 255,
"green": 0,
"blue": 0
}
}
}
],
"paragraphStyles": [
{
"alignment": "center"
}
]
}
}
]
},
"outputs": [{
"href": "<SIGNED_POST_URL>",
"storage": "external",
"type": "image/vnd.adobe.photoshop"
}]
}'
V2 approach
curl -X POST \
https://photoshop-api.adobe.io/v2/create-composite \
-H "Authorization: Bearer $token" \
-H "x-api-key: $apiKey" \
-H "Content-Type: application/json" \
-d '{
"document": {
"width": 1000,
"height": 1000,
"resolution": {"unit": "density_unit", "value": 72},
"fill": {
"solidColor": {
"red": 255,
"green": 255,
"blue": 255
}
},
"mode": "rgb"
},
"edits": {
"layers": [
{
"type": "text_layer",
"name": "My Title",
"text": {
"content": "Hello World",
"antiAliasing": "smooth",
"textOrientation": "horizontal",
"characterStyles": [
{
"characterStyle": {
"fontSize": 48,
"font": {
"postScriptName": "ArialMT"
},
"fontColor": {
"rgb": {
"red": 255,
"green": 0,
"blue": 0
}
}
}
}
],
"paragraphStyles": [
{
"paragraphStyle": {
"alignment": "center"
}
}
]
},
"operation": {
"type": "add",
"placement": {
"type": "top"
}
}
}
]
},
"outputs": [
{
"destination": {
"url": "<SIGNED_POST_URL>"
},
"mediaType": "image/vnd.adobe.photoshop"
}
]
}'
Key migration changes
1. Layer type name
V1: "type": "textLayer"
V2: "type": "text_layer"
typeis required on every layer entry inedits.layers[]. V1 allowed omittingtypefor simple visibility or property-only edits on existing layers; V2 always requires it. Error when omitted:Missing required field 'type' for edit operation at path 'edits.layers[N]'.
2. Character styles structure
data-variant=warning
data-slots=text
to field on a character or paragraph style range entry was interpreted as a length — so from: 0, to: 5 meant "5 characters starting at index 0" (indices 0-4). In V2, apply.to is an inclusive end index (0-based) — so apply: {from: 0, to: 4} means "characters at indices 0, 1, 2, 3, 4" (the first 5 characters). When migrating V1 ranges to V2, set apply.to to the last character index you want to style, not a length.Off-by-one example — styling the word "Hello" in "Hello World":
// V1: to=5 means length 5 → characters 0,1,2,3,4
{"from": 0, "to": 5, "fontName": "Arial-BoldMT"}
// V2: to=4 is the inclusive end index → characters 0,1,2,3,4 (same result)
{"apply": {"from": 0, "to": 4}, "characterStyle": {"font": {"postScriptName": "Arial-BoldMT"}}}
characterStyles with no range (implicit full-string in V1): If a V1 characterStyle entry has neither from nor to (applies to the entire content implicitly), apply is optional in V2. Omitting apply applies the style to the entire text content. You may include apply: {from: 0, to: len(text.content) - 1} explicitly if you prefer, but it is not required.
V1: Direct properties in characterStyles array. The range is given with from and to on each item (no apply wrapper):
{
"characterStyles": [
{
"fontName": "Arial-BoldMT",
"fontSize": 60,
"from": 0,
"to": 3,
"fontColor": {
"rgb": {
"red": 255,
"green": 0,
"blue": 0
}
}
}
]
}
V2:
- Put style properties inside a
characterStyleobject. - Use
apply.fromandapply.tofor the character range (0-based, inclusive). - Use
font.postScriptNameinstead offontName.
{
"characterStyles": [
{
"apply": {
"from": 0,
"to": 11
},
"characterStyle": {
"font": {
"postScriptName": "Arial-BoldMT"
},
"fontSize": 48,
"syntheticBold": false,
"syntheticItalic": false,
"underline": false,
"capsOption": "all_caps",
"fontColor": {
"rgb": {
"red": 255,
"green": 0,
"blue": 0
}
}
}
}
]
}
data-variant=info
data-slots=text
apply.from and apply.to are inclusive: the style applies to characters at indices from through to (0-based). For example, "from": 0, "to": 4 applies to the first five characters (indices 0, 1, 2, 3, 4).3. Paragraph styles structure
V1: Range-based: each array item has from, to, and direct properties (e.g. alignment).
{
"paragraphStyles": [
{
"from": 0,
"to": 20,
"alignment": "center"
}
]
}
data-variant=info
data-slots=text
to was incorrectly interpreted as length. In V2, apply.from and apply.to are both inclusive 0-based indices. When migrating, use the last character index for apply.to, not a length.V2: Each item is a paragraph style range with optional apply.from/apply.to (0-based inclusive character indices) and a paragraphStyle object.
{
"paragraphStyles": [
{
"paragraphStyle": {
"alignment": "center"
}
}
]
}
With a range (V2 only): use apply.from and apply.to to apply the paragraph style to a specific character range (same inclusive 0-based indexing as character styles).
{
"paragraphStyles": [
{
"apply": { "from": 0, "to": 20 },
"paragraphStyle": {
"alignment": "left",
"writingDirection": "left_to_right",
"firstLineIndent": 0,
"startIndent": 0,
"endIndent": 0,
"spaceBefore": 0,
"spaceAfter": 6
}
}
]
}
4. Font management and font options
Font management covers how you supply custom fonts (e.g. .ttf, .otf), set a default or fallback font when a referenced font is missing, and choose whether to fail the job or substitute the default.
V1 font options
options.fontshref (URL) and storage (e.g. "external").options.globalFontoptions.manageMissingFonts"useDefault" or "fail". When "useDefault", missing fonts are replaced by globalFont if set, otherwise ArialMT.Example (V1):
{
"options": {
"fonts": [
{ "href": "<SIGNED_GET_URL>", "storage": "external" }
],
"globalFont": "ArialMT",
"manageMissingFonts": "useDefault"
}
}
V2 font options
fontOptions.additionalFontssource.url. Downloaded before the document is opened.fontOptions.defaultFontPostScriptNamefontOptions.missingFontStrategy"use_default" or "fail". Default is "use_default".Example (V2):
{
"fontOptions": {
"additionalFonts": [
{ "source": { "url": "<SIGNED_GET_URL>" } }
],
"defaultFontPostScriptName": "Arial_BoldMT",
"missingFontStrategy": "use_default"
}
}
Property mapping (V1 to V2)
options.fontsfontOptions.additionalFonts (use source.url instead of href + storage)options.globalFontfontOptions.defaultFontPostScriptNameoptions.manageMissingFontsfontOptions.missingFontStrategy"useDefault""use_default""fail""fail"Font types
Both V1 and V2 support three font sources:
.ttf, .otf) provided via fontOptions.additionalFonts.Default font value
- V1: If
globalFontis not set andmanageMissingFontsis"useDefault", the worker uses ArialMT as the fallback. - V2: If
defaultFontPostScriptNameis not set and the strategy is use default, the worker uses ArialMT as the fallback.
In both V1 and V2, the default fallback font is ArialMT when no global/default is specified.
Missing-font strategy
manageMissingFonts)missingFontStrategy)"useDefault""use_default" (default)"fail""fail"Adding text to existing document
V1 approach: documentOperations
curl -X POST \
https://image.adobe.io/pie/psdService/documentOperations \
-H "Authorization: Bearer $token" \
-H "x-api-key: $apiKey" \
-H "Content-Type: application/json" \
-d '{
"inputs": [{
"href": "<SIGNED_GET_URL>",
"storage": "external"
}],
"options": {
"fonts": [
{
"href": "<FONT_FILE_URL>",
"storage": "external"
}
],
"layers": [
{
"add": { "insertTop": true },
"type": "textLayer",
"bounds": {
"left": 25,
"top": 42,
"width": 977,
"height": 267
},
"text": {
"content": "NEW TEXT LAYER",
"characterStyles": [
{
"fontSize": 56,
"orientation": "horizontal",
"fontColor": {
"rgb": {
"red": 0,
"green": 0,
"blue": 0
}
},
"fontName": "AcerFoco-SemiboldItalic"
}
]
}
}
]
},
"outputs": [{
"href": "<SIGNED_POST_URL>",
"storage": "external",
"type": "image/vnd.adobe.photoshop"
}]
}'
V2 approach
curl -X POST \
https://photoshop-api.adobe.io/v2/create-composite \
-H "Authorization: Bearer $token" \
-H "x-api-key: $apiKey" \
-H "Content-Type: application/json" \
-d '{
"image": {
"source": {
"url": "<SIGNED_GET_URL>"
}
},
"fontOptions": {
"additionalFonts": [
{ "source": { "url": "<FONT_FILE_URL>" } }
],
"defaultFontPostScriptName": "Arial_BoldMT",
"missingFontStrategy": "use_default"
},
"edits": {
"layers": [
{
"type": "text_layer",
"name": "New Text Layer",
"operation": {
"type": "add",
"placement": {
"type": "top"
}
},
"text": {
"content": "NEW TEXT LAYER",
"antiAliasing": "smooth",
"textOrientation": "horizontal",
"frame": {
"type": "area",
"bounds": {
"top": 42,
"left": 25,
"right": 1002,
"bottom": 309
}
},
"characterStyles": [
{
"apply": { "from": 0, "to": 13 },
"characterStyle": {
"font": { "postScriptName": "AcerFoco-SemiboldItalic" },
"fontSize": 56,
"fontColor": {
"rgb": {
"red": 0,
"green": 0,
"blue": 0
}
}
}
}
]
}
}
]
},
"outputs": [
{
"destination": { "url": "<SIGNED_POST_URL>" },
"mediaType": "image/vnd.adobe.photoshop"
}
]
}'
Editing existing text layers
V1 approach
{
"options": {
"layers": [
{
"name": "Existing Text Layer",
"text": {
"content": "Updated Text"
},
"edit": {}
}
]
}
}
V2 approach
{
"edits": {
"layers": [
{
"name": "Existing Text Layer",
"operation": {
"type": "edit"
},
"text": {
"content": "Updated Text",
"characterStyles": [
{
"characterStyle": {
"fontSize": 48
}
}
]
}
}
]
}
}
Common migration patterns
Multi-style text
V1:
{
"text": {
"content": "Bold and Italic",
"characterStyles": [
{
"from": 0,
"to": 4,
"fontSize": 48,
"fontName": "Arial-BoldMT"
},
{
"from": 9,
"to": 15,
"fontSize": 48,
"fontName": "Arial-ItalicMT"
}
]
}
}
data-variant=info
data-slots=text
to was treated as length; in V2, apply.from and apply.to are both inclusive 0-based indices. Use the last character index for apply.to, not a length. See Character styles structure.V2: Use apply.from/apply.to (inclusive) and characterStyle with font.postScriptName
{
"text": {
"content": "Bold and Italic",
"characterStyles": [
{
"apply": { "from": 0, "to": 4 },
"characterStyle": {
"fontSize": 48,
"font": { "postScriptName": "Arial-BoldMT" }
}
},
{
"apply": { "from": 9, "to": 15 },
"characterStyle": {
"fontSize": 48,
"font": { "postScriptName": "Arial-ItalicMT" }
}
}
]
}
}
Multi-line text
Both V1 and V2 use \r for line breaks:
{
"text": {
"content": "Line 1\rLine 2\rLine 3"
}
}
Custom fonts
V1:
{
"options": {
"fonts": [
{
"href": "<FONT_FILE_URL>",
"storage": "external"
}
]
}
}
V2:
{
"fontOptions": {
"additionalFonts": [
{
"source": {
"url": "<FONT_FILE_URL>"
}
}
]
}
}
Character style properties
V2 has expanded character style options beyond V1:
fontSizefontNamefont.postScriptName in V2font.postScriptNamecharacterStyle.fontfontColorfontColor.rgb, fontColor.cmyk)orientationtextOrientation on textsyntheticBoldsyntheticItalicletterSpacinglineHeightfontAlphacapsOptionall_caps)data-variant=info
data-slots=text
Paragraph style properties
In V2, all paragraph style properties below are optional; defaults are applied when omitted.
alignmentleft (default), center, right, justify, justify_left, justify_right, justify_center.writingDirectionleft_to_right (default), right_to_left.firstLineIndentstartIndentendIndentspaceBeforespaceAfterText positioning
Frame types: point vs area
text.frame with type: "area" and bounds: { top, left, right, bottom }.text.frame with type: "point" and origin: { x, y }. Single-point text, no wrapping.V1 only supported area frames. V2 supports both point and area frames.
Default bounds when frame is omitted
left: 0, top: 0, width: 4, height: 4 (pixels).text.frame is omitted, the default is a point frame at the center of the canvas.In V1 the default was a small area at the top-left; in V2 the default is a point at the middle of the canvas. To get predictable placement in V2, always set text.frame (either type: "area" with bounds or type: "point" with origin).
Using bounds
V1: Layer-level bounds with left, top, width, height. Only area frame; no point frame support.
{
"type": "textLayer",
"name": "Positioned Text",
"bounds": {
"left": 100,
"top": 50,
"width": 800,
"height": 200
},
"text": {"content": "..."}
}
V2: Position and size the text using text.frame with type: "area" and bounds as top, left, right, bottom (no width/height). Convert V1 bounds: right = left + width, bottom = top + height.
{
"type": "text_layer",
"name": "Positioned Text",
"operation": {
"type": "add",
"placement": {
"type": "top"
}
},
"text": {
"content": "...",
"frame": {
"type": "area",
"bounds": {
"top": 50,
"left": 100,
"right": 900,
"bottom": 250
}
}
}
}
Font size units
Both V1 and V2 use pixels for fontSize. In V2 it goes inside characterStyle.fontSize.
Font color structure
Supported in both V1 and V2: rgb, cmyk, lab, and gray. V1 accepted 0–65535 for all components; V2 enforces lower maximums:
rgbred, green, bluered, green, bluecmykcyan, magenta, yellowColor, blackcyan, magenta, yellow, blacklabluminancellaba, ba, bgraygraygrayAll components are required and default to 0 when omitted.
data-variant=warning
data-slots=text1
fontColor component exceeds its maximum. If your V1 payload contains a value above the V2 limit, cap it at the maximum before sending:rgb,cmyk,gray,lab.l— cap at 32768. Example: V1red: 40000→ V2red: 32768lab.a,lab.b— cap at 16384. Example: V1a: 40000→ V2a: 16384
V2 structure (updated field names): rgb, cmyk, lab, gray:
{
"fontColor": {
"rgb": {
"red": 32768,
"green": 0,
"blue": 0
}
}
}
{
"fontColor": {
"cmyk": {
"cyan": 32768,
"magenta": 0,
"yellow": 0,
"black": 0
}
}
}
{
"fontColor": {
"lab": {
"l": 5000,
"a": 10000,
"b": -5000
}
}
}
{
"fontColor": {
"gray": {
"gray": 16384
}
}
}
Migration checklist
When migrating text layer operations from V1 to V2:
- Change
type: "textLayer"totype: "text_layer" - Use
apply.from/apply.tofor character style ranges (V2); both indices are inclusive (0-based); wrap style properties incharacterStyle - Use
font.postScriptName(insidecharacterStyle.font) in V2 instead of top-levelfontName - Wrap paragraph style properties in
paragraphStyleobject - Font color field renames:
fontColor.cmyk.yellowColor→fontColor.cmyk.yellow;fontColor.lab.luminance→fontColor.lab.l - Font color rgb/cmyk/gray/lab-l: V1 accepted 0–65535; V2 clamps to 0–32768 (any value > 32768 becomes 32768)
- Font color lab
a/b: V1 accepted 0–65535; V2 range is -16384–16384 (any value > 16384 becomes 16384) - Update font management properties (
manageMissingFonts→missingFontStrategy,globalFont→defaultFontPostScriptName) - Update custom fonts:
options.fonts(href, storage) →fontOptions.additionalFonts(source.url) - Update inputs/outputs: V1
inputs[].href→ V2image.source.url; V1outputs[].href→ V2outputs[].destination.url, and usemediaTypeinstead oftype - Map layer placement: V1
add.insertTop(or insertBottom, etc.) → V2operation: { type: "add", placement: { type: "top" } }(or bottom, above, below, into) - Convert bounds: V1 layer
bounds(left, top, width, height) → V2text.framewithtype: "area"andbounds: { top, left, right, bottom }whereright = left + width,bottom = top + height
Common migration issues
Differences between V1 and V2 that often cause issues when migrating:
- Character and paragraph style range (
from/to): In V1,towas incorrectly taken as a length. In V2,apply.fromandapply.toare both inclusive 0-based character indices. When migrating, setapply.toto the last character index you want to style, not a length. Same applies to paragraph style ranges. - Layer type: V1 uses
"textLayer"; V2 uses"text_layer". - Font name: V1 uses top-level
fontName. V2 usescharacterStyle.font.postScriptName. - Bounds: V1 uses layer-level
boundswithleft,top,width,height. V2 usestext.framewithtype: "area"andbounds: { top, left, right, bottom }whereright = left + width,bottom = top + height. - Default when no frame/bounds given: V1 default was an area frame (0, 0, 4, 4). V2 default is a point frame at the center of the canvas. Set
text.frameexplicitly for predictable placement. - Font options: V1 uses
options.fonts(href, storage),options.globalFont,options.manageMissingFonts. V2 usesfontOptions.additionalFonts(source.url),fontOptions.defaultFontPostScriptName,fontOptions.missingFontStrategy(e.g."use_default").
Feature availability
Currently available in V2
- ✅ Add text layers
- ✅ Edit text content
- ✅ Character styles (font, size, color: rgb, cmyk, lab, gray)
- ✅ Paragraph styles (alignment)
- ✅ Multi-style text (from/to ranges)
- ✅ Custom fonts
- ✅ Text positioning (bounds)
- ✅ Expanded character properties (tracking, leading, etc.)
V1 features not yet in V2
- ⏳
fill_to_canvasproperty - ⏳ Some advanced text effects
Complete migration example
V1 documentOperations:
{
"inputs": [
{
"href": "<SIGNED_GET_URL>",
"storage": "external"
}
],
"options": {
"layers": [
{
"type": "textLayer",
"name": "Headline",
"text": {
"content": "Welcome",
"characterStyles": [
{
"fontSize": 72,
"fontName": "Arial-BoldMT",
"fontColor": {
"rgb": {
"red": 0,
"green": 0,
"blue": 0
}
}
}
],
"paragraphStyles": [
{
"alignment": "center"
}
]
},
"bounds": {
"left": 0,
"top": 100,
"width": 1920,
"height": 150
}
}
],
"manageMissingFonts": "useDefault"
},
"outputs": [
{
"href": "<SIGNED_POST_URL>",
"storage": "external",
"type": "image/vnd.adobe.photoshop"
}
]
}
V2 create-composite:
{
"image": {
"source": {
"url": "<SIGNED_GET_URL>"
}
},
"edits": {
"layers": [
{
"type": "text_layer",
"name": "Headline",
"text": {
"content": "Welcome",
"frame": {
"type": "area",
"bounds": {
"top": 100,
"left": 0,
"right": 1920,
"bottom": 250
}
},
"characterStyles": [
{
"characterStyle": {
"fontSize": 72,
"font": {
"postScriptName": "Arial-BoldMT"
},
"fontColor": {
"rgb": {
"red": 0,
"green": 0,
"blue": 0
}
}
}
}
],
"paragraphStyles": [
{
"paragraphStyle": {
"alignment": "center"
}
}
]
},
"operation": {
"type": "add"
}
}
]
},
"fontOptions": {
"missingFontStrategy": "use_default"
},
"outputs": [
{
"destination": {
"url": "<SIGNED_POST_URL>"
},
"mediaType": "image/vnd.adobe.photoshop"
}
]
}
Next steps
- Review Layer Operations Overview for general concepts
- Check Document Creation for creating new documents with text
- See Advanced Operations for text layer transforms and effects