Library / SDK
Agamnentzar/ag-psd avatar
Agamnentzar/ag-psd

ag-psd reads and writes Photoshop files without Photoshop

Javascript library for reading and writing PSD files

734 stars93 forksTypeScriptNOASSERTION

At a glance

What is it?
ag-psd is a JavaScript library for reading and writing PSD documents, and the honest measure of it is the long limitations list in its readme, which tells you exactly which parts of the format it does not implement.
Who is it for?
ag-psd suits a tool that has to inspect or generate Photoshop files as part of a larger pipeline, especially a batch job that needs to read layer structure without launching Photoshop, and it suits a browser application that has to show a layered document.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Reading and writing are two functions with a document model between them

The library is a JavaScript library for reading and writing PSD files, that is Photoshop Document files, and it was implemented according to the official Adobe documentation, the fileformat.info description of the format, and a lot of trial and error. The shape of the API is a document object in the middle. The read side takes an ArrayBuffer, a typed array or a Node.js Buffer and returns a Psd object.

ts
// read PSD from ArrayBuffer, typed array or Node.js Buffer object
export function readPsd(buffer: Buffer | ArrayBuffer | BufferLike, options?: ReadOptions): Psd;

The write side takes that object and gives you bytes back, in three flavours, one writing to an ArrayBuffer, one writing to a Uint8Array which avoids some memory allocation, and one writing to a Node.js Buffer object.

ts
// write PSD to ArrayBuffer
export function writePsd(psd: Psd, options?: WriteOptions): ArrayBuffer;

// write PSD to Uint8Array (avoids some memory allocation)
export function writePsdUint8Array(psd: Psd, options?: WriteOptions): Uint8Array;

// write PSD to Node.js Buffer object
export function writePsdBuffer(psd: Psd, options?: WriteOptions): Buffer;

The structure of the Psd object is documented separately in the project's PSD readme, and the presence of the separate accessor functions is the other half of the design, since when you read with the useRawData option the pixel data stays as raw data and you pull it out yourself with a dedicated getter.

Skipping the pixels is the difference between fast and slow

The read options decide how much work the library does. The Node.js example reads only the document structure by passing skipLayerImageData, skipCompositeImageData and skipThumbnail as true, and then reads the document structure and image data by calling readPsd with no options at all. That difference is not a minor optimisation, because decoding the pixel data of every layer of a large document is most of the cost of opening it, and a tool that only needs to know the layer names should not pay for it. When you do want the pixels, the library exposes a set of accessors for the raw and canvas forms of each one, covering the layer image data, the layer mask image data, the real mask image data, the layer canvas, the mask canvas, the real mask canvas, and the composite image data and composite canvas. The canvas and the raw pixel data are two views of the same thing, and which one you get depends on what your environment can provide.

Node.js needs a canvas, and the import that provides it is explicit

Reading image data and thumbnails in Node.js needs node-canvas, and the library handles that through a side-effect import rather than a dependency. The example starts with the canvas initializer and a comment explaining that it is only needed for reading image data and thumbnails.

javascript
import * as fs from 'fs';
import 'ag-psd/initialize-canvas'; // only needed for reading image data and thumbnails
import { readPsd } from 'ag-psd';

const buffer = fs.readFileSync('my-file.psd');

The comment on the canvas field explains the type friction. By default the canvas field type is HTMLCanvasElement, so it needs to be cast to any or to the node-canvas Canvas type in order to call the toBuffer method, and the example writes out a layer as a PNG that way. The writing side is symmetric, with the same initializer import needed only for writing image data and thumbnails, and a document object that is just width, height and a list of children with names. That minimal object is enough to produce a valid file, which is the part that surprises people who expect a Photoshop document model to require more.

In the browser the same code runs, with a bundle for script tags

The browser example fetches the file with an XMLHttpRequest, sets the response type to arraybuffer, and reads it on the load event, then appends the layer's canvas to the document body. Writing in the browser uses the saveAs function from FileSaver.js, turning the ArrayBuffer into a Blob with the application/octet-stream type before handing it over. For pages that are not using a module bundler, there is a bundle build that can be loaded with a plain script tag, which exposes the library as a global named agPsd.

