Search Docs
Loading...
Skip to content

Photobook Editor

A complete photobook editor with a layouts library, photo auto-fill, live design validation, and export to print-ready PDF/X-4.

Photobook Editor starter kit showing a photobook spread with the layouts library and validation sidebar

15 mins
estimated time
Download
GitHub

Pre-requisites#

This guide assumes basic familiarity with React and TypeScript.

  • Node.js v22+ with npm – Download
  • Supported browsers – Chrome 114+, Edge 114+, Firefox 115+, Safari 15.6+
    See Browser Support for the full list

Get Started#

Start fresh with a standalone Photobook Editor project. This creates a complete, ready-to-run JavaScript application with a start screen, an editor, and an export pipeline.

Step 1: Clone the Repository#

Terminal
git clone https://github.com/imgly/starterkit-photobook-editor-react-web.git
cd starterkit-photobook-editor-react-web
git clone https://github.com/imgly/starterkit-photobook-editor-react-web.git
cd starterkit-photobook-editor-react-web

The kit is split into a web app, an optional export server, and the wire types they share:

src/client/ # The web app (Vite + React)
src/server/ # The optional export server (Express + @cesdk/node-native)
src/shared/ # Wire types shared by the app and the server

Inside src/client/src, the demo application lives in app/ and the reusable editor logic lives in imgly/:

src/client/src/
├── index.tsx # Application entry point
├── app/ # Demo application shell
└── imgly/
├── index.ts # Editor initialization function
├── photobook.ts # Sizes, styles, and the scene each starts from
├── photos.ts # Upload asset source for the reader's photos
├── autofill.ts # Places photos and captions into placeholders
├── placeholders.ts # What counts as unfilled placeholder content
├── photo-stash.ts # Holds photos that a smaller layout cannot fit
├── preview-mode.ts # Page-spread preview
├── block-utils.ts # Engine helpers shared across the kit
├── constants.ts # Shared identifiers and panel helpers
├── plugins/layouts/
│ └── layout.ts # Layouts asset source and layout switching
├── validation/
│ ├── validation.ts # The print validation checks
│ ├── types.ts # Validation result types
│ └── utils.ts # Validation helpers
└── config/
├── plugin.ts # Main configuration plugin
├── actions.ts # Export/import actions
├── features.ts # Feature toggles
├── i18n.ts # Translations
├── settings.ts # Engine settings
├── keyboard/ # Keyboard shortcut catalog
└── ui/ # UI customization

Step 2: Install Dependencies#

Install the required packages:

Terminal
npm install
npm install

Step 3: Download Assets#

CE.SDK requires engine assets (fonts, icons, UI elements) to function. These must be served as static files from your project’s public/ directory.

Terminal
curl -O https://cdn.img.ly/packages/imgly/cesdk-js/1.83.0/imgly-assets.zip
unzip imgly-assets.zip -d public/
rm imgly-assets.zip
curl -O https://cdn.img.ly/packages/imgly/cesdk-js/1.83.0/imgly-assets.zip
unzip imgly-assets.zip -d public/
rm imgly-assets.zip
src/client/src/index.tsx
const config: Configuration = {
// ...
baseURL: '/assets'
// ...
};

This kit also loads its own demo assets — the layout scenes, the style scenes, and the example photos — from the IMG.LY CDN. To host them yourself, copy the kit’s asset bundle to your own server and point VITE_DEMO_ASSETS_BASE_URL at it:

.env
VITE_DEMO_ASSETS_BASE_URL=https://your-server.example.com/assets

The default is set in src/client/src/imgly/demo-assets.ts.

Step 4: Run the Development Server#

Terminal
npm run dev
npm run dev

Open http://localhost:5173 in your browser. Everything, including PDF export, runs in the browser.

Step 5 (Optional): Connect a Node Server#

While exporting in the browser works, you might want to offload that to a server for a long book or a low-powered device. The kit can hand rendering to a Node server instead. Only rendering moves; the PDF/X conversion always stays in the browser.

