The Best Tool for Parsing Photoshop Files in Javascript

Recently, I spent some time looking through more than 15 libraries across several different programming languages to find a viable option for parsing and generating Photoshop Document (PSD) files. After comparing and testing options in Javascript, Python, Ruby, Java, C#, C++, Rust, and more, I was able to narrow the list down my favorite option: ag-psd.

ag-psd has many of the parsing features I was looking for and is one of the rare options that supports writing PSD files as well. On top of that, it’s written in TypeScript, supports browser and node environments, and has plenty of tests. What’s not to love?

Getting Started with ag-psd

First, we need to add ag-psd to our project, so we can install that with our package manager of choice. Additionally, I want to run this as a Node application, so we need to install node-canvas as well.

yarn add ag-psd canvas

Then, we just need to initialize the canvas, and we can read the PSD file:

import 'ag-psd/initialize-canvas';

import { readPsd } from 'ag-psd';
import * as fs from 'fs';
import * as path from 'path';

const file = fs.readFileSync(path.resolve(__dirname, 'file.psd'));

const psd = readPsd(file);

And as easy as that, we’ve parsed our first PSD file!

Additional Parsing

In my sample PSD file, I had several nested and grouped layers, but I just wanted a flat list of them. This is fairly easy to achieve with a simple recursive function:

const parseLayer = (layers: Layer[]): Layer | Layer[] => {
  return flattenDeep(
    layers.map((layer) => {
      if (layer.children) {
        return parseLayer(layer.children);
      }
      return layer;
    })
  );
};

const parsedLayers: Layer[] = flattenDeep([parseLayer(psd.children || [])]);

Note: From here, if we want, we can write PNGs of each of the layers:

const outPath = (name: string) => path.resolve(__dirname, 'out', `${name}.png`);

parsedLayers.map((layer, i) => {
  if (layer.canvas) {
    fs.writeFileSync(
      outPath(layer.name || `layer-${i}`),
      (layer.canvas as any).toBuffer() // I'm lazy with types here, but this should work
    );
  }
});

From here, you can take a look at the types or parse a PSD file yourself to see some of the other things you can achieve.

Writing a PSD file

With ag-psd, writing a file is as simple as reading one too! In the following example, I’m going to take some of the layers from the PSD I had previously imported and insert a PNG that I had locally.

import { Canvas, createCanvas, loadImage } from 'canvas';
import { Layer, Psd, readPsd, writePsdBuffer } from 'ag-psd';

const getLayer = (name: string) => {
  return parsedLayers.find((l) => l.name === name);
};

const myImage = await loadImage(path.resolve(__dirname, 'my-image.png'));
const myCanvas = createCanvas(myImage.width, myImage.height);
const ctx = myCanvas.getContext('2d');
ctx.drawImage(myImage, 0, 0);

const children: Layer[] = [
  getLayer('Background'),
  getLayer('Layer 1'),
  {
    name: 'New Layer',
    canvas: myCanvas as any, // I'm lazy with types here, but this should work
    top: 421,
    left: 254,
    bottom: 1674,
    right: 804,
    // set any other metadata in here
  },
  getLayer('Layer 2'),
];

const canvas = new Canvas(psd.width, psd.height);
const newPsd: Psd = { ...psd, children, canvas: canvas as any };

const buffer = writePsdBuffer(newPsd);
fs.writeFileSync(path.resolve(__dirname, 'out', 'out.psd'), buffer);

Wrapping Up: Parsing PSD Files with ag-psd

As you can see from the above examples, ag-psd is simple and straightforward, and has been a pleasure to work with. It handles many common use cases, but there are some limitations. If there’s something that’s missing, feel free to consider contributing!