agentsclimarketplace

Indd

Skill mindboard/indesign-extendscript-plugins/skills/indd

Generate InDesign documents with Claude — a Claude Code skill powered by ExtendScript

Install
npx -y skills add mindboard/indesign-extendscript-plugins --skill indd

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Run InDesign ExtendScript (.jsx) on macOS by writing a script to a local file and executing it in Adobe InDesign via AppleScript (osascript). Use when the user wants to automate Adobe InDesign — create/modify documents, place images, build tables, export PDF/PNG/JPG/EPS, read fonts/styles/colors, convert IDML, or otherwise drive InDesign from scripts on a Mac.

SKILL.md

29.6 KB, as published. Nobody here has run it

InDesign ExtendScript Runner

Automate Adobe InDesign on macOS by generating an ExtendScript (.jsx) file and executing it inside a running InDesign application via AppleScript.

InDesign ExtendScript is ECMAScript 3 (old JavaScript) plus an InDesign-specific DOM and Adobe's File/Folder/$ host objects. It is not Node.js or modern JS — no let/const/arrow functions/JSON.parse/template literals. Use var, classic function expressions, and the idioms shown below.

Prerequisites

  • macOS with Adobe InDesign installed (e.g. "Adobe InDesign 2026").
  • The script runs against the currently running InDesign. Many scripts assume an open document via app.activeDocument — make sure InDesign is open with the relevant document, or have the script create one with app.documents.add(...).

How to run a script

  1. Write the ExtendScript to a local file, e.g. /tmp/indesign-script.jsx.
  2. Execute it with osascript, telling InDesign to do script ... language javascript:
osascript -e 'with timeout of 300 seconds
tell application "Adobe InDesign 2026"
do script (POSIX file "/tmp/indesign-script.jsx") language javascript
end tell
end timeout'
  • Replace 2026 with the installed version year the user has (2024, 2025, 2026, …). Default to 2026 unless told otherwise.
  • Always pass an absolute POSIX path to the .jsx file.
  • Always wrap in with timeout of N seconds … end timeout. The default Apple event timeout is ~60s; longer scripts (enumerating app.fonts, placing images, building tables, exporting) fail with "AppleEvent timed out. (-1712)" without it. Verified: a font-enumeration script timed out at the default and succeeded with with timeout.
  • stdout of osascript is the value of the last evaluated expression in the .jsx. That is why the scripts below end with logs.join('\n') — it returns the collected log lines to the shell so you can read the result.

A reusable one-liner (adjust version + path):