Run the web app and the export server together:

Terminal
npm run dev:server
npm run dev:server

This starts the server on port 8080, proxied at /api, and sets VITE_USE_SERVER=true for you. Plain npm run dev never calls the server, so an export failure surfaces instead of silently falling back.

Server Endpoints#

Route What it does
POST /api/export Takes the raw scene archive, checks the size and rate limits, creates a pending job, and answers 202 with a job id straight away
GET /api/export/:id Returns { status, phase, error } for polling. 404 when the id is unknown
GET /api/export/:id/file Sends the finished PDF and then drops the job, so an interrupted download can be retried. 409 while the job has no result yet
DELETE /api/export/:id Discards a job the client abandoned. Always answers 204, and only removes a job belonging to the calling client
GET /api/health Returns { status, activeJobs }. Reports degraded once any export has timed out

Code structure:

src/server/index.ts # Composition root: wires the exporter in, listens on 8080, graceful shutdown
src/server/http/routes.ts # The endpoints above, size and rate limits, job wiring, JSON error handler
src/server/imgly/export.ts # Renders the archive with @cesdk/node-native
src/server/jobs/store.ts # In-memory job store, limits, and the expiry sweep
src/shared/export-api.ts # Wire types shared with the client

The bundled server renders with @cesdk/node-native and ships without TLS or authentication, so put a real gateway in front of it before exposing it.

Exporting Print-Ready PDFs#

Export produces a PDF/X-4 file with a FOGRA39 output intent. The conversion always runs in the browser, so the server path returns a plain PDF and the browser converts it.


What the Kit Offers#

  • Layouts — a layouts library filtered to the loaded page format. Applying one keeps the content already on the page.
  • Auto-fill — photos placed into the book automatically, each one matched to the slot that crops it least, and captions filled from your own copy.
  • Validation — a sidebar reporting low-resolution photos, empty slots, and content outside the trim, each one selectable.
  • PDF/X compliance — export to PDF/X-4 with a FOGRA39 output intent, the format commercial printers ask for.

Set Up a Scene#

A photobook starts from a template rather than an empty page. The kit opens the scene, then your app puts content into it:

src/client/src/app/App.tsx
// Any scene URL your asset bundle serves.
const sceneURL = `${DEMO_ASSETS_URL}/style-chic-portrait.imgly`;
await initPhotobookEditor(cesdk, sceneURL);
const photos = await addPhotosToUploadSource(
cesdk.engine,
examplePhotos,
uploadedFiles
);
// Skip both calls to let the reader place every photo by hand.
autoFillPhotobook(cesdk.engine, photos);
autoFillPhotobookTexts(cesdk.engine, exampleTexts);

In short: the scene decides the book, and filling it is your app’s job.

  • initPhotobookEditor takes only a scene and reads the format from it.
  • Adding a style means adding its scene to your asset bundle.
  • addPhotosToUploadSource is where your own photos come in.
  • Both auto-fill helpers touch only blocks the template marked as placeholders.
  • Size and style come from the start screen, configured in src/client/src/app/photobook-options.ts.

Customize#

Layouts#

The layouts library is a CE.SDK asset source. Choosing a layout re-lays out the current page instead of inserting a block, and switching to a layout with fewer slots does not discard photos — the extras move to a per-page stash and flow back when a larger layout returns.

Layouts are authored per page format. Each format has its own directory with its own content.json, scenes, and thumbnails:

layouts/
├── portrait/ # content.json, scenes/, thumbnails/
└── square/ # content.json, scenes/, thumbnails/

To add a layout, add its scene to your asset bundle and its entry to that format’s content.json:

layouts/portrait/content.json
{
"id": "portrait-001",
"label": { "en": "Title" },
"groups": ["Titles"],
"meta": {
"blockType": "acme.layouts",
"thumbUri": "{{base_url}}/portrait/thumbnails/portrait-001.png",
"uri": "{{base_url}}/portrait/scenes/portrait-001.blocks"
}
}