html
<script src="node_modules/ag-psd/dist/bundle.js"></script>
<script>
  var readPsd = agPsd.readPsd;
  // rest the same as above
</script>

There is also a Web Worker path, and it exists for a specific reason. The browser has to support OffscreenCanvas and the bitmaprenderer context, and the worker reads the file, converts the canvas to an ImageBitmap and posts both the document and the bitmap back, marking the bitmap for transfer so it is moved rather than copied. The comment in the worker code explains that canvases cannot be posted back from workers, so the canvas is deleted from the document object before posting and the main script converts the bitmap back into a canvas. If your work is heavy, this path keeps the decoding off the main thread.

The limitations list is the real specification

The readme is unusually honest, and a reader should treat the limitations list as the contract. Reading does not support Indexed, CMYK, Multichannel, Duotone and LAB color modes, and all supported color modes are converted to RGB mode when reading. Writing supports no color modes other than RGB. There is no support for 16 bits per channel, for the Large Document Format, for color palettes, for animations, for some metadata fields, for 3D effects, or for some new features from recent Photoshop versions, and not all filters on smart layers are supported. Patterns are limited to document-level pattern blocks for raw or RLE-compressed RGB and Grayscale patterns, with no support for zip-compressed patterns, which are used by most real Photoshop files, nor for Indexed or 16-bit patterns, and the Pattern Overlay layer effect is only read and written as a name or id reference. The implementation was put together from the official documentation, the fileformat.info page and a lot of trial and error, and that origin story is the reason to read those caveats before designing around the library.

Text layers are the sharpest edge in the format

Text layers get their own entry in the limitations, and the sub-points are the ones to plan around. Writing a text layer with vertical orientation may result in a broken PSD file. There is no support for writing or reading predefined Paragraph Styles or Character Styles. And the library does not redraw bitmap data for the text layer, so files with updated or written text layers will result in a warning prompt when opening the file in Photoshop, which the readme links to a longer section about. The general statement behind all of this is the one that matters most for planning. The library does not handle redrawing layer and composite image data by itself when blending options, vector data or text options are changed, and any updates to image data have to be done by the user or by opening and re-saving the file in Photoshop. So the library is a good editor of document structure and a poor substitute for Photoshop's renderer.

Editorial conclusion

ag-psd suits a tool that has to inspect or generate Photoshop files as part of a larger pipeline, especially a batch job that needs to read layer structure without launching Photoshop, and it suits a browser application that has to show a layered document. It is the wrong tool for round-tripping a design file that a designer will open again in Photoshop, because the library does not redraw layer and composite image data when blending options, vector data or text options change, so the file has to be opened and re-saved in Photoshop for the pixels to catch up. It is also the wrong choice for documents outside RGB. Before using it, read the limitations list end to end, and if your files are CMYK, 16 bits per channel or use zipped patterns, you need to know that before you build on it. The last push was on 2026-10-07 and the package version is 31.0.3.

Frequently asked questions

Which color modes can ag-psd read and write?

Reading does not support Indexed, CMYK, Multichannel, Duotone and LAB color modes, and all supported color modes are converted to RGB when reading. Writing supports no color modes other than RGB, and there is no support for 16 bits per channel, the Large Document Format, color palettes or animations.

How do I read a PSD file in Node.js with ag-psd?

Read the file into a Buffer and pass it to readPsd. To read image data and thumbnails you also need node-canvas, imported as a side effect with import 'ag-psd/initialize-canvas'. Passing skipLayerImageData, skipCompositeImageData and skipThumbnail reads only the document structure, which is much faster on large files.

Can ag-psd write a PSD file from scratch?

Yes. Build a Psd object with a width, a height and a list of children, then call writePsd, writePsdUint8Array or writePsdBuffer and save the result. The README's minimal example creates one layer named Layer #1 and writes the file.

Official sources

  1. Agamnentzar/ag-psd on GitHub
  2. Issues
  3. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/agamnentzar-ag-psd.svg)](https://hysenlabs.com/projects/agamnentzar-ag-psd)

Was this useful?