InDesign Data Merge Skill
Orchestrates the complete InDesign data merge pipeline. The naive path — calling
document_merge_data_layout directly — often fails because the template must have its
data-merge fields linked to the correct data columns before merging. This skill ensures
that linkage is in place first, including automatic placeholder creation when the template
has none, and PDF-to-INDD conversion when the user only has a PDF layout.
Pipeline at a glance:
Tool Reference
⛔ MANDATORY GATES
Two rules are non-negotiable and override all efficiency instincts.
Gate 1 — PDF template: analyse layout before converting
Never call convert_pdf_to_indd on a user-supplied PDF without first inspecting its
page layout structure. A multi-page PDF (e.g. a prior merged output) produces a multi-page INDD
which breaks the merge. Always distil it to a single-layout condensed PDF first.
Gate 2 — Mapping: show and wait before creating placeholders
Never call prepare_indd_merge_template in the same turn you generate or display the mapping JSON.
Show the mapping summary + raw JSON, ask for approval, and end the turn. Only call
prepare_indd_merge_template after the user explicitly approves in a later turn.
Workflow
Step 0 — Initialize Adobe Tools
Call adobe_mandatory_init once, before any other Adobe tool.
Step 1a — Convert PDF Template to INDD (skip if already .indd)
When to run: The user's template is a PDF (or any non-INDD layout). If you already have a
usable .indd URL, skip directly to Step 1b.
Sub-step 1 — Inspect all pages
Record the user-supplied PDF URL as originalPdfUrl.
Call asset_inline_preview on the PDF (or asset_preview_file as fallback). Record totalPages.
If totalPages == 1: set templatePdfUrl = originalPdfUrl and skip to Sub-step 5.
Sub-step 2 — Compare page layouts visually
Look at each preview image. Ask: "If I removed all text and image content and compared only the skeleton — frame positions, structural regions — would this page match another page?"
Group pages by layout skeleton into layoutGroups:
Pick templatePages as a single representative page:
- If
layoutGroupshas exactly 1 entry:templatePages = [first page in that group]. - If
layoutGroupshas >1 entry: ask the user which layout group to use (or default to the group with the most pages) and settemplatePagesto one page from that group.
Tell the user:
"Your PDF has {totalPages} pages. I found {N} unique layout(s). I'll use page {templatePages[0]} as the template. (If you need multiple layouts, we can run separate merges per layout.)"
Sub-step 3 — Extract only the template pages
Download the PDF from {originalPdfUrl} and extract only page(s) {templatePages} (1-indexed) to template-condensed.pdf using PyMuPDF (fitz). Report the absolute file path as intermediatePdfLocalPath.
Record the path as intermediatePdfLocalPath.
Sub-step 4 — Upload the condensed PDF
Record the resulting presigned URL as templatePdfUrl.
Sub-step 5 — Convert to INDD
Use the first entry in extractedDocumentPresignedUrls as inddUrl. If that field is
missing, re-run with embedLinks: true. Do not attempt to download or unzip the output ZIP.
Step 1b — Prerequisite Check
Confirm all inputs are accessible (attached files, URLs from conversation, or prior tool calls):
.inddURL — if missing, prompt the user to upload their template- Data file — CSV, TSV, or other delimited text; if missing, ask the user
- Image assets (optional) — any images referenced by the data file (e.g. photo columns)
If any file is a local attachment without a URL, upload it first:
Record inddUrl and dataUrl. Proceed only when both are confirmed.
Collect image assets (if the data file references images)
Parse the header row of dataUrl. If any column values look like image filenames or paths
(common with @-prefixed image fields in InDesign templates), collect those images:
- For each unique image filename referenced in the data, obtain a presigned URL — either from
the user's supplied URLs, or by uploading local files via
asset_initialize_file_upload+asset_finalize_file_upload. - Build
additionalImageFiles: a map of{ "filename.jpg": "<presignedUrl>", ... }covering every unique image referenced across all rows.
Record additionalImageFiles (or {} if no image columns are present).
Step 2 — Export IDML and Inspect for Placeholders
Export IDML
Record the presigned idmlUrl from the response.
If export_idml fails (missing execute URL), tell the user you cannot proceed with
automatic placeholder creation and ask whether they want to continue manually.
Inspect for existing placeholders
Fetch and unzip the IDML. Build docAnalysis capturing every candidate merge target:
- Text frames:
uid, bounds, current text content, any existing<<fieldName>>or<AppliedDataMergeField>tokens - Image frames:
uid, bounds, aspect ratio, any@-prefixed image field names
Extract idmlPlaceholders: string[] — unique field names stripped of <</>> wrappers.
If placeholders are found: skip Phase 3 and go directly to Step 4 using inddUrl.
If no placeholders are found: proceed to Step 3.
Step 3 — Create Placeholders (only if Step 2 found none)
Step 3a — Read data headers
Parse the header row of dataUrl. Treat the file as delimited text; infer the delimiter if
ambiguous. Preserve exact column name strings as dataColumns: string[].
Step 3b — Generate mapping JSON
Call generate_indd_mapping_prompt — this returns a prompt string only; it does not call an LLM:
Run the returned prompt through an LLM requiring JSON-only output. Record as mappingJson.
Step 3b.1 — Validate / filter the mapping
Call generate_indd_mapping_prompt again with the same inputs, adding:
If the response includes filteredMappings (hallucinated UIDs removed), use it as the working
mapping. Fix any errors and re-validate until isValid: true.
After the first successful validation, record:
originalMappingJson— the first validated mapping (usefilteredMappingsif returned)originalIdmlUrl— theidmlUrlfrom Step 2 (never overwrite with a post-edit version)originalInddUrl— theinddUrlfrom Step 1b (never overwrite withmappedInddUrl)
Step 3b.2 — ⛔ Show mapping and END TURN (Gate 2)
Your response for this step must contain exactly:
- A human-readable summary of text/image mappings (placeholder name → CSV column, frame UID)
- The raw mapping JSON in a code block
- This confirmation question as the final line:
"Please confirm this mapping (or tell me what to change). I'll apply it to create placeholders in your
.inddand then run the merge."
After that line — no more tool calls, no further narration. End the turn.
If the user requests changes, enter a modification loop:
- Apply only the user's requested deltas on top of
originalMappingJson— never use an intermediate draft ormappedInddUrlas the base - Re-run Step 3b.1 validation with
tier: "modification", passingoriginalIdmlUrlandoriginalMappingJsoninmodificationContext - Re-present the updated mapping and ask for confirmation again — do not call
prepare_indd_merge_templateuntil the user approves
Step 3c — Apply placeholder mapping to INDD
Only callable after user explicitly approves in a later turn.
Record the first presigned URL from the response as mappedInddUrl.
Step 4 — Run the Data Merge
Return download links to the user:
"Data merge complete — {N} record(s) rendered. Here are your download links:"
Decision Tree
Error Handling
Admin Notes (IDCS Environment)
The IDML export and smart-mapping tools require these environment variables (configure them in your deployment environment):