JSX=/tmp/indesign-script.jsx VER=2026
osascript -e "with timeout of 300 seconds
tell application \"Adobe InDesign $VER\"
do script (POSIX file \"$JSX\") language javascript
end tell
end timeout"

If the file does not exist, write it first.

Launch InDesign before running the tell block. AppleScript resolves the do script terminology from InDesign's scripting dictionary at compile time, so if the app is not already running the script fails to compile with a misleading "Expected end of line but found "script". (-2741)" error (it is not really a syntax error). Start it first and wait until it registers, then run:

open -a "Adobe InDesign 2026"
# wait until the process is registered before sending the Apple event:
until osascript -e 'tell application "System Events" to (name of processes) contains "Adobe InDesign 2026"' | grep -q true; do sleep 1; done

The first time, the user may need to grant the terminal Automation/Accessibility permission for InDesign. Note also that sending Apple events to InDesign is a cross-application action — if you run inside a command sandbox that blocks Apple events, the osascript call must be allowed to run outside it.

Tip — put the AppleScript in a file, not in -e. Quoting the multi-line tell block through repeated -e flags is fragile; writing it to a .applescript file and running osascript /path/to/run.applescript avoids shell-quoting breakage (verified — the -e form mis-parsed, the file form worked).

Writing ExtendScript: core idioms

Start every script with the target directive and a console.log shim that both prints (to the ExtendScript console) and accumulates output to return to the shell:

//@target InDesign

var logs = [];
var console = {};
console.log = function(v){
    $.writeln(v);   // ExtendScript console
    logs.push(v);   // collected for the return value
};

// ... your work ...

console.log('OK');
logs.join('\n');     // LAST expression -> becomes osascript stdout

Key InDesign / ExtendScript specifics:

  • Measurements are strings with units: '210mm', '10mm', 36 (points for type).
  • geometricBounds is [top, left, bottom, right].
  • Create a document: app.documents.add({ documentPreferences: { pageWidth:'210mm', pageHeight:'297mm', facingPages:false } })
  • Active document: app.activeDocument. Collections (doc.fonts, doc.pages, page.textFrames, doc.paragraphStyles, doc.swatches, …) are accessed by .item(n), [i], or .itemByName(name), and have a .length.
  • Enums are global, e.g. SaveOptions.no, ExportFormat.PDF_TYPE, PNGColorSpaceEnum.RGB, PNGExportRangeEnum.EXPORT_ALL.
  • Export: set app.<format>ExportPreferences.properties = {...} then doc.exportFile(ExportFormat.PNG_FORMAT, File('/abs/out.png')).
  • Close without saving: doc.close(SaveOptions.no).
  • File I/O uses Adobe's File object (not fs):
    var file = new File('/abs/path.txt');
    file.encoding = 'UTF-8';
    if (file.open('w')) { file.write(text); file.close(); }   // write
    if (file.open('r')) { var text = file.read(); file.close(); } // read
    
  • Current script directory: File($.fileName).parent (has .fullName).
  • Parse JSON (no JSON.parse in ES3): eval('(' + jsonString + ');').
  • Iterate with classic for loops; there is no forEach. A common helper:
    var eachItem = function(list, fn){ for(var i=0;i<list.length;i++){ fn(list[i]); } };
    

Non-ASCII / CJK (Japanese etc.) content — important

Multibyte string literals embedded in the .jsx source can get corrupted depending on how InDesign reads the file. The robust rule, verified building a Japanese document:

  1. Keep the .jsx source pure ASCII. Do not paste Japanese (or any non-ASCII) text, or Japanese font names, directly into the script.
  2. Read non-ASCII content from UTF-8 files at runtime with the Adobe File API (file.encoding = 'UTF-8'; file.open('r'); file.read()). Assigning that read-in string to frame.contents / cell.contents renders correctly.
    • Data-driven CJK (tables etc.): do the formatting (joining arrays, currency, labels) in a non-JSX language and write a UTF-8 file of ready-to-render strings, then have the .jsx just read the pairs. E.g. Python turns mentai-ficelle.json into {"rows":[["価格","¥380(税込)"], …]}; the script eval('(' + read + ')')s it and fills cells. Keeps every Japanese byte out of the .jsx source (verified building the spec table).
  3. Reference fonts by their ASCII PostScript name, not by their Japanese display name. Look up the Font object and assign it:
    var findFont = function(ps){
        for (var i=0;i<app.fonts.length;i++){ if(app.fonts[i].postscriptName===ps) return app.fonts[i]; }
        return null;
    };
    paraStyle.appliedFont = findFont('HiraMinProN-W3');   // assign the Font object
    

Japanese fonts confirmed available on this macOS + InDesign 2026 (PostScript names):

UsePostScript nameDisplay name
Mincho (serif) bodyHiraMinProN-W3, HiraMinProN-W6ヒラギノ明朝 ProN
Gothic (sans) headingHiraKakuProN-W3, HiraKakuProN-W6ヒラギノ角ゴ ProN
Adobe-bundled Mincho/GothicKozMinPr6N-Regular, KozGoPr6N-Regular小塚明朝 / 小塚ゴシック Pr6N

To discover fonts, enumerate app.fonts and read .name (family⇥style), .postscriptName, .fontFamily, .fontStyleName (wrap the run in with timeout, the list is large).

Vertical Japanese text (縦書き / vertical-rl)

Vertical writing is set per story through storyPreferences.storyOrientationnot storyDirection. Confusing the two is the single biggest time-sink here; they are different axes:

  • storyOrientation = the writing axis: HorizontalOrVertical.HORIZONTAL (default) vs HorizontalOrVertical.VERTICAL (縦書き). This is what makes text run top-to-bottom. The enum object is HorizontalOrVertical (alias StoryHorizontalOrVertical), members HORIZONTAL / VERTICAL.
  • storyDirection = the bidi reading direction (for Hebrew/Arabic), enum StoryDirectionOptions with only LEFT_TO_RIGHT_DIRECTION, RIGHT_TO_LEFT_DIRECTION, UNKNOWN_DIRECTION. It has no top-to-bottom valueStoryDirectionOptions.TOP_TO_BOTTOM_DIRECTION does not exist and throws "Object does not support the property or method … (55)". For Japanese 縦書き leave storyDirection at its default (LeftToRightDirection); the right-to-left column flow comes for free from vertical orientation.
// make a frame's text vertical — set it on the frame's STORY, not the frame
tf.parentStory.storyPreferences.storyOrientation = HorizontalOrVertical.VERTICAL;
doc.recompose();
  • Set it on the story. Each unthreaded frame is its own story, so set frame.parentStory.storyPreferences.storyOrientation on every frame you want vertical. To make new frames inherit it, flip the document default doc.storyPreferences.storyOrientation before creating them.
  • Assign the named enum, not a bare integer. storyDirection = 2 (or any int) throws "値が無効です … 値 2 を受け取りました". (The value is stored as a 4-char code — storyOrientation reads back 1752134266 = "horz" or 1986359924 = "vert" — but always write the enum, e.g. HorizontalOrVertical.VERTICAL.)
  • vertical-rl falls out for free. In a vertical story a single-column frame (textColumnCount:1) runs each line top→bottom and wraps the next line to the left — i.e. native right-to-left column order; no RTL flag needed. For a band of running vertical text use a wide, short frame: it yields many vertical lines reading right-to-left. A vertical title down the right edge is just a tall, narrow vertical frame at the right of the page. (Verified building pokemon_b.indd: vertical title on the right edge; three stacked wide/short bands of right-to-left vertical body text; images on the left.)
  • Headings inside vertical text: prefer psHeading.spanColumnType = SpanColumnTypeOptions.SINGLE_COLUMN so a heading sits inline as a vertical run — SPAN_COLUMNS makes it span the vertical height and reads oddly.
  • Finding the right property/enum (reusable technique). When a guessed name throws (55), reflect instead of guessing again: tf.parentStory.storyPreferences.reflect.properties listed both storyOrientation and storyDirection (revealing they are separate), and SomeEnum.reflect.properties lists an enum's valid members. Reading the current value (the 4-char int) then probing candidate enum names (eval('HorizontalOrVertical')…) confirmed VERTICAL. This reflect-first loop is the fastest way out of "name throws 55".
  • Verify vertical output visually: export PNG and read it back (see below). A center crop is usually enough to confirm right-to-left order and that headings are vertical — note sips -c H W crops centered (its --cropOffset is unreliable) and PIL/ImageMagick may be absent, so don't rely on cropping an exact top-left region.

Layout building blocks (verified)

  • Multi-column text frame:
    tf.textFramePreferences.properties = { textColumnCount: 3, textColumnGutter: '6mm' };
    
  • Heading that spans all columns: set it on the paragraph style:
    paraStyle.spanColumnType = SpanColumnTypeOptions.SPAN_COLUMNS;
    
  • Flow text + apply styles by paragraph: join lines with '\r' (the paragraph separator), assign to tf.contents, then loop tf.parentStory.paragraphs[i] and set .appliedParagraphStyle. Paragraph count equals the number of joined lines, so keep a parallel array of styles. Use empty entries ('') as placeholders for objects you will anchor later — they become empty paragraphs you can target by index.
  • Make a paragraph style's font actually take effect — clear character overrides. Applying text.appliedParagraphStyle = ps does not remove existing character-level formatting, so a font baked onto a run (e.g. from earlier editing or an imported IDML) survives and the style's appliedFont appears ignored. Clear it:
    text.appliedParagraphStyle = ps;
    text.clearOverrides(OverrideType.CHARACTER_ONLY);   // now the style's font wins
    
    Re-apply any intentional local emphasis (bold run, etc.) after clearing (verified: this is what made a Yu-Gothic body style override an old Hiragino run).
  • Anchored inline image (lands in the text flow / column):
    var ip = story.paragraphs[idx].insertionPoints[0];
    var frame = ip.textFrames.add();
    frame.geometricBounds = ['0mm','0mm', h+'mm', w+'mm'];
    frame.contentType = ContentType.graphicType;
    frame.place(File('/abs/image.png'));
    frame.fit(FitOptions.PROPORTIONALLY);     // scale to frame, keep aspect
    frame.fit(FitOptions.FRAME_TO_CONTENT);   // shrink frame to the scaled image
    
    By default the placed frame is anchored as an inline character on the text line. An inline image taller than the line's leading visually overlaps the surrounding body text (the image overflows its small line box). The fix is Above Line anchoring — it puts the image on its own line and reserves vertical space, so text flows cleanly above and below (verified: this removed image/text overlap in the multi-column example). Configure anchoredObjectSettings (all verified):
    var it = frame.anchoredObjectSettings;
    it.anchoredPosition   = AnchorPosition.ABOVE_LINE;
    it.horizontalAlignment = HorizontalAlignment.RIGHT_ALIGN; // LEFT_ALIGN | CENTER_ALIGN | RIGHT_ALIGN
    it.anchorSpaceAbove   = 3;                                // space above, in the document's units
    
    Watch the exact property names — these are easy to get wrong and throw "Object does not support the property or method … (55)":
    • It is horizontalAlignment, not alignment.
    • It is anchorSpaceAbove, not spaceAbove.
    • Other useful AnchoredObjectSetting members: verticalAlignment, horizontalReferencePoint, anchorXoffset / anchorYoffset, anchorPoint, pinPosition, lockPosition. To discover the supported members of any object at runtime, reflect on it: obj.reflect.properties (each has a .name).
  • Inline table at an insertion point: ip.tables.add({headerRowCount:1, bodyRowCount:n, columnCount:c, width:'85mm'}). A Table has no .texts — style cells individually via table.cells.item(k).texts[0].properties = {...} (or loop table.cells). Set insets with cell.properties = { topInset:1.5, ... }.
  • Standalone (placed) table — to drop a table into a specific spot (e.g. a card's bottom strip), make a text frame and add the table at its insertion point:
    var tf = page.textFrames.add();
    tf.geometricBounds = ['17mm','1.5mm','29mm','38.5mm'];
    tf.textFramePreferences.insetSpacing = [0,0,0,0];
    var tbl = tf.insertionPoints[0].tables.add({ bodyRowCount:n, columnCount:2, headerRowCount:0 });
    
  • Per-column width: tbl.columns.item(0).width = '9mm'; (set each; they should sum to the table width). Fill cells with tbl.rows.item(i).cells.item(j).contents = '...';.
  • Merge cells to span (e.g. a full-width footnote row): row.cells.item(0).merge(row.cells.item(1)); — after the merge the row has one cell; set its .contents after merging.
  • Cell shading + hairline grid (all verified property names): fill a cell with cell.fillColor = swatch; and set borders per edge — cell.topEdgeStrokeWeight = 0.25; cell.topEdgeStrokeColor = lineSwatch; (likewise bottomEdge…, leftEdge…, rightEdge…). Tighten padding with cell.leftInset/rightInset/topInset/bottomInset.
  • Auto-fit a fixed-size table (sibling of the text-frame fit loop): a table can overflow its frame and individual cells can overset. Shrink the whole table's font until neither happens, recomposing each step:
    function anyOverset(){
        if (tf.overflows) return true;
        for (var k=0;k<tbl.cells.length;k++){ if (tbl.cells.item(k).overflows) return true; }
        return false;
    }
    var sz = 3.4;                          // applyFont(sz) sets every cell's pointSize/leading
    applyFont(sz); doc.recompose();
    while (anyOverset() && sz > 1.8){ sz -= 0.1; applyFont(sz); doc.recompose(); }
    
    (Verified: packed a 7-row spec/allergen table into a 40×30mm card's bottom strip this way, settling at ~3.4pt with no overset.)
  • Measurement units: to avoid ambiguity, set doc.viewPreferences.horizontalMeasurementUnits = MeasurementUnits.POINTS (and vertical), use point numbers for type (pointSize, leading, spaceAfter) and unit strings ('15mm') for geometry / table sizes.
  • pointSize/leading get silently scaled when the ruler is mm — pin the script unit. app.scriptPreferences.measurementUnit defaults to AUTO_VALUE, which follows the document's ruler. If the ruler is millimeters and you assign a bare number like style.pointSize = 7, InDesign interprets it through that unit and stores a different size (observed ≈0.71×, e.g. 7 → 4.96pt, 5.5 → 3.90pt). The text then renders far smaller than intended. Fix (verified): set
    app.scriptPreferences.measurementUnit = MeasurementUnits.POINTS;
    
    once at the top, so numeric pointSize/leading are taken as real points — even while the ruler stays mm for geometry. This was the difference between type coming out at 2.4pt vs the intended ~4–5pt.
  • Fit text to its frame by sizing on overflows feedback. To fill a fixed frame with a balanced margin (instead of guessing a size), grow the point size until the frame just overflows, then back off a notch — calling doc.recompose() after each change so overflows reflects the new layout:
    var sz = style.pointSize, g = 0;
    doc.recompose();
    while (!tf.overflows && sz < 60 && g++ < 400){ sz += 0.25; style.pointSize = sz; style.leading = sz*1.4; doc.recompose(); }
    while ( tf.overflows && sz > 2  && g++ < 400){ sz -= 0.25; style.pointSize = sz; style.leading = sz*1.4; doc.recompose(); }
    sz -= 0.5; style.pointSize = sz; style.leading = sz*1.4;   // small bottom margin
    
  • Check for hidden overset text after filling a fixed frame: tf.overflows (boolean). If true, shrink the font, enlarge the frame, or thread to more frames/pages.

Export formats (third arg of ExportFormat)

TargetConstant
PDFExportFormat.PDF_TYPE
IDMLExportFormat.INDESIGN_MARKUP
PNG / JPG / EPSExportFormat.PNG_FORMAT / JPG / EPS_TYPE

Save a native .indd with doc.save(new File('/abs/out.indd')). Verified end-to-end: a 3-column A4-landscape Japanese document with an inline image and a CSV-driven table was built and exported to .indd, .idml, and .pdf in one run (see scripts/20_build-multicolumn-doc-from-md-and-csv.jsx).

Detecting & fixing overset (hidden) text — verified

Overset text is content that does not fit its container and so is invisible in the output (a table cell or text frame just looks blank). ExtendScript can detect it:

  • Text frame / story: textFrame.overflows (Boolean).
  • Table cell: cell.overflows (Boolean). Scan a whole table with table.cells.everyItem() (loop table.cells.item(i)), or one column via table.columns.item(c).cells. A cell's name is "column:row".
  • Gotcha: an overset cell's .contents returns "" even though text is there — so detect with .overflows, never by testing for empty content.

Why a cell oversets: an unbreakable Latin token (a filename like chimchar.png, a URL, a long English word) cannot wrap, so if it is wider than the column it spills out and the cell renders blank. CJK (Japanese/Chinese) text breaks between characters, so it wraps and grows the row height instead — it rarely oversets. So overset in a mixed table is usually a too-narrow Latin column.

Fix by widening the column — borrow width from a "safe donor" (the widest column that does not itself overset after shrinking), keeping the total width constant so the table still fits its frame column. Call doc.recompose() after every width change — otherwise .overflows reports the stale pre-change layout and the logic misbehaves (this was the difference between a diverging and a converging fix):

var colHasOverset = function(t,c){
    var cs = t.columns.item(c).cells;
    for (var i=0;i<cs.length;i++){ if (cs.item(i).overflows) return true; }
    return false;
};
var tableHasOverset = function(t){
    for (var c=0;c<t.columnCount;c++){ if (colHasOverset(t,c)) return true; }
    return false;
};
var step=2, floor=16, guard=0;
while (tableHasOverset(table) && guard<200){
    guard++;
    var widenC=-1;
    for (var c=0;c<table.columnCount;c++){ if (colHasOverset(table,c)){ widenC=c; break; } }
    if (widenC<0) break;
    var donor=-1, donorW=-1;                       // widest column that survives a shrink
    for (var c=0;c<table.columnCount;c++){
        if (c===widenC || colHasOverset(table,c)) continue;
        var col=table.columns.item(c), orig=col.width;
        if (orig-step < floor) continue;
        col.width = orig-step; doc.recompose();    // tentative
        var bad = colHasOverset(table,c);
        col.width = orig;      doc.recompose();     // revert
        if (!bad && orig>donorW){ donorW=orig; donor=c; }
    }
    if (donor<0) break;                            // nothing safe to borrow from
    table.columns.item(widenC).width += step;
    table.columns.item(donor).width  -= step;
    doc.recompose();
}

Verified: this cleared an overset filename cell in the pokemon table by widening the image column ~2pt (borrowed from the numeric column) while leaving the English-name column intact. Alternative fixes if no width can be borrowed: shrink the cell/table font until overflows is false, enlarge the whole frame, or allow the token to break.

Session control recipes (verified on InDesign 2026 / 21.3)

These cover the essential "drive a live InDesign session" operations. All were tested against a running Adobe InDesign 2026 on macOS.

  • Is there an active document? Accessing app.activeDocument when nothing is open throws, so always gate on the count first:
    if (app.documents.length > 0) {
        var doc = app.activeDocument;      // safe
    }
    
  • Create a document if none is open:
    var doc = (app.documents.length > 0)
        ? app.activeDocument
        : app.documents.add({ documentPreferences: { pageWidth:'210mm', pageHeight:'297mm', facingPages:false } });
    
  • Enumerate every open document (when multiple files are open):
    for (var i=0; i<app.documents.length; i++) {
        var d = app.documents.item(i);     // index 0 is the frontmost/active one
        console.log(d.name + ' saved=' + d.saved);
    }
    
  • Open a specific InDesign file: var doc = app.open(new File('/abs/file.indd')); Works for .indd and .idml. The opened doc becomes the active document.
  • A crashed/aborted run leaves the doc open and modified in memory — reopen returns the dirty copy. When an earlier script dies mid-way, the document stays open with its half-built state; a later app.open(samePath) hands back that in-memory dirty copy, not the clean file on disk. During iterative builds this silently feeds you partial state (observed: a re-harvest read 1 paragraph instead of 15 because a prior run had already cleared the page before crashing). Start every rebuild by discarding open copies, then open fresh:
    while (app.documents.length>0){ app.documents[0].close(SaveOptions.NO); }
    var doc = app.open(new File('/abs/base.indd'));
    
  • Save to a specific path: doc.save(new File('/abs/out.indd'));
  • Close: doc.close(SaveOptions.NO); (or SaveOptions.YES to write changes first).

Letting Claude verify its own work (important)

Export the result to an image/PDF, then read it back:

var doc = app.activeDocument;
doc.exportFile(ExportFormat.PDF_TYPE, new File('/abs/out.pdf'));   // PDF
// or PNG (set app.pngExportPreferences.properties first, see 02_export-doc-as-png.jsx)
doc.exportFile(ExportFormat.PNG_FORMAT, new File('/abs/out.png'));
  • Prefer PNG/JPG for visual verification. Claude's file reader renders PNG/JPG natively, so it can see the rendered page and confirm the result. Verified: an exported PNG was read back and its on-page text was legible.
  • PDF needs a renderer. Reading a PDF page requires poppler (pdftoppm); if it is not installed, export PNG/JPG instead, or brew install poppler.

Recipes / examples

This skill bundles 50+ working example scripts under scripts/, indexed in examples-index.json (each entry has title, description, filepath). When a task matches one of these, read the closest example and adapt it rather than writing from scratch — they encode the correct InDesign DOM idioms.

Workflow:

  1. Skim examples-index.json for a matching title/description.
  2. Read the referenced scripts/*.jsx.
  3. Adapt paths, sizes, and content for the user's task.
  4. Write the result to a temp .jsx and run it with the osascript command above.
  5. Report the returned stdout (the logs.join('\n') output) back to the user.

Categories of bundled examples (prefix = rough grouping):

  • 00 — inspect / utilities: console logging, current dir, active document filename, selection info, list fonts (installed or used in doc), close all documents.
  • 01 — documents: create a document with a text frame.
  • 02 — export: export active/Hello-World doc as PDF (PDF/X-1a), PNG, JPG, EPS.
  • 03 — tables: create a table, create + find a table, build a price-list table from JSON.
  • 04 — images / PDF placement: place image at page center, into a rectangle, with a graphic frame, as an inline graphic; place a PDF.
  • 05 — IDML: list IDML files, merge IDML files, convert IDML → INDD.
  • 06 — file I/O: save/read/parse JSON, save/read/parse TSV text (UTF-8).
  • 07 — traversal / linked images: traverse all pages & page items, replace an image.
  • 08 — vector drawing: graphic line, polygon, Bézier curves, fractal triangle, fractal ginkgo leaf.
  • 09 — styles & type: read paragraph/character styles, apply paragraph (and character) styles to "Hello World" text.
  • 10 — layers: find an existing layer or create one, assign page items to it.
  • 11 — links: check linked-image metadata (paths, status).
  • 12 — text frames: rounded corners, center-align contents, inline text frame, inspect text object properties.
  • 13 — pages: create a document with multiple pages.
  • 14 — conditional text: read all condition names, delete all conditions.
  • 15 — colors / swatches: read all color names, create a custom swatch.

Notes & gotchas

  • The shell receives only the last evaluated expression. Don't rely on $.writeln alone for results you need programmatically — push to logs and end with logs.join('\n').
  • ExtendScript errors surface in osascript stderr / the return string; if output is empty or an error, check that a document is open and the InDesign version matches.
  • Wrap a build script in try/catch and return the error — on an uncaught throw osascript returns nothing useful. A failure surfaces only as a generic execution error with no logs and no checkpoint output, so you can't see how far it got. Wrap the body and log the message with its line number, then end as usual:
    try { /* ... build ... */ }
    catch(err){ console.log('ERROR: ' + err + ' @line ' + err.line); }
    logs.join('\n');
    
    err.line alone usually pinpoints the failing statement (this is how the storyDirection and 'mm'-arithmetic errors below were located).
  • Don't do arithmetic on 'NNmm' measurement strings. '190mm' is just a string, so '190mm' + 0.5 yields the invalid '190mm0.5', which InDesign rejects with "要求された 種類に関する利用可能なデータはありません / there is no data available for the requested type". Compute in plain numbers and append the unit last: (190 + 0.5) + 'mm'. A handy helper is function mm(n){ return n + 'mm'; } with all bounds math done numerically.
  • Rectangle has no cornerRadius property (throws 55); corner rounding lives on the per-corner option/radius family (topLeftCornerOption + topLeftCornerRadius, …), or just omit it.
  • Use absolute paths everywhere (script file, image/PDF inputs, export outputs).
  • Do not write InDesign export/output files into /tmp. On macOS /tmp is a symlink to /private/tmp, and InDesign's File export fails with "Folder … not found" (error 48). The .jsx script itself can live in /tmp (it's read by osascript, not by InDesign's File API), but export targets must be a real directory such as the user's project folder or ~/Desktop. Verified: /tmp export failed; exporting to a real path succeeded.
  • This is macOS-only; it depends on AppleScript (osascript) and the InDesign AppleScript do script command.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.