Authoring a Layout Scene#

A layout scene holds one page’s worth of slots, and the kit reads it as the page’s new content. Four rules make it behave once applied:

  1. One page per scene. The kit swaps the page’s children, so a scene with two pages contributes only the first.
  2. Mark every slot as a placeholder. Auto-fill, the layout stash, and the placeholderImage and placeholderText checks all key off the placeholder flag. A slot without it is never filled and never reported.
  3. Author at the format’s page size. Layouts live under the format directory whose pages they were drawn for; a scene drawn at another size arrives scaled.
  4. Give text slots their default copy. The placeholderText check compares the text against what the template shipped, so unedited captions are reported.

Photos reach the pages in the order given, but within a page auto-fill assigns them by shape, not position: it pairs the photos with the slots that crop them least. Slot geometry therefore decides which photo lands where.

Validation#

Validation runs against the book while the reader edits it and reports what would go wrong in print. Eight checks ship with the kit:

Check Fires when
outsidePage An element sits completely outside the page
protrudesFromPage An element extends beyond the page edge
lowResolution A photo is too small for its printed size
bleedMargin An element crosses the bleed margin
duplicateImage The same photo appears more than once
emptyPage A page has no content yet
placeholderImage An image slot has no photo yet
placeholderText Text still shows its placeholder content

The detectors live in src/client/src/imgly/validation.ts and report independently of any UI. Adding a check means adding its detector there, and a name and description for it wherever your interface presents the results — in this kit, src/client/src/app/validation-config.ts.

Export produces a PDF/X-4 file with a FOGRA39 output profile, set in PDFX_OPTIONS in src/client/src/app/print-ready-pdf.ts. The scene renders to a standard PDF first, then convertToPDFX from @imgly/plugin-print-ready-pdfs-web converts it.

Theming and Localization#

Match the editor to your brand by setting a theme in the editor configuration. See Theming for the full set of variables.

The kit’s own strings live in src/client/src/imgly/config/i18n.ts. Add a locale there alongside the editor’s built-in translations. See Localization.


Key Capabilities#

The Photobook Editor covers the path from a folder of photos to a file a printer accepts.

Layouts Library

Layouts Library

Swap a page between authored layouts. Photos and captions carry over, and extras wait in a stash until a larger layout takes them back.

Photo Auto-Fill

Photo Auto-Fill

Place uploaded photos into the book automatically, matching each photo to the slot whose shape crops it least.

Styled Templates

Styled Templates

Start every book from a real scene, authored per page format and style, instead of from a blank canvas.

Design Validation

Design Validation

Catch low-resolution photos, off-page content, and bleed-margin problems while the reader can still fix them.

Print-Ready Export

Print-Ready Export

Export PDF/X-4 with a FOGRA39 output intent, the format commercial printers ask for.

Multiple Book Formats

Multiple Book Formats

Offer portrait and square books at several sizes, each with its own layouts and its own style scenes.


Troubleshooting#

Editor doesn’t load#

  • Check the container element exists: Ensure your container element is in the DOM before calling create()
  • Verify the baseURL: Engine assets must be reachable from the CDN or your self-hosted location
  • Check console errors: Look for CORS or network errors in browser developer tools

Layouts or example photos don’t appear#

  • Check the demo asset location: The layouts, style scenes, and example photos come from a separate bundle. The kit derives their root from the scene URL you pass to initPhotobookEditor, so check that URL, or VITE_DEMO_ASSETS_BASE_URL when you run the kit as-is
  • Check network requests: Open the DevTools Network tab and look for failed requests to the asset host
  • Self-host assets for production: See Serve Assets to host assets on your infrastructure

Layouts don’t fill with photos#

  • Check the layout’s placeholders: Auto-fill only touches blocks the layout scene marked as placeholders. A layout authored without them stays empty. See Placeholders

