@imgly/psd-exporter
v0.1.0
Published
Export CE.SDK scenes as Adobe Photoshop PSD files
Readme
@imgly/psd-exporter
Export CE.SDK scenes as Adobe Photoshop PSD files.
Run the exporter in Node.js. Its native dependencies require a server runtime; browser applications need a separately implemented Node.js endpoint.
Installation
npm install @imgly/[email protected] @cesdk/[email protected]Quick Start
import CreativeEngine from '@cesdk/node';
import { exportPsd } from '@imgly/psd-exporter';
import fs from 'fs/promises';
const engine = await CreativeEngine.init({ license: 'YOUR_LICENSE' });
await engine.scene.loadFromArchiveURL('https://example.com/scene.zip');
const { psds, logger } = await exportPsd(engine); // one PSD per page
for (const [index, { psd }] of psds.entries()) {
await fs.writeFile(`./page-${index + 1}.psd`, psd);
}
for (const message of logger.getMessages()) {
console.warn(`${message.type}: ${message.code} — ${message.message}`);
}
engine.dispose();API
exportPsd(engine)
Returns PSDExportResult:
interface PSDExportResult {
psds: { pageName: string; psd: Uint8Array }[]; // one .psd per page
logger: Logger; // typed diagnostics; nothing is dropped silently
}Preserved metadata round trips — x-adobe-psd metadata
Importing a PSD through importPsdWithMetadata from @imgly/psd-importer
stashes PSD data CE.SDK cannot represent — layer
effects, masks, unmapped blend modes, ICC/document data, hidden layers —
as x-adobe-psd/... block metadata. The exporter restores it on the way
out, so import → export approaches the original file. Engine state wins for mapped
properties; preserved metadata wins for unmapped ones.
Graphics remain pixel layers. Their current fill appearance wins over an
original PSD solid-color fill, including color edits, disabled fills and
replacement gradients; the original editable fill is not restored over those
pixels. Imported block identities retain the metadata association through
reordering, additional layers and scene serialization, including same-named
layers. Legacy scenes without those identities restore only unambiguous
matches. Original effects, document resources and skipped layers do not attach
to unrelated new pages with matching names or counts. Legacy scenes retain
document data on their sole page; with multiple pages, only one matching
original snapshot may claim it. Ambiguous page matches and copied identities
are skipped with a METADATA_UNRESTORABLE warning. Modern import provenance survives
removing all original layers or pages, so fresh matching artwork cannot inherit
their metadata. Empty and hidden-only original pages retain document data.
For Node.js, install @imgly/psd-importer and pngjs and provide the PNG
encoder as the third argument:
import {
importPsdWithMetadata,
createPNGJSEncodeBufferToPNG,
clearPreserved
} from '@imgly/psd-importer';
import { PNG } from 'pngjs';
const pngEncoder = createPNGJSEncodeBufferToPNG(PNG);
await importPsdWithMetadata(engine, psdBytes, pngEncoder);The sidecar is opt-out: pass { metadata: false } as the fourth argument
to import without it
(the saved .imgly then carries no x-adobe-psd data), or strip it from
an existing scene with clearPreserved(engine, sceneBlock) before
saving. This clears the document sidecar; clearing the imported blocks as
well removes their identity markers.
Imported layer metadata follows its stable identity through renaming,
reordering and saved scenes. Copied or ambiguous identities are refused with
METADATA_UNRESTORABLE so metadata cannot move to the wrong layer.
await importPsdWithMetadata(engine, psdBytes, pngEncoder, { metadata: false });What is exported
Layers (raster + native text with per-run fonts/weights/styles/colors/ sizes, tracking, leading, caps, alignment, basic underline and strikethrough), groups, all 27 CE.SDK blend modes (1:1 PSD names), opacity, visibility, rotation/flip, the page fill as a Background layer, and a flattened composite preview. RGB 8-bit output; CMYK/spot appearance is preserved visually, spot identity only via metadata. Safe spot fills carry a pixel fingerprint: re-import restores their editable identity only while their raster is unchanged. Photoshop recoloring, erasing or new masks retain the edited raster.
Blocks marked excluded from export, and their descendants, are omitted before layer creation or font loading. Excluded page, stack or scene ancestors keep the one-file-per-page result with empty content matching the native preview. Included hidden layers retain their hidden state.
New PSD documents record the scene DPI as their print resolution. Imported
documents retain their preserved original resolution metadata.
The bottom Background layer retains page fill and visible page stroke paint,
including strokes on pages with disabled fills. Isolated child rasters exclude
this page paint so the border is not repeated over content.
Named spot page fills retain their name, tint and base RGB definition in the
Background layer's XMP carrier. Re-import restores the spot definition and
preserves the composed Background pixels; METADATA_UNRESTORABLE with reason
spot-color-fill-not-swappable reports that the raster remains in place.
After conversion at the scene DPI, each page dimension must be at least one
pixel. Invalid dimensions or positive dimensions below this minimum cause
a RangeError before rendering. Fractional page dimensions round to the
nearest pixel, with both
the background and composite rasters matching the PSD header.
Positive subpixel blocks and transformed strokes retain their full native
paint as pixel layers. Their bounds follow the isolated page render on the
same pixel grid as the composite, including antialiased edges and miter
corners. Subpixel spot fills retain their raster on re-import. Genuinely
zero-size graphic frames remain empty.
Fractional pixel origins or dimensions and transformed graphics also use
page-space paint, retaining edge coverage instead of rounding their placement.
Spot fills affected by this geometry or fractional-page normalization retain
their raster on re-import. Nonvisual children, including soundtracks, are
skipped with UNSUPPORTED_BLOCK_TYPE diagnostics.
Graphics with an enabled positive-width stroke retain their exact page-space
paint in every stroke position, including fractional widths, dashes and caps.
Layer bounds follow the rendered coverage instead of rounded stroke overhang.
Imported XMP packets are preserved. Spot-color carriers merge into compatible RDF metadata; unsupported packets retain their original content with a diagnostic. Preserved group effects restore only when a native group matches all its original imported leaves. Other group matches emit a diagnostic without reconstructing the original hierarchy.
Editable text leading includes the font's line gap when the engine's
features/fontLineGapEnabled setting is enabled, preserving baseline spacing.
Editable paragraph text also retains its first baseline in Photoshop. The
Photoshop layout box compensates for the engine's first-line ascender versus
Photoshop's cap-height placement, including mixed first-line fonts and sizes
and enabled font line gaps; the separate imported frame remains unchanged.
Photoshop paragraph boxes retain the exact native frame width, so recomposing
editable text preserves its wrapping and horizontal alignment instead of
joining lines inside a wider box.
Otherwise editable text that actually clips overflow lines retains only its
visible paint as a pixel layer with TEXT_OVERFLOW_RASTERIZED. Text that fits
its frame, or has overflow clipping disabled, keeps its supported editable form.
Editable text with overflow clipping disabled also retains all visible glyphs
in its cached layer pixels, so choosing to keep the rendering in Photoshop
does not crop lines to the smaller text frame. Its editable frame stays unchanged.
The Photoshop layout box also reserves enough local leading for every visible
unclipped line, preserving those lines when Photoshop updates the text.
Its width, first baseline and separate imported frame remain unchanged.
When formatting requires a pixel layer and overflow clipping is disabled,
the layer retains the complete visible page-space paint beyond its text frame,
including glyphs, decorations, strokes and shadows. Its existing formatting
diagnostic explains the unsupported formatting.
Drop shadows remain in the raster appearance, including pixels outside the
object frame. Text with an enabled drop shadow is exported as a pixel layer
with a TEXT_SHADOW_RASTERIZED warning to preserve its appearance; that text
has an additional hidden editable alternative. Spot-color restoration retains these pixels instead
of replacing a shadowed rectangle with a plain fill.
Page rotation does not move exported text: layer coordinates use the same
local page frame as the native page render. Page mirrors remain visible, and
export restores the original page transform even when rendering fails.
Filled editable text stores glyph fill in its cached pixels and its current
stroke only as FrFX, preventing Photoshop from outlining a baked stroke again.
Text with a disabled fill and a visible stroke keeps its complete rendered
paint as pixels with TEXT_STROKE_RASTERIZED, alongside a hidden editable alternative.
Editable text retains its current fill enablement, per-range RGBA colors and
single solid layer stroke with Round corners in every stroke position.
Custom dash patterns (including those overriding Solid), non-solid stroke styles,
Miter or Bevel corners, or a conflict with disabled, multiple or otherwise
unmapped preserved PSD strokes, rasterize the text with a
TEXT_STROKE_RASTERIZED warning. This retains its current paint and unmapped
original effects.
An offset with no custom pattern has no dash effect; cleared or all-zero
patterns with Solid remain editable.
Block and per-range text backgrounds, including backgrounds with no padding,
and center or bottom vertical alignment retain their exact
paint and glyph placement as pixel layers, with TEXT_BACKGROUND_RASTERIZED
or TEXT_VERTICAL_ALIGNMENT_RASTERIZED warnings. Hidden alternatives retain their editable text content. Top-aligned text with supported formatting keeps its native form.
Lowercase and titlecase retain their visible Unicode content as editable
text with a TEXT_CASE_NORMALIZED warning when native layout confirms that
the materialized content matches. The original case style becomes part of
the text string. If internal font fallback or incomplete layout prevents an
exact match, TEXT_CASE_RASTERIZED retains the visible paint as a pixel layer.
Uppercase keeps Photoshop's native all-caps style.
Native text combines block letter spacing and each range's additional kerning as Photoshop tracking in thousandths of an em. The engine's enabled or disabled font kerning is preserved independently as Photoshop's automatic kerning flag. Different range values retain their text styles and UTF-16 boundaries, including after case normalization.
Multiline text with incompatible font metrics or sizes retains its exact
baseline spacing as pixels with TEXT_LEADING_RASTERIZED when editable PSD
leading cannot reproduce its layout. Matching metrics and single-line text
keep their supported editable formatting. Literal tabs retain their actual
positions as pixels with TEXT_TABS_RASTERIZED, including custom intervals,
mixed sizes and the legacy tab layout of older supported engines.
Superscript and subscript retain their glyph scale and baseline as pixel
layers with TEXT_SCRIPT_RASTERIZED. Overlines, non-solid decorations and
underlines with a custom color, thickness, offset or skip-ink setting retain
their appearance with TEXT_DECORATION_RASTERIZED. Paragraph gaps, different
per-paragraph line heights or alignments, direction-dependent Auto alignment
and list markers retain their layout with
TEXT_PARAGRAPH_RASTERIZED. Hidden alternatives retain editable content and
supported formatting. Default underline, strikethrough and paragraph formatting remain
native text.
Text on an active curved path retains its complete glyph placement and
orientation as a pixel layer with TEXT_PATH_RASTERIZED. Default or cleared
paths and paragraph overrides matching the block alignment remain editable.
The first release targets the current stable CE.SDK 1.83.0. Both engine peers require version 1.83.0 or newer.
Editing difficult text in Photoshop
When a text feature cannot currently be retained faithfully as native PSD text,
its original layer keeps the exact visible pixels. A second, hidden text layer
named <original name> [Editable text] appears in the Editable text copies
folder. The existing formatting warning reports both layer names in its metadata.
To edit, hide the original appearance layer and reveal its editable alternative. The alternative retains the complete original string (including clipped content), fonts, sizes, colors and supported character formatting. Native superscript, subscript and character outlines are retained as approximations. Path layout, custom decorations, tabs, lists, backgrounds, shadows and parent-group effects may change when Photoshop recomposes or edits this alternative. The two versions are independent; editing the text does not update the raster. Ordinary supported text stays in one visible editable layer.
The visible raster and the document preview use the native page pixel grid, avoiding resampling differences in difficult text. Original layer names, groups, metadata and clipping chains remain intact. New exports identify editable alternatives by unique PSD layer IDs in their namespaced XMP metadata. The PSD importer recognizes these alternatives even with its default hidden-layer settings; their text edits and visibility survive subsequent exports. Configure font asset sources to retain their fonts. An optional copy uses the native default and reports unresolved fonts if those sources are absent. Older or unmarked hidden copies still follow the normal hidden-layer import setting. The carrier identifies only the alternative's role; it does not synchronize the two versions or give the alternative the appearance layer's metadata.
Development
pnpm install && pnpm build
pnpm test # unit + capability + loop + metadata suites
pnpm coverage # branch coverage over src/lib
pnpm test:mutation # Stryker over the pure modulesThe loop and metadata suites need the PSD fixture corpus from imgly/ubq
(packages/psd-importer/src/test/examples, git LFS): set
IMGLY_PSD_FIXTURES_DIR. The Photoshop G6 leg runs via
scripts/check-exports-photoshop.sh on macOS, then
IMGLY_PHOTOSHOP_RENDERS_DIR=<photoshop-renders> pnpm test:photoshop
gates every render (verified: 29/29 open and render within thresholds,
Photoshop 2026). Round-trip structural
parity for opacity/blend needs the paired importer branch
daniel/feat-psd-importer-exporter-parity in imgly/ubq.
License
See LICENSE.md. A valid CE.SDK license is required — get one at img.ly.
