This tool is a Photoshop Script Library that generates multiple files from one PSD template, changing text or adding different images for each output file.
The main usecases for this script are:
- Form letters;
- Named certificates & diplomas;
- Wedding invitations.
This script requires minimal knowledge of JavaScript to use it. If you are completely new to automation and programming, you can read this handy automation guide.
- Setting text layer contents.
- Scaling down text to fit in the given boundaries.
- Centering text on a specific layer.
- Replacing parts of text layer.
- Setting a variant of text specified in the layer (e.g. Mr|Mrs).
- Adding an image (e.g. company logo) to a file.
- Scaling and centering of the image.
- A fluent syntax that separates data from transformation description.
Copy the Processor.jsxinc file to a directory and create a file named
example.jsx in the same directory:
#include "Processor.jsxinc"
processor = new Processor()
processor.transform(function(t) {
t.putTextOnLayer('TEXT')
.centerToBoundaryLayer('BOUNDARY')
})
processor.generate({
'paths': {
'output': 'C:/My/Path/To/A/Directory',
},
'putTextOnLayer.TEXT': {
'einstein': 'Albert Einstein',
'planck': 'Max Planck',
},
})Now, create a PSD file named example.psd, which contains two layers:
- A layer named
TEXT(with or without contents); - A shape layer named
BOUNDARY(to center the text layer inside it).
Most likely the BOUNDARY layer will be an invisible rectangle on top of a larger background.
Otherwise a longer text could connect with edges of the layer without any padding inside.
Open example.psd in Photoshop and run example.jsx via File → Scripts → Browse…
In C:/My/Path/To/A/Directory, the processor will create three directories,
JPG, PDF and PSD with two files inside each (XXX being the specific extension):
example-einstein.XXXexample-planck.XXX
For each entry in your data, the processor duplicates the active document, runs every
transform on the copy, and saves it. Output files are named
<output>/<FORMAT>/<template>-<suffix>.<ext>, where <template> is the open document's
name and <suffix> identifies the entry (see Suffixes).
A transform points at a layer by name (searched recursively, case-insensitive) and fills it with the matching value from your data. If there is no data for an entry, text layers are hidden automatically.
var processor = new Processor()| Method | Description |
|---|---|
processor.transform(callback) |
Define the transforms. callback receives a builder t (see below). |
processor.data(data) |
Set the data object (paths, suffixes, per-layer values). |
processor.setOutput(output) |
Set the output formats without running. |
processor.generate(data) |
Shortcut for data(data) then run. Uses the output set earlier (or the default). |
processor.output(output) |
Shortcut for setOutput(output) then run. Use this last when data is already set. |
processor.getDefaultConfiguration(type) |
Returns the default save/export options for a format ('PSD', 'PDF', 'PNG', 'JPG'). |
A run is triggered by either generate(...) or output(...) — whichever you call last.
Inside transform(function(t) { ... }) you add transforms. Each returns the transform
object so you can chain the positioning & sizing methods.
| Builder method | Data key | What it does |
|---|---|---|
t.putTextOnLayer('NAME') |
putTextOnLayer.NAME |
Replaces the text layer's contents with the data value. |
t.replaceTextOnLayer('NAME') |
replaceTextOnLayer.NAME |
Replaces {0}, {1}… and {key} placeholders inside the layer's existing text. |
t.putTextVariationOnLayer('NAME') |
putTextVariationOnLayer.NAME |
Picks one of several choices; the data value is the index of the chosen variant. |
t.addImageLayer('NAME') |
addImageLayer.NAME |
Opens an image file and places it above the named layer. The data value is a filename (relative to paths.input). |
Text transforms hide their layer when an entry has no value. Image transforms simply skip the entry if the file is missing.
t.putTextOnLayer('TEXT')'putTextOnLayer.TEXT': ['Albert Einstein', 'Max Planck']Use \r inside a string for line breaks.
Keeps the layer text and only substitutes placeholders. {0}, {1}… read from a
positional array; {key} reads from a named sub-key.
t.replaceTextOnLayer('FMT') // layer text e.g. "Certificate no. {0} for {name}"'replaceTextOnLayer.FMT': [ ['001'], ['002'] ], // fills {0}
'replaceTextOnLayer.FMT.name': [ 'Ada', 'Alan' ], // fills {name}The data value is the index of the variant to use. Choices come from one of three sources:
// 1. From a `.choices` data key (default)
t.putTextVariationOnLayer('VARIANT')
'putTextVariationOnLayer.VARIANT.choices': ['she', 'he'],
'putTextVariationOnLayer.VARIANT': [0, 1], // -> "she", "he"
// 2. From the layer's own text, split by a separator
t.putTextVariationOnLayer('VARIANT_B').separatedBy('|') // layer text: "she|he"
'putTextVariationOnLayer.VARIANT_B': [1, 0],
// 3. From a fixed list in the script
t.putTextVariationOnLayer('VARIANT_C').fromChoices(['Mr', 'Mrs'])t.addImageLayer('IMAGE')'paths': { 'input': '/path/to/images', 'output': '...' },
'addImageLayer.IMAGE': { 'ar': 'logo.png' }, // filename resolved against paths.inputImages are scaled down to fit and centered on the document by default. Add
.centerToBoundaryLayer('NAME') to fit/center them inside a specific layer instead.
These chain onto any transform and control where the result is placed and how it is scaled.
| Method | Effect |
|---|---|
.withBoundary(x, y) |
Fit inside fixed dimensions, e.g. .withBoundary('30mm', '10mm'). |
.withBoundaryLayer('NAME') |
Fit inside the bounds of the named layer. |
.centerToLayer('NAME') |
Center the result on the named layer (no resizing). |
.centerToBoundaryLayer('NAME') |
Both: fit inside and center on the named layer. |
Text layers shrink their font (down to half the original size) to fit; if they still don't fit, the text is left at its original size and coloured red to flag the overflow. Images are scaled proportionally and never enlarged beyond 100%.
processor.data({
'paths': {
'input': '/path/to/images', // base folder for addImageLayer filenames
'output': '/path/to/output', // FORMAT subfolders are created here
},
'suffixes': ['einstein', 'planck'], // optional, see below
// one key per transform: '<type>.<LAYER>'
'putTextOnLayer.TEXT': ['Albert Einstein', 'Max Planck'],
})The suffix is the part of the output filename after the template name. The list of suffixes (and thus how many files are produced) is resolved in this order:
- An explicit
'suffixes': [...]array — used as-is for filenames. - Object data (
{ 'einstein': '…', 'planck': '…' }) — the object's keys become suffixes, and each transform looks up its value by suffix. - Array data (
['…', '…']) — values are matched by position, and suffixes default to numeric indexes (0,1, …) unlesssuffixesis provided.
You can mix arrays and objects across transforms; just keep their lengths/keys aligned with your suffixes.
processor.output(['PSD', 'JPG', { format: 'PDF', merged: true }])Each entry can be:
- A format string:
'PSD','PDF','PNG','JPG'/'JPEG'— saved with sensible defaults. - A config object:
{ format: 'JPG', quality: 90, merged: true }— any extra keys override the default save/export options.merged: trueflattens visible layers before saving (e.g. a flat PDF alongside a layered PSD). - A genuine Photoshop options object (
new PhotoshopSaveOptions(),new ExportOptionsSaveForWeb(), …) for full control.
If you call processor.output() with no argument, it defaults to
['PSD', 'JPG', { format: 'PDF', merged: true }].
A global layers object is available for ad-hoc layer manipulation (also used internally):
| Method | Description |
|---|---|
layers.find(ref, name) |
Recursively find a layer by name (case-insensitive). |
layers.select(ref, name) |
Make the named layer active. |
layers.toggle(ref, name, visible) |
Show (1) or hide (0) the named layer. |
layers.remove(ref, name) |
Delete the named layer. |
- Treat your data as a table. With arrays, each transform key is a column and each index
is a row — one output file per row. Keep every array (and
suffixes) the same length and aligned, and group related rows with blank lines so the script reads like a spreadsheet. - Put meaningful information in the suffix. The suffix becomes the file name, so encode
whatever you'll need to sort, find or act on later — language, variant, even print quantity
(e.g.
510a-eval-en-1szt). No separate tracking sheet required. - Leave a value blank to get a blank. An empty string (
'') clears a text layer, while an omitted/undefinedentry hides it — so you can generate filled files and spare blank ones in the same run. - Image layers accept PSD files, not just PNG/JPG — handy for reusing one template with several swappable badges or logos.
The script was tested on Adobe CS6 on Mac. If you find some issues using it, create an issue in the issue tracker on this repository.