Design templates — document templates and PDF rendering
DD7 lets an integration discover the same published template that the Editor picker uses. The REST v1 routes and MCP tools return a portable field contract and expose no database IDs or mutable full-document body.
REST v1
Section titled “REST v1”Use a token with catalog:read scope and workspace WsAssetsRead permission:
GET /api/v1/design-templatesGET /api/v1/design-templates/{tpl_id}GET /api/v1/design-templates/{tpl_id}/versionsGET /api/v1/design-templates/{tpl_id}/versions/{revision}/contracttpl_id is an opaque tpl_ prefixed GUID. The list contains published templates in the caller’s
workspace, with name, status, page_count and current_revision. The contract endpoint returns
the exact immutable revision, page count, hashes and fields with each placeholder’s code,
type, required and optional default. Use the field codes as keys of the JSON object connected to
the design/template_render data port. For the two-page proposal example, the field keys are
customer and headline; a named headline port overrides the value in data.
data accepts several objects: connect them to data_0, data_1, … and they are merged in index order,
a later object overriding an earlier one’s keys (a connection to the unindexed data is read first).
Use it to keep AI-written copy and verified facts apart — for example the copy produced by an LLM on
data_0 and the offer maintained by the business on data_1 — instead of splitting the facts into
one field input each. Precedence overall: named field inputs, then assets, then the merged data.
{ "customer": "Acme Studio", "headline": "Proposal for Acme Studio" }A json field read by a Repeat region (a list printed one row per item) also lists
item_fields: the keys each item provides, with code, name, type and required. The data
shape does not change; for a price list whose row reads name and price:
{ "items": [ { "name": "Espresso", "price": "1.20" }, { "name": "Tea", "price": "1.80" } ] }With missing_policy fail_required (the default), an item without a required key stops the
render with DESIGN_PLACEHOLDER_REQUIRED and the item path, e.g. items[1].name.
The /versions route lists published revisions newest first with a current marker and portable
tplv_ IDs. Choose a revision from this list before requesting its field contract. To author a
workflow via REST v1, MCP, or an agent, set design/template_render.parameters.template_id to
the discovered tpl_ ID and optionally set template_revision to a published revision number.
If you omit template_revision, Madoo fixes the currently published revision during workflow
validation and creation. The definition readback displays the revision it fixed. The server
resolves the internal document and version IDs and derives the typed field input ports; the caller
never needs to read the Editor’s numeric IDs or supply templateContract.
{"id":"render_proposal","type":"design/template_render","parameters":{ "template_id":"tpl_205ed88f82fd2ce57f3587b579705094", "output":"both"}}Two other nodes fill a template, selected the same way (parameters.template_id, optional
template_revision, never documentId). Their input ports are the template’s field codes, typed
like the fields; connect only the fields to fill, the others keep the value authored in the template.
document/pdfrenders one PDF. For one documentdesign/template_renderis generally preferable (structureddata, pinned revision, page images, layout report, PDF/X-4).aggregate/pdfturns an iteration into one PDF: placed after the per-item branch of an enumerator, it fills the template once per item and appends each item’s pages, with optionalintroPdf/outroPdfpages (a cover rendered bydocument/pdfordesign/template_render). Use it for a catalog, a price list or one sheet per product;utility/merge_pdfonly joins a fixed list of existing PDFs.
Unlike the renderer these two nodes are not pinned: without template_revision they render the
template’s current draft when the workflow runs, so later draft edits change the output. Set
template_revision for a reproducible workflow.
{"id":"catalog","type":"aggregate/pdf","parameters":{ "template_id":"tpl_205ed88f82fd2ce57f3587b579705094","template_revision":3,"outputName":"catalog"}}A different workspace cannot resolve the ID; the lookup is workspace-scoped in the Application service and SQL query. Draft and archived documents are not exposed as selectable published templates, even if their ID and revision are known. Version contracts remain immutable after a later template edit while the template stays published.
New revisions in a saved workflow
Section titled “New revisions in a saved workflow”To check every design/template_render node in a workflow without changing it, call:
GET /api/v1/workflows/{wf_id}/template-revisionsThis requires workflows:read scope plus workflow/template read permissions. The response gives
the saved and latest revision, changed field codes and page counts, a compatibility reason, and
the workflow definition ETag. latest_revision is null with an explanation when the
source document is not currently Published; an editable draft is never treated as the
latest published revision. get_workflow_template_revisions in MCP,
workflow.review_template_revisions in AI Agent, and
review_workflow_template_revisions in AI Assistant call the same Application review.
Checking is read-only. To save a different pin, the author reviews the change and updates the
workflow definition in an authoring surface; a Run never saves it.
Migration 480 adds template_version_policy to the renderer node. Its default pinned uses the
saved revision. latest_compatible lets a new execution of the current workflow version use
a newer published revision only when the existing field codes/types, required defaults, page
count and repeated regions pass a conservative check. For a repeated region only data-facing
changes need review: a removed or retyped item key, a new or newly required item key, a different
list, overflow rule or lower Max items, or less room while the rule is to stop. Restyling, moving
or resizing the row does not. Otherwise it keeps the saved pin and
records the reason. The effective definition, including the choice or fallback, is written to
a unique immutable execution snapshot and referenced by the WorkflowTask. Readback and retry
use that snapshot. Historical version requests and released App bindings keep their frozen
workflow version. AI Agent fixes the workflow version observed during planning; if it
is still current when the new run starts, the node’s opt-in policy can resolve a
newer template inside that run snapshot. If that workflow version became historical,
the agent keeps its observed pin. This policy does not change the workflow’s saved
template pin.
The existing REST PUT /api/v1/workflows/{wf_id}/definition with If-Match and its MCP/Agent
draft authoring equivalents can persist a reviewed template_revision. The targeted command
below updates selected pins under a workflow definition lock. REST, MCP, AI Agent and Editor
authoring use that path; Assistant presents an explicit confirmation card in the Editor.
Live qualification of these surfaces remains DD7 work.
At workflow publish, selecting the current revision or requesting an upgrade requires the template
to be Published. A workflow that already holds an immutable template_revision pin can keep using
that revision after the source template returns to draft or is archived.
The internal workflow publisher checks that an existing pin still carries the same document/version
identity and content/contract hashes. Supplying only legacy numeric document and version IDs after
archive or revert cannot make a new template selection.
Draft inspection uses the separate design-templates:read scope and workspace WsAssetsRead
permission:
GET /api/v1/design-templates/{tpl_id}/draftGET /api/v1/design-templates/{tpl_id}/draft/publish-readinessGET /api/v1/design-templates/{tpl_id}/draft/outlineThe draft response gives its exact draft_etag (also in the ETag header), revision and status.
The readiness response returns can_publish, blocking checks and suggested actions from the same
server assessment used by the Editor. A later edit or publish must use the observed ETag as its
precondition. This read surface returns no mutable full-document body.
The outline lists page IDs, element IDs/names/types and named sample sets with their field codes,
without exposing the editable JSON body.
Use its page IDs when adding a local element. The first page can also be selected by omitting
page_id.
list_design_templates returns the same list contract. get_design_template reads one template by
view, each with the same projection as its REST v1 route:
view |
REST v1 equivalent | Scope |
|---|---|---|
contract (default; optional revision, else the current one) |
GET …/versions/{revision}/contract |
catalog:read |
versions |
GET …/versions |
catalog:read |
draft |
GET …/draft |
design-templates:read |
outline |
GET …/draft/outline |
design-templates:read |
content (optional revision, else the draft) |
GET …/draft/content, GET …/versions/{revision}/content |
design-templates:read |
readiness |
GET …/draft/publish-readiness |
design-templates:read |
palette (needs element_id) |
GET …/draft/elements/{element_id}/palette |
design-templates:read |
All views also require WsAssetsRead. The contract carries the same portable field codes and hashes as
REST v1, so an agent can wire design/template_render without guessing the JSON shape. The write scope
design-templates:write also satisfies draft read, but it is not automatically granted to existing
automation clients or default MCP consent; request it explicitly when authoring templates.
Create a draft
Section titled “Create a draft”Use design-templates:write and workspace WsAssetsManage. A stable 8–255 character
Idempotency-Key is required. Creation makes a native blank draft, never a published template:
POST /api/v1/design-templatesIdempotency-Key: proposal-template-001Content-Type: application/json
{ "name": "Proposal template", "description": "Two-page proposal", "tags": ["proposal"] }The response is 201 with the portable tpl_ ID inside draft.template.id, its draft_etag
and an ETag header. Retrying with the same key and details returns that same template and
Idempotency-Replayed: true; reusing the key with different details returns
409 idempotency_conflict. The workspace and key are persisted by migration 478. A draft whose
metadata was saved just before an interrupted initial content upload is completed on retry.
MCP create_design_template_draft takes the same fields and required idempotency_key, calls
the same Application service, and records a write audit with the template ID and hashed key.
Build a multipage brochure semantically
Section titled “Build a multipage brochure semantically”The draft outline now reports each page’s one-based number, stable ID, name, format, orientation, dimensions, background color and element count. Integrations can build a brochure without downloading or replacing the DesignDocument JSON.
Every page mutation requires the latest If-Match and a fresh Idempotency-Key:
POST /api/v1/design-templates/{tpl_id}/draft/pagesPOST /api/v1/design-templates/{tpl_id}/draft/pages/{page_id}/duplicatePUT /api/v1/design-templates/{tpl_id}/draft/pages/{page_id}POST /api/v1/design-templates/{tpl_id}/draft/pages/{page_id}/moveDELETE /api/v1/design-templates/{tpl_id}/draft/pages/{page_id}For example, this inserts an A4 landscape page after the cover:
{ "name": "Services", "format": "a4", "orientation": "landscape", "background_color": "#ffffff", "after_page_id": "<cover page GUID>"}POST and PUT also accept optional background_paint for a full-page vector
gradient. The shape is the same safe SVG paint object used by vector fills. For
example, add this beside background_color:
"background_paint": { "type": "linear_gradient", "units": "object_bounding_box", "x1": 0, "y1": 0.5, "x2": 1, "y2": 0.5, "stops": [ { "offset": 0, "color": "#123456", "opacity": 1 }, { "offset": 1, "color": "#abcdef", "opacity": 1 } ]}radial_gradient is also supported. Use 2–32 ordered stops with offsets and
opacities between 0 and 1. background_color stays as the underlay for
translucent stops. Omit background_paint on an update to keep the current
gradient. Set clear_background_paint: true to remove it and return to the solid
background_color. The draft outline returns the active background_paint, and the
same input is available through MCP page tools and AI Agent page capabilities.
Add background_image as an optional PNG, JPEG or WebP layer above the solid
color or gradient. Transparent pixels and uncovered page areas show the base:
"background_image": { "src": "<uploaded image storage path or HTTPS URL>", "fit_mode": "cover", "focal_x": 0.75, "focal_y": 0.4, "scale": 1.25}cover fills the page and crops the image; contain shows the whole image;
stretch fills without preserving aspect ratio. scale multiplies the fitted
size (0.25–4, default 1). focal_x and focal_y align the scaled image within
the page (0 = left/top, 1 = right/bottom, default 0.5). The editor lets the
user drag and scale the image in a page preview. On PUT, omit
background_image to retain it, supply a new image to replace it, or set
clear_background_image: true to remove it independently of the base paint.
The editor uploads a local image through the DesignDocument image
upload endpoint. REST, MCP and AI Agent page commands accept the uploaded path
or an HTTPS image URL. The draft outline includes the active image settings.
Use a Madoo storage path for templates that will be shared: stored background
assets are listed in published revisions and copied with shared templates.
An external HTTPS URL can render, but it must be imported into Madoo storage
before sharing the template.
Named formats are a3, a4, a5, letter, legal, tabloid and square.
Use custom with width and height in PDF points for another size. A document can
contain 1–100 authored pages. The last page cannot be removed. A page referenced as a
repeating region’s continuation prototype is protected until that reference is changed.
Duplicate creates fresh stable IDs for the page, every element, placeholder and guide;
it never aliases the source objects. move accepts a one-based position.
The corresponding MCP tool is edit_design_template_page with operation add, duplicate,
update or move; a page is removed with remove_design_template_item (kind page), the one
destructive template edit.
AI Agent exposes the same operations as design_template.add_page,
duplicate_page, update_page, move_page and remove_page. It reads the compact
outline and chains the returned ETag after every local edit. AI Assistant remains
workflow-scoped and does not edit DesignDocuments.
Reuse a master layout across pages
Section titled “Reuse a master layout across pages”A master page holds a shared header, footer, logo, decoration or background. Create it
from an existing page size, add static elements using its page_id, then assign it
to one or more output pages of exactly that size:
POST /api/v1/design-templates/{tpl_id}/draft/master-pagesPUT /api/v1/design-templates/{tpl_id}/draft/master-pages/{master_page_id}PUT /api/v1/design-templates/{tpl_id}/draft/pages/{page_id}/masterPUT /api/v1/design-templates/{tpl_id}/draft/master-pages/{master_page_id}/automatic-rulePOST /api/v1/design-templates/{tpl_id}/draft/pages/batch-masterDELETE /api/v1/design-templates/{tpl_id}/draft/master-pages/{master_page_id}Create with { "name": "Brochure header", "size_from_page_id": "<page GUID>" }.
The creation response supplies the stable master page_id; use that ID to add text,
shapes or images. Update its name, size or background with the same page-update body
described above; changing its size is rejected while incompatible pages are attached.
Assign with:
{ "master_page_id": "<master GUID>", "use_master_background": true, "master_layer": "underlay"}Use overlay to place master artwork above local elements. Set master_page_id
to null to detach. Deleting a master detaches its pages; it does not delete
their local content. The draft outline exposes master_pages, each output
page’s master assignment and the master element IDs. Up to 32 masters can be
stored in one document; they are not counted as output pages.
Set automatic_rule on a master to none, all, odd, or even when creating it,
or update it later through /automatic-rule with { "automatic_rule": "odd" }.
Only one master can use the same rule at a given page size; odd/even override all.
New pages use automatic assignment. Existing pages retain their previous choice.
The single-page /master body accepts assignment: automatic, manual, or
none. A manual choice requires master_page_id; automatic and none require null.
The latter two choices let a cover or back cover override the rules permanently.
Automatic pages re-evaluate their master after reorder and on the final page
number after repeated content expands.
To change several pages atomically, post { "scope": "all|odd|even|range", "start_page": 2, "end_page": 10, "assignment": "automatic|manual|none", "master_page_id": null, "use_master_background": true, "master_layer": "underlay" } to /batch-master. The inclusive range fields are
needed only for range. A manual master must match every selected page’s size;
otherwise nothing is saved. The draft outline returns automatic_rule on
masters and master_assignment on output pages.
Master artwork can contain static elements and page-number fields. Put data
placeholders, conditions and repeated regions on output pages. For editable
page labels, author a normal text element whose text contains complete tokens,
for example Page {{page}} of {{pages}}. {{page}} is the current page’s number
within its numbering sequence; {{pages}} is the document page count;
{{sequencePages}} is the current sequence count. Values use the active Arabic
or Roman numbering style and resolve after repeated content expands. Literal
words remain editable. A hidden page renders the entire token-bearing text
element empty. Unknown or incomplete tokens stay literal. The editor inserts
localized default words, while the stored text keeps the author’s wording.
The same text works through REST v1, MCP and the AI Agent.
Existing documents can continue to use special_field_code set to
page_number, page_label, document_page_count, sequence_page_count,
page_document_slash, page_document_of, page_sequence_slash, or
page_sequence_of. These field-only elements retain ordinary text styling and can live on a
page or master. Their displayed text is resolved after repeat expansion. The
draft outline exposes each element’s special_field_code. The labelled codes
(page_label, page_document_of, page_sequence_of) always print Italian
words (“Pagina 1 di 3”), whatever the document’s language, so new text should
use the tokens above. REST v1 still accepts special_field_code when adding
text, for older clients; the MCP tool and the AI Agent no longer offer it.
Use PUT /draft/pages/{page_id} with the existing page settings and optional
numbering_start (1–3000) plus numbering_style (arabic, roman_upper,
roman_lower) to restart a sequence on that page. clear_numbering_start
removes the marker. hide_page_number hides page fields on one page without
interrupting its sequence; a common use is an unnumbered cover followed by a
second page starting at 1. The marker follows its page on reorder. The outline
reports these page properties. Document page counts include hidden pages;
sequence counts stop at the next marker. MCP add_design_template_text and
edit_design_template_page (operation update), and AI Agent design_template.add_text and
design_template.update_page, accept the same optional properties.
For a local Development qualification after restarting the stack, run
node docs/demo-workflows/design-template-render/qualify-page-fields-authoring-dev.mjs.
The gate writes through REST and MCP, checks the outline and canonical content,
then extracts the resulting page numbers from the PDF. To exercise the AI Agent
planner as well, set MADOO_PAGE_FIELD_AGENT_GATE=true and run
qualify-master-page-agent-dev.mjs from the same directory.
Master assignments persist through published revisions, sharing/import and PDF
rendering. These commands require the current If-Match draft ETag and a new
Idempotency-Key for each edit.
MCP provides edit_design_template_master_page with operation create, assign, set_rule or
batch_assign, and remove_design_template_item (kind master_page).
AI Agent provides design_template.create_master_page,
design_template.assign_master_page, design_template.set_master_rule,
design_template.batch_assign_master and design_template.remove_master_page.
For a local Development gate after restarting the stack, run
node docs/demo-workflows/design-template-render/qualify-master-page-authoring-dev.mjs.
It tests REST v1 and authenticated MCP writes, then revokes its temporary key and
deletes the draft. install-master-page-brochure-dev.mjs installs the published
brochure template and workflow; qualify-master-page-brochure-dev.mjs runs it
and checks final page numbering in the PDF plus its page images.
The checked-in brochure template demonstrates an all master rule, a fixed
manual cover exception, and an automatic interior page.
qualify-master-page-agent-dev.mjs runs a live AI Agent planner turn and checks
that it authors one shared master and assigns it to two pages; this gate uses
planner credits and deletes its temporary Idea and draft.
Add fixed artwork, vector lines and revise existing elements
Section titled “Add fixed artwork, vector lines and revise existing elements”A fixed logo, photo, background or decorative asset is different from an image placeholder: its source is stored in the template and does not need render data.
POST /api/v1/design-templates/{tpl_id}/draft/static-imagesIf-Match: "dd-draft-r4-..."Idempotency-Key: brochure-brand-art-v1Content-Type: application/json
{ "page_id": "<cover page GUID>", "name": "Brand artwork", "source": "https://cdn.example.com/brand.png", "left": 48, "top": 80, "width": 180, "height": 90, "fit_mode": "fit"}source accepts an HTTPS URL, a Madoo storage path or an image data URI. HTTP URLs,
absolute filesystem paths and control characters are rejected. Use fit, fill or
stretch for sizing. To create an empty image frame, send "source":"" and
"frame_shape":"rectangle", "ellipse", or a curated shape catalog ID. The
frame stays editable while its image source is empty.
A divider, connector or arrow is a native vector line and remains vectorial in PDF:
POST /api/v1/design-templates/{tpl_id}/draft/linesIf-Match: "dd-draft-r5-..."Idempotency-Key: brochure-arrow-v1Content-Type: application/json
{ "page_id": "<page GUID>", "name": "Next step", "x1": 80, "y1": 310, "x2": 420, "y2": 310, "stroke_paint": { "type": "solid", "color": "#2563eb" }, "stroke_width": 3, "stroke_opacity": 0.85, "stroke_line_cap": "round", "stroke_line_join": "round", "stroke_dash_array": [12, 8], "start_marker": "circle", "end_marker": "arrow"}Use an empty stroke_dash_array for a solid line. Common presets are [12, 8]
for dashed and [1, 6] for dotted. Caps are butt, round, or square;
endpoint decorations are none, arrow, or circle.
A rectangle starts with four sharp corners. Each corner can then be rounded independently:
POST /api/v1/design-templates/{tpl_id}/draft/rectanglesIf-Match: "dd-draft-r6-..."Idempotency-Key: brochure-panel-v1Content-Type: application/json
{ "page_id": "<page GUID>", "name": "Feature panel", "left": 80, "top": 120, "width": 280, "height": 160, "corner_radius_top_left": 0, "corner_radius_top_right": 24, "corner_radius_bottom_right": 48, "corner_radius_bottom_left": 12, "fill_paint": { "type": "solid", "color": "#f8fafc" }, "stroke_paint": { "type": "solid", "color": "#334155" }, "stroke_width": 2}Corner radii use the document’s point coordinate system and must be between zero and half
the shorter side. A zero remains a sharp corner. The same four optional fields are accepted
by the element PATCH, so integrations can change one corner without replacing the others.
MCP exposes add_design_template_rectangle; AI Agent exposes
design_template.add_rectangle. The Editor’s live-corner widgets write the same fields.
Built-in shape library
Section titled “Built-in shape library”Madoo exposes a versioned catalog of built-in vector shapes. Read the catalog before authoring so the integration does not have to guess IDs or embed its own geometry:
GET /api/v1/design-template-shapesThe response groups shapes into basic, arrows, stars_badges, callouts, and
flowchart. Each item includes a stable shape_id, display name, search keywords,
standard SVG path_data, a normalized view box, fill rule, provenance, and license.
Catalog version 1.0.0 contains 25 Madoo-original shapes.
Add a selected shape to the draft with the latest ETag and a fresh retry key:
POST /api/v1/design-templates/{tpl_id}/draft/shapesIf-Match: "dd-draft-r6-..."Idempotency-Key: brochure-feature-star-v1Content-Type: application/json
{ "page_id": "<page GUID>", "shape_id": "star_5", "left": 80, "top": 120, "width": 120, "height": 120, "fill_paint": { "type": "solid", "color": "#f97316" }, "stroke_paint": { "type": "solid", "color": "#7c2d12" }, "stroke_width": 2}The operation copies the standard SVG path into a normal PathElement. The saved
document, its revisions, shared imports, and PDFs therefore remain independent of later
catalog changes. MCP exposes list_design_template_shapes and
add_design_template_shape; AI Agent exposes design_template.list_shapes and
design_template.add_shape. Editor, REST v1, MCP, and AI Agent all use the same catalog
and canonical vector representation.
Custom vector artwork uses the standard SVG d syntax rather than Fabric JSON or a
Madoo-specific path language:
POST /api/v1/design-templates/{tpl_id}/draft/svg-pathsIf-Match: "dd-draft-r6-..."Idempotency-Key: brochure-feature-star-v1Content-Type: application/json
{ "page_id": "<page GUID>", "name": "Feature star", "path_data": "M 60 0 L 74 42 L 120 42 L 82 68 L 96 112 L 60 84 L 24 112 L 38 68 L 0 42 L 46 42 Z", "left": 80, "top": 120, "width": 120, "height": 112, "fill_paint": { "type": "linear_gradient", "units": "object_bounding_box", "spread_method": "pad", "x1": 0, "y1": 0, "x2": 1, "y2": 1, "gradient_transform": [], "stops": [ { "offset": 0, "color": "#f97316", "opacity": 1 }, { "offset": 1, "color": "#7c3aed", "opacity": 1 } ] }, "fill_rule": "non_zero"}The bounded safe profile accepts SVG commands M/L/H/V/C/S/Q/T/A/Z, including relative
commands. Fill and stroke paint can be none, solid, linear_gradient, or
radial_gradient, with 2–32 ordered stops and an optional six-value SVG
gradient_transform. Gradient stroke stops must be opaque; fill-stop transparency is
preserved in the vector PDF through a soft mask. paint_order chooses fill_stroke or
stroke_fill; non_scaling_stroke keeps the visual stroke width during proportional
resizing. Unsupported combinations fail validation instead of producing a visually
different PDF.
Every gradient stop accepts an exact offset from 0 to 1 and a #RRGGBB color. Fill
stops additionally accept opacity from 0 to 1. Linear geometry uses x1, y1, x2
and y2; radial geometry uses cx, cy, r and optional fx, fy, fr. Coordinates
are fractions when units is object_bounding_box and local document units when it is
user_space_on_use. The Editor exposes the same data as draggable stops, exact color,
position and opacity fields, linear angle, and radial center, focus and radius controls.
Changing gradient kind preserves existing stops and any imported gradient_transform.
Complete SVG clipart or multi-path artwork can be imported in one operation:
POST /api/v1/design-templates/{tpl_id}/draft/svg-importsIf-Match: "dd-draft-r7-..."Idempotency-Key: brochure-brand-clipart-v1Content-Type: application/json
{ "page_id": "<page GUID>", "name": "Brand clipart", "svg": "<svg viewBox=\"0 0 200 100\"><path d=\"M0 0H200V100H0Z\" fill=\"#2563eb\"/></svg>", "left": 80, "top": 120, "width": 240, "height": 120, "mode": "preserved"}mode accepts editable (the default) or preserved. Editable import converts the
supported SVG subset into normal Madoo groups, paths and text for deep editing.
Preserved import keeps complex passive artwork as one atomic vector element. Its
source is an immutable managed asset and its palette remains editable and resettable.
Both modes reject active and external content and neither silently rasterizes the file.
The importer accepts at most 10 MB of UTF-8 SVG, 500 drawable objects and 16
levels of source nesting. It converts paths, rectangles, circles, ellipses,
lines, polylines, polygons and groups into normal canonical groups and paths.
It supports local paints, gradient transforms, fill and clip rules, gradient stroke,
paint order, non-scaling stroke, and affine transform matrices including skew. A
bounded embedded-CSS subset supports simple element, .class, #id, and
compound selectors for the same portable visual properties; inline style keeps
normal cascade precedence. CSS imports, at-rules, combinators, pseudo-classes,
external URLs, and unsupported visual properties are rejected.
Local href/xlink:href inheritance between gradient definitions and bounded local
use references are resolved before conversion. Vector clip paths, bounded alpha or
luminance masks, endpoint markers, and simple single-run SVG text become canonical
DesignDocument data. Non-rendering editor metadata is ignored. External legacy DOCTYPE
declarations are ignored with resolution disabled; DTD entities remain invalid.
The source XML and CSS are discarded after conversion. Scripts, event handlers, external
references, embedded SVG images, arbitrary filters, marker-mid, tspan, text paths and
other unsupported constructs fail with a specific svg_import_* error. They are never
silently rasterized or simplified.
Portable colors include named colors, #RGB, #RRGGBB, integer rgb(0, 128, 255)
and decimal percentage rgb(0%, 50.2%, 100%) notation. Percentage channels must remain
between 0 and 100 and are normalized to #RRGGBB; paint opacity stays in its explicit
SVG opacity field.
MCP exposes import_design_template_svg; AI Agent exposes
design_template.import_svg. Both accept the same mode. The Editor asks which
mode to use when an SVG is selected through the Image tool. Revisions and sharing
carry either canonical geometry or the immutable preserved asset. Both PDF paths
remain vectorial.
Preserved mode accepts at most 10 MB of UTF-8 SVG, 2,000 elements and 32 levels of
nesting. It flattens the safe CSS subset, rejects scripts, event handlers, external
files, network references and embedded raster data, and runs the actual PDF graphics
preflight before storing the content-addressed asset. A saved svg_artwork records
the source hash, sanitizer profile, viewBox and bounded paint-token palette. Palette
changes modify only the token overrides; they do not rewrite the source geometry.
An imported canonical artwork keeps an editable palette after import. Selecting its group in the Editor opens Colors, which lists each original color, its current value and its use count. A color can be changed throughout the artwork and later restored on its own; Reset all restores the complete import-time palette. The replacement covers solid fills, outlines, gradient stops, simple SVG text and endpoint decorations while preserving geometry, opacity, gradient coordinates and transforms. It is a normal draft edit, so it survives save/reopen, revisions, sharing and PDF rendering.
The original value is stored beside every canonical paint occurrence rather than inferred from the current palette. If two original colors are both changed to the same target, either one can therefore still be reset independently. Documents created before this metadata existed capture their current palette as the baseline on their first palette edit.
REST v1 exposes the same semantic operation without requiring the full document body:
GET /api/v1/design-templates/{tpl_id}/draft/elements/{element_id}/palettePOST /api/v1/design-templates/{tpl_id}/draft/elements/{element_id}/palette-replacementsIf-Match: "dd-draft-r8-..."Idempotency-Key: recolor-brand-artwork-001Content-Type: application/json
{ "source_color": "#f97316", "target_color": "#22c55e" }source_color identifies the immutable original palette token returned by the read.
The read response returns original_color, current color, uses, and modified for
every editable paint in the selected element subtree. Restore one token, or omit
original_color to restore the complete palette:
POST /api/v1/design-templates/{tpl_id}/draft/elements/{element_id}/palette-resetsIf-Match: "dd-draft-r9-..."Idempotency-Key: reset-brand-artwork-001Content-Type: application/json
{ "original_color": "#f97316" }MCP reads the palette with get_design_template (view palette, element_id) and changes it with
edit_design_template_palette (operation replace_color, reset_color or reset_all); AI Agent exposes
design_template.get_element_palette, design_template.replace_palette_color,
design_template.reset_palette_color, and design_template.reset_palette.
All authoring surfaces call the same bounded Application palette logic.
Rectangle, circle, ellipse and SVG path use the same portable fill contract. Their
fill_paint can be none, solid, linear_gradient or radial_gradient, with a
separate fill_opacity. Rectangle, circle, ellipse, line and SVG path also use the same
stroke fields: stroke_paint (none, solid, linear_gradient, or radial_gradient), stroke_width, stroke_opacity,
stroke_line_cap, stroke_line_join, stroke_dash_array, stroke_dash_offset and
stroke_miter_limit and non_scaling_stroke. Closed vectors also accept paint_order.
The earlier fill and stroke color strings remain accepted when
typed paint is absent, so previously saved templates do not need a migration.
Existing elements can be changed or removed locally:
PATCH /api/v1/design-templates/{tpl_id}/draft/elements/{element_id}DELETE /api/v1/design-templates/{tpl_id}/draft/elements/{element_id}The patch accepts common geometry and state fields (name, left, top, width,
height, angle, opacity, visible, locked, flip_x, flip_y). The flip fields
mirror an element within its authored bounding box and are preserved in editor, preview,
rendered pages and PDF. target_page_id moves a top-level
element to another page. Text elements additionally accept text, font family/size/
weight/style, color, alignment, line height, underline, strikethrough and bounded
overflow settings. Image elements accept image_source and fit_mode. A vector image
frame uses frame_shape (rectangle, ellipse, or a shape catalog ID) or
frame_path_data (a closed standard SVG path in image-local PDF points). Optional
frame_focal_x, frame_focal_y (0–1), frame_scale (0.01–4),
frame_offset_x, frame_offset_y (image-local points, bounded to four times the image size),
and frame_rotation (degrees, -360 to 360) place the source independently inside the clipped
frame. The offset remains effective even when the source and contour have identical dimensions.
The editor may lower the stored frame scale while enlarging the mask viewport so the
photo keeps the same visible size and page position.
frame_stroke_color and
frame_stroke_width draw an inside border. clear_frame releases the frame.
mask_shape or mask_path_data adds a vector opacity mask in the same local
coordinate system, with optional mask_opacity (0–1); clear_mask removes it.
The image frame and mask travel with the normal element through revisions,
published versions, sharing/import, preview and PDF. Closed vector
elements accept the shared fill fields, while every vector element accepts the shared
stroke fields; lines additionally accept start/end decorations. Rectangles accept
corner_radius_top_left, corner_radius_top_right, corner_radius_bottom_right, and
corner_radius_bottom_left. Every drawable element accepts a shadows array containing
zero or one outer shadow; semantic containers and groups do not. The shadow has color,
opacity, offset_x, offset_y, blur, spread, and non_scaling; send an empty
array to remove it. Coordinates and sizes use the same PDF-point space as the element,
and the shadow is retained by draft revisions, published versions, sharing/import, page
rendering and PDF export. Type-specific
fields on the wrong element type fail before saving. An SVG path additionally accepts
path_data on the same update operation, using the standard safe d grammar
(M/L/H/V/C/S/Q/T/A/Z, including relative forms). This lets REST, MCP and AI Agent
reshape artwork created in the Editor without replacing the document. Nested elements can be edited or
removed in place; moving one across pages requires moving its containing region.
MCP uses add_design_template_static_image, add_design_template_line,
add_design_template_rectangle,
add_design_template_svg_path, import_design_template_svg,
update_design_template_element and
remove_design_template_item (kind element). AI Agent uses the parallel
design_template.add_static_image, add_line, add_rectangle, add_svg_path, import_svg, update_element, update_image and
remove_element capabilities. Its text/common update and image-specific update are
separate so the planner cannot accidentally apply image defaults to a text box.
All three surfaces call the same Application commands, workspace checks, ETag compare,
transactional receipt and content validator.
Add one text element to a native draft
Section titled “Add one text element to a native draft”Use design-templates:write and WsAssetsManage. Read the outline for the current draft_etag
and an optional page ID, then add a named text element using PDF points. A blank A4 draft is
about 595 × 842 points. This command modifies just the requested element; it does not accept a
replacement document body.
POST /api/v1/design-templates/{tpl_id}/draft/text-elementsIf-Match: "dd-draft-r1-..."Idempotency-Key: proposal-title-001Content-Type: application/json
{ "name": "Proposal title", "text": "Proposal for Acme", "left": 36, "top": 42, "width": 500, "height": 60 }The response returns element_id, the new draft_revision and draft_etag. A stale ETag
returns 412 precondition_failed. Retrying the same body/key returns the original result and
Idempotency-Replayed: true even when the draft has since advanced; changed body with the same
key returns 409 idempotency_conflict. The semantic body, not the If-Match header, defines
the retry payload. Migration 479 stores the receipt in the same SQL transaction as the draft
revision and placeholder index. The Editor continues to use its existing DesignDocument
content save path and sees this added element on reload.
MCP add_design_template_text uses the same command and contract. It requires if_match and
idempotency_key, applies the same workspace/scope/permission guard and records a write audit.
Mixed styles inside one text element
Section titled “Mixed styles inside one text element”POST /draft/text-elements and PATCH /draft/elements/{element_id} accept optional
rich_text in the renderer-neutral madoo.rich-text/v1 format. It contains paragraphs and
contiguous character runs; it never contains HTML, editor selection offsets or Fabric objects.
Each null run property inherits from the owning text element.
{ "name": "Proposal title", "left": 36, "top": 42, "width": 500, "height": 80, "rich_text": { "schema": "madoo.rich-text/v1", "paragraphs": [ { "list": { "kind": "ordered", "level": 0 }, "runs": [ { "text": "Proposal for ", "fontWeight": "bold" }, { "text": "Acme", "fontStyle": "italic", "fill": "#2563eb" } ] }, { "list": { "kind": "ordered", "level": 0 }, "runs": [ { "text": "Hackathon brief", "underline": true } ] } ] }}The Application service derives the compatible TextElement.Text projection by joining runs
and paragraphs with \n. When a client also supplies text, it must equal that projection.
An LF inside a run is a soft break belonging to the same paragraph/list item (the editor authors it
with Shift+Enter); a paragraph boundary is a hard break and starts the next item. CR characters are
invalid. The bounded format allows 1–1000 paragraphs, at least one run per paragraph, at most 10,000
runs and 10,000 projected characters.
A paragraph can carry optional list semantics:
"list":{"kind":"bullet|ordered","level":0,"start":1}. level is 0-8; start is
allowed only on an ordered paragraph and restarts that level between 1 and 1,000,000. Markers are
generated by the editor and PDF renderer and never become part of TextElement.Text, run text,
selection offsets or placeholder values.
A run can override fontFamily, fontSize, fontWeight, fontStyle, exact portable font,
fill, underline, linethrough and baselineShift (positive values move upward). Rich text
upgrades the draft to DesignDocument schema 2.0 and participates in revisions, sharing/import,
font manifests, masters/components, previews and PDF rendering.
On PATCH, sending plain text deliberately clears prior inline formatting. Send
clear_rich_text: true to remove inline formatting while retaining the existing plain projection.
rich_text and clear_rich_text cannot be combined. MCP add_design_template_text and
update_design_template_element, plus Agent design_template.add_text and
design_template.update_element, use the same canonical object, validation, ETag and idempotency
rules. Lists are paragraph semantics; columns will belong to the frame; linked frames will reference
one shared text story rather than duplicate the run content.
To make that text dynamic, read its element_id from the outline and configure a text field:
PUT /api/v1/design-templates/{tpl_id}/draft/text-elements/{element_id}/placeholderIf-Match: "dd-draft-r2-..."Idempotency-Key: proposal-headline-field-001Content-Type: application/json
{ "code": "headline", "name": "Proposal headline", "required": true, "description": "The title supplied at render time" }This creates or updates the placeholder on just that element. The code becomes a key in the
published revision’s fields contract and in the design/template_render JSON input, for
example { "headline": "Proposal for Acme" }. Use a new retry key for this command.
The same ETag/retry and workspace rules apply; the Editor sees the binding on reload.
MCP configure_design_template_element_placeholder with type text uses the same service and response.
For an element already present in a native draft, the typed variant can bind any compatible field. Read the outline for its element ID and current ETag, then use a fresh retry key:
PUT /api/v1/design-templates/{tpl_id}/draft/elements/{element_id}/placeholderIf-Match: "dd-draft-r5-..."Idempotency-Key: proposal-budget-field-001Content-Type: application/json
{ "type": "number", "code": "budget", "name": "Campaign budget", "required": true, "description": "Amount supplied as a JSON number" }The field types follow the DesignDocument renderer’s compatibility rules:
| Field type | Compatible existing element | Example values JSON |
|---|---|---|
text, number |
Text | "headline": "Proposal", "budget": 1200 |
boolean |
Text or container | "approved": true |
image |
Image | "hero": "https://example.com/hero.jpg" |
color |
Text or compatible shape | "accent": "#2255AA" |
json |
Repeat region | "items": [{"name":"A"}] |
A repeat region’s JSON source code is updated with the field code. An incompatible element/type,
invalid code or wrong-type default returns 400 before a draft write. The same ETag/retry behavior
applies as for text fields. MCP configure_design_template_element_placeholder calls the same
command and returns the same result. This command binds existing elements.
Create a dynamic image box
Section titled “Create a dynamic image box”One command places an image box and defines its required image field together:
POST /api/v1/design-templates/{tpl_id}/draft/image-placeholdersIf-Match: "dd-draft-r3-..."Idempotency-Key: proposal-hero-image-001Content-Type: application/json
{"name":"Hero image","code":"hero_image","left":48,"top":80, "width":495,"height":260,"fit_mode":"fill"}Dimensions are PDF points within the selected page; page_id is optional and
defaults to the first page. fit shows the whole image, fill crops it to cover
the box, and stretch changes its proportions. The field code becomes a key in
sample values and design/template_render.data, for example
{"hero_image":"data:image/png;base64,..."} or an image storage URI. The
placeholder is required, so add a sample image and inspect its exact preview
before publish. MCP add_design_template_image_placeholder calls the same
Application command with the same permission, ETag and retry receipt.
Create a simple repeated text list
Section titled “Create a simple repeated text list”This one command creates a native repeat region, its JSON array field and a text row bound to one property of each item. It edits only that region; no full DesignDocument JSON is sent to the API:
POST /api/v1/design-templates/{tpl_id}/draft/repeating-text-listsIf-Match: "dd-draft-r3-..."Idempotency-Key: proposal-items-list-001Content-Type: application/json
{"name":"Proposal line items","source_code":"items","item_field":"name", "left":48,"top":120,"width":495,"height":240,"item_height":32, "max_items":10,"overflow_policy":"fail"}source_code is the JSON key supplied to design/template_render.data;
item_field is the property read from each array item. For example,
{"items":[{"name":"Discovery"},{"name":"Delivery"}]} draws two rows.
The list area and row height use PDF points. max_items bounds expansion and
overflow_policy chooses fail, fit, clip or continue_page when rows do not fit
(continue_page adds copies of the page; only one such list per page, placed directly on it).
The design/template_render node keeps this rule by default (overflow_policy
template); setting the node to fail, fit or clip applies that rule to
every Repeat of the template instead.
Read the outline for its page ID and current ETag, then preview a sample before
publishing. MCP add_design_template_repeating_text_list uses the same Application
command, permission, retry receipt and response. The Development qualifier
qualify-dd7-repeating-list-dev.mjs checks two rendered rows and a page JPEG;
repeating-line-items.json is the workflow example with PDF, page image and
layout report.
Rows with several fields: cards, tables, galleries
Section titled “Rows with several fields: cards, tables, galleries”A row is not limited to one text: like in the editor it can hold texts, images, shapes and Layouts —
a product card with photo, name, price and a “sold out” badge. Build it with the same commands used
on a page, adding parent_id:
- Add elements into the row. Every add command (
text-elements,image-placeholders,rectangles,lines,shapes,svg-paths,svg-imports,static-images) accepts"parent_id": "<repeat_region id>"; coordinates are then relative to the row.parent_idalso places an element inside alayout_region, a group or a layer folder. - Bind them to the keys of each item. A field bound inside a row — with
PUT /draft/text-elements/{id}/placeholder,PUT /draft/elements/{id}/placeholderor an image box added withparent_id— is a key of each list item: the outline showsbinding_path: "item.price"and the data is{"products":[{"name":"Lampada","price":89,"photo":"…","sold_out":false}]}. A number key keeps itsformat. - Arrange the row with
POST /draft/layouts/arrange(the elements share the row as parent), and set what sits on top with the element update’sz_order(front,back,forward,backward): a card background added after its texts goesback. - Show an element only when a key says so with the element update’s
condition:{"condition":{"operator":"equals","placeholder_code":"item.sold_out","literal":"true"}}. Operators:not_empty,equals,greater_than,greater_than_or_equal,less_than,less_than_or_equal(numeric literal),not(one condition),all/any(conditions); a code outside a row is a template field.clear_conditionremoves it. - Configure the Repeat — the editor’s Repeat panel:
PATCH /api/v1/design-templates/{tpl_id}/draft/repeats/{repeat_id}If-Match: "dd-draft-r7-..."Idempotency-Key: catalog-grid-001
{"list_key":"products","max_items":9,"overflow_policy":"continue_page", "layout":{"mode":"grid","columns":3,"row_gap":16,"column_gap":12}, "item_fields":[{"code":"sold_out","name":"Sold out","type":"boolean"}]}list_key renames the list everywhere it is named (its field, conditions, sample sets); layout lays
the rows out (vertical, horizontal, grid with columns, gaps, padding, alignments);
item_fields declares the keys a condition reads but the row does not print. MCP
configure_design_template_repeat and the agent’s design_template.configure_repeat run the same
command. The outline reports a Repeat’s list_key, max_items, overflow_policy, item_fields,
and every element’s position, size, placeholder_type, binding_path, format and condition.
Two more element properties, on the same PATCH /draft/elements/{element_id} (MCP
update_design_template_element, agent design_template.update_element):
char_spacing— letter spacing of a text, in thousandths of an em (100= a tenth of the font size;300for a spaced-out small-caps label).follows_row_height— in a Repeat row whose texts grow:truemakes a rectangle, ellipse, image or line stretch with the row (a card background),falsekeeps its size;clear_follows_row_heightreturns to the automatic rule (full-height rectangles and lines stretch).
Whole documents: read, write, create and duplicate
Section titled “Whole documents: read, write, create and duplicate”Everything above edits a draft one command at a time. A template is also one JSON document, in the
public format madoo.design-document/2.0 — the format the editor saves, so everything the editor can
author can be written here. Its JSON Schema is served by the schema catalog:
GET /api/v1/json-schemas/madoo.design-document/2.0 (MCP get_json_schema).
GET /api/v1/design-templates/{tpl_id}/draft/content # the draft; ETag headerGET /api/v1/design-templates/{tpl_id}/versions/{revision}/content # a published revision, as publishedPUT /api/v1/design-templates/{tpl_id}/draft/content # If-Match + Idempotency-Key; body = the documentPOST /api/v1/design-templates # {"name":"…","content":{…}} starts the draft as that documentPOST /api/v1/design-templates/{tpl_id}/duplicate # {"name":"…","revision":2} copies a revision (or the draft)- The format.
schemaVersionis"2.0". Pages, master pages, components and container children hold elements discriminated by$type:text,image,rectangle,circle,ellipse,line,path,group,layout_region,repeat_region,svg_artwork,component_instance,folder. IDs are GUIDs unique in the document; a field is aplaceholderon its element (code,placeholderType,bindingPathitem.keyinside a Repeat row,format). Read an existing template to see a complete example: it is the quickest way to learn the format. - Validation. The document goes through the editor’s validation and save. Unknown properties are errors
(a misspelt name would otherwise be lost), as are duplicate IDs, invalid field codes, bindings, conditions
and limits. A rejected document returns 422
content_invalidwith every problem inerrors(field= JSON path,message= code and explanation). - Concurrency and retries.
PUTrequires the draft ETag asIf-Matchand anIdempotency-Key, like every draft edit;POSTrequires anIdempotency-Key. - When to use it. Build or rewrite a whole template, copy an example and adapt it, move a template between workspaces (fonts and storage images must exist in the target workspace). For small changes the element commands above are safer: they cannot touch what they do not name.
Sample sets. A document sent without a sampleSets property keeps the draft’s sample sets (an empty list
clears them). REST v1 returns them in full; MCP (get_design_template view content) and the agent
(design_template.get_content) leave them out unless include_sample_sets is true and list them in
omitted_sample_sets (ID, name, field codes): they are example values, often with inline images, and can
weigh most of a template (F05: 180,000 of 255,000 characters). Reading the draft without them and sending it
back therefore never loses them.
One page of the outline. MCP get_design_template view outline and the agent’s
design_template.get_draft_outline take page_id (a page or master page) to list only that page’s
elements; every page, master page, sample set, style and component stays listed.
MCP: get_design_template (view content), replace_design_template_content, duplicate_design_template,
create_design_template_draft with content. Agent: design_template.get_content,
design_template.replace_content, design_template.duplicate, design_template.create_draft with
content. The template list (GET /api/v1/design-templates, MCP list_design_templates, agent
design_template.list) takes status = published (default), draft, archived or all.
Exercise the template with example JSON
Section titled “Exercise the template with example JSON”Read the current draft outline for its ETag and the placeholder codes, then add a named sample set.
Its values object uses those codes as keys, exactly like the design/template_render data input.
For a required text field called headline:
POST /api/v1/design-templates/{tpl_id}/draft/sample-setsIf-Match: "dd-draft-r3-..."Idempotency-Key: proposal-headline-sample-001Content-Type: application/json
{ "name": "Proposal authoring example", "values": { "headline": "Proposal for Acme Studio" } }The response supplies sample_set_id and the new draft ETag. The outline lists the sample by
name and covered field codes. Unknown codes or values of the wrong JSON type return 400. A stale
ETag returns 412; the same body/key replays without another draft revision, while a changed body
with that key returns 409. Use a different retry key for each sample. MCP
edit_design_template_sample_set (operation add) uses the same Application command, scope and audit rules.
When a new required field is added later, an earlier sample may become unready. Extend that specific sample without replacing the document or losing its other values:
PATCH /api/v1/design-templates/{tpl_id}/draft/sample-sets/{sample_set_id}/valuesIf-Match: "dd-draft-r6-..."Idempotency-Key: proposal-example-add-budget-001Content-Type: application/json
{ "values": { "budget": 1200 } }This merges just budget into the selected sample. Read its ID and ETag from the outline.
Unknown codes or wrong JSON types return 400; a missing sample returns 404, stale ETag 412,
same-key replay returns the original revision, and changed payload/key returns 409.
MCP edit_design_template_sample_set (operation set_values) uses the same command and write audit. Existing values
such as headline remain in the sample; the next readiness check evaluates all samples again.
An exact preview is a read operation using design-templates:read and WsAssetsRead:
GET /api/v1/design-templates/{tpl_id}/draft/sample-sets/{sample_set_id}/exact-preview?max_page_dimension=900It returns page JPEG data_uri values, a compact layout report and readability/readiness checks
for the current draft.
The page size can be 400–1600 pixels. MCP preview_design_template_sample_set returns the same
projection. Preview renders the sample with the DesignDocument engine and makes no AI provider call.
Check the pages and fix blocking diagnostics before publication. The readiness response reports
whether required fields remain unexercised by any sample.
Update a saved workflow pin after review
Section titled “Update a saved workflow pin after review”The default workflow pin stays at the published revision selected during authoring.
The latest_compatible policy may use a newer compatible revision in one new run,
but that run never edits the saved workflow. To promote a saved pin, review the
affected workflow first:
GET /api/v1/workflows/{wf_id}/template-revisionsThis response shows each renderer’s saved/latest revision, field and page changes, compatibility issue and the definition ETag. Choose only nodes marked compatible. Then explicitly save the chosen revision with the ETag from that review:
POST /api/v1/workflows/{wf_id}/template-revisions/upgradeIf-Match: "<definition ETag from review>"Content-Type: application/json
{"nodes":[{"node_id":"render_proposal","target_revision":2}]}One request may select 1–20 distinct nodes. The command changes only their
published template pins, preserves connections and editor layout, validates the
whole resulting workflow and returns the new workflow version/ETag. A Published
workflow gets a new copy-on-write definition version; an Archived workflow cannot
be edited. An old ETag returns 412; an unreviewed or incompatible revision returns
409. Review again after either conflict. MCP
get_workflow_template_revisions and upgrade_workflow_template_revision provide
the same read/write sequence for one node, and AI Agent exposes
workflow.review_template_revisions followed by
workflow.upgrade_template_revision. The Editor offers a local revision diff
before the author saves its canvas. When AI Assistant requests the same read-only
review, its chat panel shows a card with a fresh server review, one-node selection
and confirmation. Only the author’s click invokes the REST write with ETag; the
card requires the reviewed workflow to be open in Editor with no unsaved changes.
The Development example DD7 Persistent Pin Upgrade — Isolated Proposal uses
the compatible two-revision fixture and checks this operation without creating
an execution or changing the original two-page proposal workflow.
Render a published revision directly as PDF
Section titled “Render a published revision directly as PDF”DD9 applies shared admission to direct PDF render, exact preview and workflow render. A request exceeding configured data-set, aggregate output-page or resolved-element limits fails with DESIGN_RENDER_LIMIT_EXCEEDED; an oversized image fails with DESIGN_IMAGE_LIMIT_EXCEEDED, an excessive total of materialized images with DESIGN_IMAGE_TOTAL_LIMIT_EXCEEDED, an image exceeding the pixel budget with DESIGN_IMAGE_PIXEL_LIMIT_EXCEEDED, an oversized native content JSON with DESIGN_CONTENT_INPUT_LIMIT_EXCEEDED, and an oversized imported PDF with DESIGN_PDF_INPUT_LIMIT_EXCEEDED. A native draft whose stored JSON differs from its recorded SHA-256 fails with CONTENT_HASH_MISMATCH; a published version uses VERSION_HASH_MISMATCH. PDF sections already exceeding the output budget are rejected before rendering; a final PDF exceeding it fails with DESIGN_PDF_OUTPUT_LIMIT_EXCEEDED. The limits are deployment configuration (DesignTemplates:RenderLimits), not client-supplied fields. Initial defaults are 100 data sets, 200 output pages, 25,000 resolved elements, 20 MiB per image, 100 MiB total images, 40 million pixels per image, 32 MiB per native content JSON, 100 MiB per imported PDF and 100 MiB per output PDF. The final-byte cap is checked after PDF generation; file-backed native outputs are deleted on rejection.
The resolver checks workspace access before every cache lookup. Verified, content-addressed JSON is cached per organization, workspace, path and SHA-256 with a 128 MiB process-local budget and 10-minute absolute TTL by default. Content without a recorded hash is read within the byte limit but is not cached. Concurrent requests for the same content share one download per process; a hash mismatch is rejected and never cached.
POST /api/design-documents/import-pdf applies MaxImportedPdfBytes before multipart buffering and PDF analysis. The default is 100 MiB; an oversized upload returns HTTP 413 with DESIGN_PDF_INPUT_LIMIT_EXCEEDED and creates no document. The request body may use up to 1 MiB extra for the multipart envelope.
For imported PDFs with multiple data sets, the renderer also stops before merge when the cumulative byte size of generated iterations exceeds MaxOutputPdfBytes. This is a conservative bound: it may reject an input whose merged PDF would have deduplicated some bytes. The error remains DESIGN_PDF_OUTPUT_LIMIT_EXCEEDED.
DD9 render admission also has process-local pools: two simultaneous interactive preview/direct export renders and four workflow renders by default. A slot covers the PDF render and every page image rasterized from it (exact preview, pages/image outputs, REST v1 and MCP page renders). Requests beyond the slots wait in a bounded first-in-first-out queue (32 interactive, 64 workflow) for at most 20 seconds (interactive) or 60 seconds (workflow). When the queue is full or the wait expires the caller receives DESIGN_RENDER_BUSY: HTTP 503 with Retry-After: 5 on direct REST routes (REST v1 included), a retryable node failure in workflows. Cancellation while waiting aborts immediately. Exporting an imported PDF that has no image replacement returns the unchanged original without taking a slot. All values are configurable under DesignTemplates:RenderLimits.
Workspace ICC profiles and PDF/X-4
Section titled “Workspace ICC profiles and PDF/X-4”The editor’s PDF export and the design/template_render node can produce an ordinary PDF or a print-ready
PDF/X-4 that embeds an ICC profile uploaded by the user as its OutputIntent. The catalog belongs to the workspace
and can hold several profiles:
GET /api/v1/print-color-profilesThe upload is multipart and requires a printer-class CMYK ICC profile, version 2 or 4, of at most 5 MB:
POST /api/v1/print-color-profilesContent-Type: multipart/form-data
displayName=PSO Coated v3 — Printer AoutputConditionIdentifier=FOGRA51setAsDefault=truelicenseAcknowledged=truefile=@printer-a.iccThe file is immutable and identified by profileGuid and sha256. Change the default with
POST /api/v1/print-color-profiles/{profileGuid}/default, or archive a profile with
POST /api/v1/print-color-profiles/{profileGuid}/archive. Archiving removes it from new selections but does not
invalidate published workflows that already pinned it.
The editor endpoint POST /api/design-documents/{id}/generate-pdf accepts, besides the placeholder values:
{"exportMode":"pdfx4","colorProfileGuid":"11111111-1111-4111-8111-111111111111"}exportMode is standard by default. On the design/template_render node the equivalents are
pdf_export_mode=pdfx4 and color_profile_guid; on publish Madoo adds the private fingerprint
colorProfileSha256. The layout_report records the mode and the identity of the profile used.
This first version keeps the authored colours as managed RGB (sRGB by default) and uses the CMYK profile as the
print destination (output intent). It offers no soft proof, spot colours, overprint, trapping, total-ink control or
per-image source conversion. Imported PDFs and intro/outro PDFs cannot take the PDF/X-4 path; use standard or a
native DesignDocument. The profile must match the printer’s specifications, and a clone in another workspace must
select or upload an authorised profile there.
Read the published revision contract for its exact field codes and JSON types. Send that JSON object as the request body:
POST /api/v1/design-templates/{tpl_id}/versions/{revision}/render-pdfContent-Type: application/json
{"customer":"Example Customer","headline":"Example Proposal"}The response is a PDF download and carries X-Design-Template-Version: tplv_...
and X-Design-Template-Pages. This read-only render uses the same production
template renderer as the workflow node. Unknown codes, wrong JSON types,
duplicate codes and missing required fields fail before rendering; the body is
limited to 1 MB/200 fields and the direct result to 20 MB/100 pages. Use a
workflow for larger or repeated production outputs. MCP
render_design_template accepts template_id, revision, data_json and output pdf
and returns the PDF as an embedded binary resource with small metadata.
Neither REST nor MCP stores a new asset or calls an AI provider.
The internal AI Agent design_template.render_pdf uses the same revision and
typed data contract, stores the resulting PDF as an Idea-linked workspace asset
and returns only its opaque reference, file metadata and digest to the planner.
The conversation shows the generated file with a browser-resolved download;
PDF bytes and private storage paths never enter model context. AI Assistant
check_design_template_pdf_render remains a workflow-authoring check and returns
only page count, byte count, digest or field errors.
Render selected published pages and inspect the manifest
Section titled “Render selected published pages and inspect the manifest”Use the same typed JSON object with render-pages when an integration needs
page images directly, without first creating a workflow execution:
POST /api/v1/design-templates/{tpl_id}/versions/{revision}/render-pages?page_selection=1,3-4&max_page_dimension=1200Content-Type: application/json
{"customer":"Example Customer","headline":"Example Proposal"}page_selection accepts all, individual one-based page numbers and ranges.
The response identifies the immutable tplv_ revision, total rendered pages,
selected pages and one JPEG item per page with byte count, SHA-256 and a
data_uri. The longest image side can be 400–1600 pixels. A direct request is
limited to 20 selected pages and 20 MB of JPEG data; use a workflow when images
must be durable, reused, or larger. Invalid ranges return an RFC 7807 error that
states the valid rendered page interval.
MCP render_design_template with output pages accepts the same template_id, revision,
data_json, selection and dimension. It returns the compact manifest as
structured content and attaches each JPEG as an embedded binary resource, so
base64 data is not copied into the explanatory text. REST and MCP call the same
Application service and PDF rasterizer as design/template_render. They store
no asset, create no execution and call no AI provider.
A new revision of a published template
Section titled “A new revision of a published template”A published template always keeps an editable draft: you do not need to return it to draft to
prepare the next revision. Edit the draft (element commands, or read and PUT the whole document),
check it with a sample set and the publish readiness, then publish: the result is a new immutable
revision (revision 2, 3, …). While you work:
- workflows whose
design/template_rendernode is pinned to an earlier revision keep rendering it; review and move their pin with the template revisions endpoints; document/pdfandaggregate/pdfnodes withouttemplate_revisionrender the draft at their next run, including unfinished edits; nodes withtemplate_revisionkeep their revision.
revert-to-draft is for a different purpose: it takes the template out of selection (status draft)
while it is reworked. To start from a published revision instead of the current draft, read it with
GET …/versions/{revision}/content and PUT it as the draft, or POST …/duplicate it into a new template.
Publish the reviewed draft
Section titled “Publish the reviewed draft”Read GET /{tpl_id}/draft/publish-readiness immediately before publishing. Confirm
can_publish: true, review its checks and use its draft_etag:
POST /api/v1/design-templates/{tpl_id}/draft/publishIf-Match: "dd-draft-r4-..."Idempotency-Key: proposal-publish-001The response gives an immutable revision, portable tplv_ version ID, page count and hashes.
The same template/key replays that exact version, even after the draft ETag changes; another
payload with that key conflicts. An outdated ETag returns 412 and an unready draft returns 422.
Migration 479 commits the publication receipt in the same SQL transaction as the new version and
current pointer. MCP publish_design_template_draft uses the same service with write scope,
WsAssetsManage and audit. When receipt retention is enabled, replay history lasts its configured
period (30 days by default); retention is disabled by default.
Archive or return a published template to draft
Section titled “Archive or return a published template to draft”Read GET /api/v1/design-templates/{template_id}/draft first and use its exact
draft_etag as the If-Match header. Send a new Idempotency-Key for each
operation. POST /api/v1/design-templates/{template_id}/archive hides a draft
or published template from future selection. An archived template cannot be
edited or returned to draft through this command.
POST /api/v1/design-templates/{template_id}/revert-to-draft makes a published
template editable again; publish a new immutable revision when the edit is
ready. Both return portable template_id, status, draft revision/ETag and
whether the retry was replayed. A stale ETag returns 412; reuse of a key for a
different operation returns 409. Status change and retry receipt commit in one
SQL transaction. Workflows pinned to an earlier published revision continue to
use that immutable snapshot.
MCP offers change_design_template_lifecycle (action archive or revert_to_draft) with the same
if_match and idempotency_key; it is marked destructive for client review. These
commands change template availability, so choose the intended template from
its draft readback before calling them.
The internal AI Agent offers design_template.change_lifecycle with
template_id, action (archive or revert_to_draft) and exact if_match.
It requests a dedicated approval tied to that template, action and draft
revision. The Agent rechecks the live state after approval and applies the same
Application command; an outdated approval changes nothing. This capability has
passed offline build and tests and awaits a live Agent planner gate.
Document-local appearance styles
Section titled “Document-local appearance styles”Native DesignDocument drafts contain an optional styles catalog. A style has an id, name,
kind (paint, text, object) and bounded appearance properties. Text and object styles may
link document paint styles through paintRefs (fill, stroke where supported). Elements carry styleRefs
(text, object, fill, stroke) and styleOverrides. The editor stores effective appearance
on each element so PDF rendering, thumbnails, published revisions, and older readers use the
same pixels. Style definitions never include text content, coordinates, dimensions, path data,
image source, crop or page layout. Master elements can reference the same document catalog.
New blank documents start with editable Title, Subtitle, and Body text styles. Documents
created with explicit initial content, including import and clone flows, preserve that content’s
style catalog unchanged.
POST /api/v1/design-templates/{id}/draft/styles/edit accepts operation:
create, update, update_from_element, apply, detach, reset_overrides, or remove.
Optional request fields are styleId, elementId, slot, name, kind, properties, paintRefs, and
replacementStyleId. It requires the exact draft If-Match and an Idempotency-Key, and returns
the usual draft edit response with the changed style or element ID in element_id. The
outline (get_design_template view outline) now lists style definitions and each element’s
style_refs. MCP exposes edit_design_template_style; the Agent capability is
design_template.edit_style with the same operations.
Editing a style updates all linked elements in this document except properties with local
overrides. Applying a style clears overrides for its managed properties. Direct element edits
create local overrides; reset_overrides restores linked values. Removing or detaching a style
keeps the element’s current appearance, unless replacementStyleId is supplied. The editor’s
Styles panel has separate Text, Paint and Object tabs with previews and usage counts. Each row
offers apply, edit and delete actions; creation and editing use a dialog with a live preview.
Import and export controls are in the panel header. It exports a
madoo.document-styles/v1 JSON file; importing into another document assigns fresh IDs and
disambiguates names. Importing does not create a live cross-document link. Workspace-wide
style libraries are planned for a later campaign.
Document-local reusable components
Section titled “Document-local reusable components”Native DesignDocument drafts can contain an optional components catalog. Each definition has an id,
name, intrinsic width and height, and a local element tree. Pages and masters reference a definition
through a lightweight component_instance element with componentId. The instance owns its page transform
(left, top, scale, rotation, opacity, visibility and lock state), while the definition owns the internal
geometry and hierarchy. Definitions cannot contain other component instances or repeat regions in this
version, so expansion is finite and deterministic.
An instance can carry componentOverrides, keyed first by definition element ID and then by property name.
The bounded override surface supports text and image content, image frame data, fill and stroke paints,
opacity, shadows, visibility and style references. Coordinates, dimensions, path geometry, child hierarchy
and component references remain definition-controlled. Editing a definition updates every linked instance;
resetting an instance clears its overrides. Detaching expands the current appearance into an ordinary group.
Deleting a definition detaches its instances so the document keeps the same visible content.
The editor exposes the catalog in the Components document panel. The + action opens a dedicated visual
component canvas, where a component can be created independently with the compatible drawing, text, image,
layer and style tools from the document editor. A separate shortcut creates a component from the current
selection, replaces that selection with its first linked instance in the same position, and opens the same
visual editor. Existing definitions are edited on that canvas with explicit Save component and Cancel actions;
the document canvas and its history are restored when the component editor closes. Catalog thumbnails use the
same rendering pipeline as document pages so text, vector paths, lines, images, masks and styles are represented
faithfully on a white preview background. Clicking a catalog entry starts placement mode, while dragging it onto
the page places the instance at the drop point. A dedicated dialog remains available for the selected instance’s
allowed content and appearance overrides. Component definitions and instances participate in undo/redo,
save, reopen, revisions, sharing, clone/import, asset and font manifests, thumbnails and PDF rendering.
Before placeholder binding and layout, the runtime expands instances into normal element groups; this also
makes fields, conditions, image masks and style references inside a component behave like their ordinary
element equivalents.
POST /api/v1/design-templates/{id}/draft/components/edit accepts operation:
create_from_elements, insert, rename, update_definition, override, reset_overrides, detach, or
remove. Optional request fields are componentId, elementIds, instanceId, pageId, name, left,
top, targetElementId, and properties. It requires the exact draft If-Match
and an Idempotency-Key, and returns the usual draft edit response. The draft outline lists component
definitions, definition element IDs, instance counts, and component_id on instance elements. MCP exposes
edit_design_template_component; the Agent capability is design_template.edit_component with the same
operations.
The catalog is local to one document. Workspace libraries, variants, nested components, component-specific auto layout, cross-document synchronization and separate component import/export are deferred to a later campaign.
Flow layouts: texts that take the lines they need
Section titled “Flow layouts: texts that take the lines they need”A title that is sometimes one line and sometimes three should not leave a gap below it, nor overlap what
follows. A Layout region whose elements fit their content (layout.childSizing: "content") behaves
like a web flex column or an auto-sized InDesign frame: each text with a line rule takes exactly the lines its
printed value needs, and everything after it in the Layout moves up or down. Nested Layouts are measured
first; an element hidden by its rule or not visible leaves no space. With sizing: "hug" the region itself
is as large as its content, and anchor decides which edge stays in place: start (top, the default),
center, or end — a block anchored at the end grows upwards from its bottom edge, like a caption sitting on
the bottom of a photo. Layouts written before this setting existed keep drawn sizes (childSizing absent).
A text’s line rule is its grow: max_lines (1–50), beyond (ellipsis, shrink, or fail, which stops
the render and names the value), and height: content makes the text exactly as tall as its lines (at least
one); at_least_drawn (the Repeat-row rule) never makes it shorter than drawn. The canvas, the exact preview
and the PDF use the same measurement; the canvas measures the text as written (a field shows its {code}),
the preview and PDF the sample or runtime values.
On a free text — on the page, in a Group, or in a Layout that keeps drawn sizes — grow has nothing to
push: the box keeps its place and height, and the value may take up to max_lines lines and no more than the
box holds (at least one). A value that needs more follows beyond inside the box — shrunk, cut with an
ellipsis, or fail with DESIGN_TEXT_TOO_LONG (the message gives the lines needed and the lines available);
a value that fits prints as drawn, and height has no effect. The exact preview and the PDF apply it; the
canvas shows the text as written. Everywhere, shrink keeps the line limit: a smaller size never wraps the value
onto more lines than max_lines (or than a free text’s box holds); a value too long even at the minimum size
keeps the limit, loses the rest and is reported in the preview’s DESIGN_TEXT_OVERFLOW.
A Repeat inside a flowing Layout takes the height of the rows it prints — its padding plus the rows
(measured when they grow) and the gaps between them; an empty list takes only its padding — so a list of
line items followed by a total, or key ideas followed by a quote, keeps what follows right after the last
row. The drawn height is the most the list may take: rows beyond it follow the Repeat’s overflow rule
(fail, clip, fit) and the list keeps its drawn height. A Repeat that continues on new pages must sit
directly on the page, not in a Layout.
A rectangle or image directly inside a Layout can be a background layer (layoutBackground: true): it is
out of the flow, covers the whole measured region (padding included), is drawn behind the other elements
and grows with the region. Its colours, gradient and opacity are those of the element; layout.cornerRadius
rounds all the background layers (images are clipped to it). A photo plus a semi-transparent rectangle makes
the classic card with an overlay.
POST /api/v1/design-templates/{id}/draft/layouts/arrange accepts operation:
create—element_ids(same parent) are wrapped in a new Layout in reading order (same visual row left to right, otherwise top to bottom). Defaults: vertical, fits its content, padding 12, gaps 8, elements fit their content (texts without a rule get up to 10 lines then an ellipsis). The padding is placed around the content, so it stays where it was drawn. Optionalnameandsettings.update—layout_idplussettingsand/ororder(every element id of the Layout once, background layers excluded; they keep their place).unwrap—layout_id: the elements return to the parent where the Layout shows them (drawn sizes); background layers become ordinary shapes covering the region’s box.
settings fields are all optional: mode (vertical, horizontal, grid, absolute), sizing (hug,
fixed), child_sizing (content, drawn), anchor, padding (all sides) or padding_top/right/bottom/left,
row_gap, column_gap, columns, main_axis_alignment, cross_axis_alignment, clip, corner_radius
(0 removes it). Switching a Layout to child_sizing: "content" gives its texts a content-height rule.
{ "operation": "create", "name": "Cover caption", "element_ids": ["<title id>", "<subtitle id>", "<rule id>"], "settings": { "padding": 24, "row_gap": 8, "anchor": "end", "corner_radius": 12 } }PATCH /draft/elements/{element_id} accepts grow ({ "max_lines": 2, "beyond": "ellipsis", "height": "content" }), clear_grow, and layout_background (true/false). The draft outline reports each element’s
parent_id, a region’s layout and a text’s grow, and uses the content type names (layout_region,
repeat_region, component_instance, svg_artwork). MCP exposes arrange_design_template_elements and the
same fields on update_design_template_element; the Agent capabilities are design_template.arrange_elements
and design_template.update_element. All require the exact draft If-Match and an Idempotency-Key.
Number formats: how a number field prints
Section titled “Number formats: how a number field prints”A number field receives a number (186000, 0.25) and the template decides how it prints — the same idea
as a .NET format string with its culture, expressed as options that the editor, REST, MCP and agents all
understand and that the editor can preview exactly (they are the options of Intl.NumberFormat):
| Option | Values | Example |
|---|---|---|
locale |
language and region (it-IT, en-GB, de-CH…) or a data key in braces ({language}) |
the same template prints 89 € in Italian and €89 in English |
style |
decimal, currency, percent |
a percent takes a fraction: 0.25 prints 25 % |
currency |
ISO code | EUR, USD, GBP |
currency_display |
symbol, code |
89 € / 89 EUR (it-IT); placed as the language places it |
minimum_fraction_digits, maximum_fraction_digits |
0–20 | 1.234,5 with 0–2 decimals |
use_grouping |
true (default), false |
1.234.567 / 1234567 |
sign_display |
auto, always, except_zero |
+3 % |
negative |
minus, parentheses |
-5 / (5) as in accounting |
prefix, suffix |
up to 24 characters, printed as written | da 120 m² |
With locale: "{language}" the language is read from the render data key language (for example
"it-IT" in one data object and "en-GB" in the other); without it the number prints in English (US).
A yes/no (boolean) field takes true_label / false_label (the words printed, default Yes/No) or
boolean_mode: "visibility" (the element shows only when the value is true); a color field takes
color_target fill or stroke. Other options on those types are rejected.
Set it with PUT /draft/elements/{element_id}/placeholder (type: "number", format: {...}), MCP
configure_design_template_element_placeholder, the Agent capability design_template.configure_placeholder,
or the editor (Make placeholder → Number → Number format, with a live example).
Compatibility. A format written before these options (only locale, style, currency and decimals) keeps
printing exactly as before, so published templates never change by themselves: currencies as EUR 89,
thousands grouped only when the minimum and maximum decimals are equal. Any of the new options — and every
format written by the editor, REST, MCP or agents from now on — follows the current rules above.
Links: elements that open an address
Section titled “Links: elements that open an address”Any element can be a link: in the PDF, clicking its box opens an address. On a group or a Layout the
whole container is the link — a product card, a contact block. The address is https:, http:,
mailto: or tel: and may contain fields in braces, filled with the render data:
{code}— a field of the document (it must exist as a field of the template);{item.key}— inside a Repeat row, a key of that row’s item. A key read only by a link joins the list’s contract as an optional text key.
A field inside the address is URL-encoded ({item.sku} = NC 01 gives NC%2001). An address that is only
a field ({product_url}) takes the value as the whole address, which must itself be https, http,
mailto or tel: a value can never turn a link into a script. When a field has no value, or the result is
not a valid address, the element is printed without its link and the render reports
DESIGN_LINKS_LEFT_OUT. Master pages are static: their links cannot read fields.
{ "link": { "href": "https://shop.example/p/{item.sku}?utm_source=whatsapp", "description": "Open the product page" } }description says what the link opens; screen readers read it and some viewers show it. Links exist
only in the PDF: the page images (output: image, the page manifest) are not clickable, and a PDF/X-4
print file leaves links out — an annotation over the printed area is not allowed there — with the
warning DESIGN_LINKS_OMITTED_FOR_PRINT. For a flyer shared on messaging apps, send the PDF alongside
the image, or print the address next to the item.
Set it with PATCH /draft/elements/{element_id} (link, clear_link), MCP
update_design_template_element, the Agent capability design_template.update_element, or the editor
(Fields → the selected element, or Visibility and link of its elements for elements inside a container;
the Layers panel marks linked elements). The draft outline reports each element’s link.
Pages whose height follows their content
Section titled “Pages whose height follows their content”A page read on screen or shared as an image — a visual summary, a social card, a receipt — often has
content of variable length. Give the page a height rule and the printed page is only as tall as what it
prints: it ends bottom_margin points below its lowest printed element (hidden elements and elements left
out by their rule do not count; master elements do), and it is never taller than the page’s height, which
stays the most the page can take. Pages without the rule keep their height, as before. Only output pages
take the rule: a master page keeps its size.
Put everything that must follow the content in flowing Layouts (a footer too: an element drawn at the bottom of the page keeps the page tall). Background colours, gradients and images cover the printed size.
In the document the page carries "fitHeight": { "bottomMargin": 40 }. Set it with
PUT /draft/pages/{page_id} (fit_height_bottom_margin, clear_fit_height), MCP
edit_design_template_page (operation update), the Agent capability design_template.update_page, or the editor (Page
settings → Height follows the content); the canvas marks where the page ends with the active sample. The
draft outline reports fit_height_bottom_margin. The exact preview, the page images and the PDF all use the
printed size.
Layer folders: organising the layers
Section titled “Layer folders: organising the layers”A folder groups page elements in the Layers panel only — like the layer folders of an image editor — so they can be selected, hidden, locked, moved or deleted together while each element stays directly editable. It is not a Group: it has no position, size or effect, its elements keep their page coordinates, and it prints as if it were not there. A hidden folder prints none of its elements; a locked folder locks them in the editor (each element keeps its own visibility and lock, which apply again when the folder is shown or unlocked).
{ "type": "folder", "name": "Header", "visible": true, "locked": false, "children": [ { "type": "image", "name": "Photo", "left": 0, "top": 0, "...": "..." }, { "type": "text", "name": "Title", "left": 40, "top": 120, "...": "..." } ] }Folders sit on pages and master pages and nest in other folders (at most 8 levels); they cannot sit inside a
Group, Layout, Repeat or component, and carry no field, rule, link, effect or geometry (all zero, scale 1,
opacity 1) — DESIGN_FOLDER_INVALID otherwise. A folder is a contiguous run of the stacking order: its
elements are drawn together, bottom to top. Folders are not part of the template contract, and a Repeat
inside a folder still counts as placed directly on the page (it can continue on new pages).
The draft outline reports folders with type folder; an element in a folder has the folder as parent_id.
Every element tool keeps working on elements inside folders by id. In the editor: Layers → New folder
(with the selected elements, if any), drag rows into, out of and between folders, the folder’s eye and lock,
double-click to rename, and the folder menu to remove it (keeping its elements in place) or delete it with them.
Print color profiles and PDF/X-4 export
Section titled “Print color profiles and PDF/X-4 export”Workspace members manage printer-supplied ICC profiles centrally in Settings > Print & Color > ICC profiles. The catalog supports multiple immutable CMYK printer profiles, one workspace default, search, and logical archive. Archiving removes a profile from new export choices but does not invalidate published workflows that already pin its GUID and SHA-256. The current default must be replaced before it can be archived.
The DesignDocument export menu keeps Standard PDF as the general-purpose path and exposes PDF/X-4 as an
explicit print-ready path. The PDF/X-4 dialog preselects the workspace default, allows another active profile to
be chosen, links back to the central catalog, and offers an inline upload action for first use. Profile upload
requires an embedding-rights acknowledgement. Profile choice is per export; this bounded version does not persist
a separate document-level color-profile preference. The design/template_render workflow node stores the chosen
profile and publish pins its content hash for reproducible execution.
Development qualification
Section titled “Development qualification”After the backend containing DD7 is restarted, run the read-only gate against the published demo:
node docs/demo-workflows/design-template-render/qualify-dd7-read-dev.mjsnode docs/demo-workflows/design-template-render/qualify-dd7-versions-dev.mjsnode docs/demo-workflows/design-template-render/qualify-dd7-portable-pdf-dev.mjsnode docs/demo-workflows/design-template-render/qualify-dd7-portable-pages-dev.mjsnode docs/demo-workflows/design-template-render/qualify-dd7-portable-pages-mcp-dev.mjsAfter migration 478 is applied in Development and the API/Auth hosts are updated, the Development-only create gate makes at most one native blank draft and verifies same-key replay, different-payload conflict and draft ETag without provider calls:
node docs/demo-workflows/design-template-render/qualify-dd7-create-dev.mjsAfter migration 479 and the API restart, the Development gate adds one text element and configures one placeholder at most, checking both replay/conflict paths and stale ETag rejection with zero provider calls:
node docs/demo-workflows/design-template-render/qualify-dd7-add-text-dev.mjsnode docs/demo-workflows/design-template-render/qualify-dd7-sample-preview-dev.mjsnode docs/demo-workflows/design-template-render/qualify-dd7-publish-dev.mjsnode docs/demo-workflows/design-template-render/qualify-dd7-typed-budget-dev.mjsnode docs/demo-workflows/design-template-render/install-typed-budget-dev.mjsnode docs/demo-workflows/design-template-render/qualify-dd7-repeating-list-dev.mjsnode docs/demo-workflows/design-template-render/install-repeating-line-items-dev.mjsnode docs/demo-workflows/design-template-render/qualify-dd7-image-field-dev.mjsnode docs/demo-workflows/design-template-render/install-image-field-dev.mjsnode docs/demo-workflows/design-template-render/qualify-dd7-lifecycle-dev.mjsRun the sample/preview gate before publish. The publish gate writes at most one immutable revision
and checks same-key replay, stale ETag rejection and the published headline contract.
Run the typed-budget gate and workflow installer only after the API loads the typed-placeholder
command. The installer builds a one-page example with optional base JSON plus visible headline
and number inputs that override those JSON keys, PDF and a separate page image. Its recipe uses
only the public tpl_ selector and published revision; no Editor API lookup is needed. The
portable installer and its replay gate have passed live. The repeating-list
qualifier and installer also passed live, including two rendered rows and a
second run without duplicates. The image-box command and workflow also passed
live, including one image loaded in exact preview and a replay without
duplicates. DD7 lifecycle archive/revert and the closed-catalog pin guard have
passed live Development gates after backend restart. The internal AI Agent
lifecycle capability has offline coverage and still needs a planner gate; AI
Assistant authoring parity and the full five-surface gate remain open.