Export is slow or blocks the editor#

  • Move rendering to the server: A large book competes with the editor for the main thread. Run the export server and set VITE_USE_SERVER=true
  • Wait for content to load: Ensure photos are fully loaded before exporting
  • Check CORS on images: Remote photos must allow cross-origin access

Watermark appears in production#

  • Add your license key: Set VITE_CESDK_LICENSE in your environment, or the license property in your configuration
  • Get a license: Contact us at img.ly/forms/contact-sales/

API Reference#

Most of what you need is four calls: open a scene, give it photos, fill it, and export it for print.

Call What it does
initPhotobookEditor(cesdk, sceneURL) Opens a photobook. Configures the editor, loads the scene, offers the layouts matching the scene’s page format, and locks the cover
addPhotosToUploadSource(engine, remotePhotos, files) Fills the upload source with the photos the book may use, and returns them for auto-fill
autoFillPhotobook(engine, photos) Fills every empty slot, matching each photo to the slot whose shape crops it least
toPrintReadyPDF(pdf, signal?) Converts an exported PDF to print-ready PDF/X-4. From src/client/src/app/print-ready-pdf.ts

Two more worth knowing:

  • VALIDATION_CHECKS in src/client/src/imgly/validation.ts — the print validation checks, each with the rule it applies. This is where you add a check of your own or drop one you do not want.
  • enterPreviewMode(cesdk, options) — switches the editor into the read-only page-spread preview and returns the function that exits it.

Everything else is exported from src/client/src/imgly/index.ts:

Export Kind What it does
initPhotobookEditor(cesdk, sceneURL) function Configures a CE.SDK instance for photobook editing: adds the editor configuration and asset source plugins, loads the scene, registers the layouts library for the format read from the loaded cover, stamps the template’s default texts, and locks the cover
DesignEditorConfig class The editor configuration plugin. Adds the actions, features, translations, settings, keyboard shortcuts, and UI layout. Add it with cesdk.addPlugin(new DesignEditorConfig())
UPLOAD_SOURCE_ID constant The id of the asset source the reader’s photos go into, ly.img.image.upload
revokeUploadedPhotoURLs() function Releases the object URLs created for uploaded files and drops their cached resolutions. Call it when the editor closes
PhotoRef type A photo available for filling the book: uri, width, height
RemotePhotoAsset type A remote example photo with known pixel dimensions, plus id, label, and thumbUri
autoFillPhotobook(engine, photos) function Fills every placeholder image slot in the book, matching each photo to the slot whose shape crops it least
autoFillPhotobookTexts(engine, texts) function Writes caption copy into the placeholder text blocks, page by page
PhotobookTextContent type The copy an auto-filled book is written with: { captions: string[] }
enterPreviewMode(cesdk, options) function Switches the editor into a read-only page-spread preview with its own navigation, and returns the function that exits it. Preview writes go into a scratch history, so the mode leaves no undo steps behind
LAYOUTS_SOURCE_ID constant The id of the layouts asset source, ly.img.layouts
LayoutsAssetSourcePlugin class Registers the layouts library for one page format. Applying a layout re-lays out the current page and carries its photos and text into the new slots
getLayoutFormat(size) function Returns the page format a size is authored at, portrait or square
getSceneLayoutFormat(engine) function Returns the page format of the loaded scene, read from its cover page
assetsBaseURLOf(sceneURL) function Returns the asset root a scene hangs off: its parent directory
addPhotosToUploadSource(engine, remotePhotos, files) function Adds photos to the upload source and returns them, the reader’s files first
getStyleSceneURL(assetsBaseURL, format, styleId) function Returns the scene URL a size and style combination starts from
PhotobookSize type A page format in millimeters: { width, height }
PhotobookStyleId type 'playful' | 'chic'
PhotoDistributionId type 'autoFill' | 'byHand'

Next Steps#