← All guides

Design handoff · 5 min

Photoshop to a coding agent: reading a PSD without Adobe

A PSD is a document of pixels with a layer tree bolted on. Parsers can read that tree without Adobe running — and knowing exactly what survives the parse is what keeps the route useful.

Updated


Photoshop predates the idea that a design file should be readable by a machine other than Photoshop. A PSD is a raster document: a canvas of pixels with a layer tree bolted on top, saved in a binary format that was never designed to describe an interface.

That is not a criticism, it is a scope. And it means the useful question is not "how do I get my PSD into my agent" but "what is actually in this file that an agent can use".

Reading the file without Adobe

You do not need Photoshop running, or installed. Two mature parsers read the binary directly:

  • ag-psd for Node.js
  • psd-tools for Python

Both open the file in memory and expose the layer tree: groups and their nesting, layer names, blend modes, opacity, position and bounds, vector path coordinates, and the rasterised pixel data for each layer. That is enough to walk the document programmatically, extract every layer as its own image, and produce a structured description an agent can read.

The practical shape is a small script rather than an integration. Parse the file, walk the tree, write out a JSON summary of the structure alongside the exported assets, and hand the agent the summary. Running inside a build script or a container is the point — this route works in places where a design tool's MCP server cannot go.

The whole of it is about fifteen lines:

import { readPsd } from "ag-psd";
import fs from "node:fs";

const psd = readPsd(fs.readFileSync("design.psd"), { skipCompositeImageData: true });

const walk = (layer, depth = 0) => [
  {
    name: layer.name,
    depth,
    bounds: [layer.left, layer.top, layer.right, layer.bottom],
    text: layer.text?.text,
    opacity: layer.opacity,
  },
  ...(layer.children ?? []).flatMap((child) => walk(child, depth + 1)),
];

fs.writeFileSync("design.json", JSON.stringify(psd.children.flatMap((l) => walk(l)), null, 2));

Note skipCompositeImageData — the flattened preview is usually the largest thing in the file and nothing downstream needs it. Give the agent design.json rather than the PSD, and it reads a structure instead of a binary it cannot open.

What survives the parse, and what does not

This is the section worth being precise about, because the gap between the two lists is where the time goes.

Survives:

  • Group structure and layer names, which is your only signal of intent. Header / Nav / CTA tells you something. Layer 12 copy 3 does not.
  • Position and bounds for every layer, in absolute pixels.
  • Vector path coordinates for shape layers, which is what makes clean icon extraction possible.
  • Blend modes and opacity, so a treatment can be reproduced rather than eyeballed.
  • Pixel data per layer, which is the actual value of this route — every asset, isolated, at full quality.

Does not survive, because it was never there:

  • Constraints and responsive intent. A PSD is authored at one canvas size. There is no expression of what should happen at any other.
  • Component semantics. Three buttons that look identical are three unrelated groups of layers. Nothing marks them as instances of one thing.
  • A type scale. You get sizes. You do not get which of them is "the H2 size" and which is a heading that happened to look better slightly larger.
  • Editable text, reliably. A live type layer usually keeps its string and some styling. A rasterised or flattened one is pixels, and recovering the words means reading the image.

Notice the pattern. Everything about appearance survives; everything about rules does not. That is the same gap as the Figma handoff, except a PSD does not even carry the component tree that Figma has, so it lands further from the finish line.

Use it for assets, not for layout

The honest recommendation is to stop asking a PSD to be a layout source.

Where this route is genuinely excellent is asset extraction at scale. A parser walking the tree can export every icon as its own transparent PNG, pull vector paths for shapes that should be SVG, isolate a photographic treatment from its background, and do it for two hundred layers without anyone clicking anything. That is a real job, it is tedious by hand, and an agent driving a parser does it reliably.

Where it is weak is asking an agent to reconstruct an interface from absolute coordinates. It can, and the result is a pixel-faithful rendering at exactly one width, built from divs that have no relationship to each other — the same failure as a flattened Canva export, for the same reason.

If the PSD is a real interface design rather than an asset source, the better move is usually sideways: import it into Figma with a tool such as Codia AI Psd2Figma, then use the far richer Figma route. You are converting a document into a design file, which is the translation the rest of the toolchain is built around.

The decisions worth rescuing

Before the file goes back in the archive, there is something in it more durable than any asset, and it is not stored anywhere the parser can reach: the decisions someone made.

Which grey is the page and which is the card. Which of those five type sizes are real and which are accidents. What the accent colour was for. Which of the treatments in this file is the house style and which was an experiment nobody liked. Those are in the designer's head and in the file's history, and they are the part that generalises to a screen the PSD never contained.

Written down, they are what lets an agent build something new that belongs to the same product — as building a colour palette you can defend argues, #E8511E is a hex code, but "#E8511E, primary action, never body text" is a rule that survives contact with a component nobody designed. A few properties are worth insisting on in anything holding that record, and which moodspec's endpoint enforces: it is read-only, the token is scoped to one canvas rather than an account, and an agent can only propose an edit through propose_change, which a person approves. The docs are one block of config.

The test

Parse the PSD and hand the agent the layer tree and the assets. Ask it to build the screen — it will do a serviceable job.

Then ask it which of the type sizes in that file is the body size, and why.

It cannot know. The file contains 14, 15, 16 and 17 with no indication of which is the system and which is drift, and neither will anyone who opens the file in two years. That answer never lived in the PSD. It is the thing worth writing down before the file stops being opened at all.

Further reading

  • ag-psd — PSD reading and writing in Node.js and the browser
  • psd-tools — the Python equivalent, with layer and pixel access
  • Codia AI Psd2Figma — the sideways route into a design file

Common questions

Can a coding agent read a PSD file directly?
Not natively. A PSD is a binary format, so it has to be parsed first. ag-psd for Node and psd-tools for Python both read the file without any Adobe application running, exposing layer groups, metadata, vector coordinates and rasterised pixel data.
Do I need Photoshop installed?
No. Both parsers work on the file itself, in server memory, which is what makes this route viable in a build script or a container.
Does text stay editable when parsed?
It depends on how the layer was saved. A live type layer usually retains its string and some styling; a rasterised or flattened one is pixels, and the words are only recoverable by reading the image.
Should I convert the PSD to Figma instead?
Often yes. Tools such as Codia AI Psd2Figma import a PSD into Figma, after which the far richer Figma handoff applies. It is the better route when the PSD is a real interface design rather than an asset source.
Is a PSD a good source for building a UI?
Rarely, as a layout source. It carries no constraints, no responsive intent and no component semantics. It is an excellent source for assets and for the visual decisions — the colours, the type sizes, the treatments — that you record separately.

Keep reading