# Rynko Document Template Schema Reference for AI/LLM > **Purpose**: Authoritative reference for AI/LLM systems generating PDF and Excel document templates via MCP. > **Last Updated**: February 6, 2026 | **Version**: 4.0 --- ## QUICK START — READ THIS FIRST ### What is Rynko? Rynko is document generation infrastructure. You create **templates** (with a JSON schema), then **generate documents** by passing variable data. Templates are reusable — one template can produce thousands of documents. ### Tool Orchestration (How to Use the MCP Tools) **Creating a new document from scratch:** 1. `create_draft_template` — Build the template with schema + variables 2. `validate_schema` (optional) — Catch schema errors early before previewing 3. `preview_template` (version: "draft") — FREE preview, no credits. **Always do this before generating.** 4. Share the preview URL with the user for approval 5. `generate_document` — Production document (watermarked unless `use_credits: true`) **Generating from an existing template:** 1. `list_templates` — Find the right template 2. `get_template` — Get its variables and schema 3. `preview_template` or `generate_document` — Pass variable values **Modifying a template:** 1. `get_template` — Fetch current schema 2. `update_draft_template` — Apply changes to draft 3. `preview_template` — Verify changes look correct **Working with images/assets:** 1. `list_workspaces` — Determine workspace context (if not already known) 2. `list_assets` — Browse existing images (logos, signatures) in the workspace's team 3. `upload_asset` — Upload new image from base64 or URL 4. Use returned `assets://{id}` in template image component `src` props **Bulk generation from uploaded data:** 1. `parse_data_file` — Extract data from user's Excel/CSV 2. `map_variables` — Auto-map columns to template variables 3. Confirm mappings with user 4. `generate_document` — Call once per row/record ### Intent-to-Action Mapping | User Says | Action | |-----------|--------| | "Create/make me an invoice/receipt/label" | `create_draft_template` → `preview_template` → share preview | | "Generate a PDF from my template" | `list_templates` → `generate_document` | | "Update/change my template" | `get_template` → `update_draft_template` → `preview_template` → **share preview with user before making further changes** (never batch multiple blind updates) | | "Generate without watermark" (free tier) | Ask user: "Would you like to use document credits to generate without watermark?" If yes, `generate_document` with `use_credits: true` | | "Generate the actual document" / "Generate final PDF" | Confirm with user: "This will use 1 document from your quota. Proceed?" Then `generate_document` | | "Import my spreadsheet data" | `parse_data_file` → `map_variables` → `generate_document` | | "Show me my templates" | `list_templates` | | "Preview this first" | `preview_template` (always free) | | "Use my logo" / "Add my company logo" | `list_assets` → show options → user picks → use `assets://{id}` | | "Use this image" (attaches file) | `upload_asset` with `image_base64` → use returned `assets://{id}` | | "Use the logo from our website: https://..." | `upload_asset` with `image_url` → use returned `assets://{id}` | | "Write code to generate documents" / "How do I integrate Rynko?" / "Show me the SDK" | `get_sdk_examples` (with language filter if specified) — returns install, init, and code examples for Node.js, Python, Java, or REST API | | "I'm out of credits" / quota error | Direct user to https://app.rynko.dev/billing/document-credits | | Authentication error / invalid API key | Direct user to regenerate PAT at https://app.rynko.dev/settings?tab=tokens and update MCP config | ### UX Guidelines for AI Agents - **Always preview before generating.** Previews are free. Users appreciate seeing the result before committing. - **Confirm before generating.** Always confirm before calling `generate_document`: - **Free tier users:** Documents have watermarks by default. Ask: "Would you like to use document credits to generate without watermark?" If yes, use `use_credits: true`. - **Paid tier users:** Documents use their monthly quota. Confirm: "This will use 1 document from your monthly quota. Shall I proceed?" - **Use realistic default values.** When creating templates, populate `defaultValue` with data that looks real, not "Lorem ipsum". Use the user's actual data when available. - **Templates are drafts by default.** MCP-created templates start as drafts. Use `use_draft: true` when generating. Mention the user can publish via the web designer if needed. - **Share download links directly.** After generation, give the user the download URL from `get_job_status`. - **Images in templates.** When a user wants to include an image: 1. If they attach/provide an image: call `upload_asset` with base64 data or URL 2. If they reference an existing image: call `list_assets` to find it 3. Use the returned `assets://{id}` reference in the template's image `src` prop 4. **Never guess or fabricate Asset IDs** — always use `list_assets` or `upload_asset` ### Credits and Watermarks **Two types of watermarks exist:** 1. **Preview watermarks** — All `preview_template` calls show a preview watermark. This is expected and free. 2. **Free tier watermarks** — Free tier users get watermarks on `generate_document` output. Paid tiers do NOT have this watermark. **How generation works by tier:** - **Free tier:** Generated documents have watermarks. To remove, use `use_credits: true` (consumes 1 credit). - **Paid tiers:** Generated documents are watermark-free and count against monthly quota. No need for `use_credits: true` unless the user explicitly requests it. **When quota/credits are exhausted:** Direct users to purchase additional credits at: https://app.rynko.dev/billing/document-credits **Error Codes to Handle:** | Error Code | Message | Action | |------------|---------|--------| | `ERR_QUOTA_008` | "Monthly document generation quota exceeded" | User has used all free tier documents. Suggest: "You've reached your monthly document limit. Purchase credits at https://app.rynko.dev/billing/document-credits to continue generating documents." | | `ERR_QUOTA_011` | "Insufficient document credits" | User requested `use_credits: true` but has no credits. Suggest: "You don't have enough document credits. Purchase more at https://app.rynko.dev/billing/document-credits" | **Tip:** When you see these errors, always provide the direct link to purchase credits and explain that credits remove watermarks and allow unlimited generation. ### API Key / PAT Authentication The MCP server authenticates using a Personal Access Token (PAT). If the token is invalid, expired, or deactivated, you'll receive authentication errors. **Error Codes to Handle:** | Error Code | Message | Action | |------------|---------|--------| | `ERR_AKEY_001` | "API key not found" | The PAT doesn't exist. Ask user to generate a new one at https://app.rynko.dev/settings?tab=tokens | | `ERR_AKEY_002` | "Invalid API key" | The PAT is malformed or incorrect. Ask user to verify and regenerate at https://app.rynko.dev/settings?tab=tokens | | `ERR_AKEY_003` | "API key has been deactivated" | The PAT was manually deactivated. Ask user to create a new one at https://app.rynko.dev/settings?tab=tokens | **When you see these errors:** 1. Inform the user: "Your API key appears to be invalid or expired." 2. Direct them to generate a new PAT: https://app.rynko.dev/settings?tab=tokens 3. Remind them to update the PAT in their MCP configuration file (usually `claude_desktop_config.json` or similar) ### Working from Sample Files and Images When a user asks you to create a template, consider whether asking for a sample file or image would produce a better result. Multimodal LLMs can extract layout structure, color schemes, component types, and variable placement from images and documents. **When to ask for a sample:** | Scenario | Ask? | Why | |----------|------|-----| | "Create a template that looks like our current invoice" | **Yes** | You need to see the existing layout to replicate it | | "Make me a quotation template like this" + vague description | **Yes** | A sample anchors the design intent and removes ambiguity | | Complex multi-section layouts (certificates, reports, forms) | **Yes** | Precise positioning is hard to describe in words | | "Recreate this PDF as a template" | **Yes** | The sample IS the specification | | "I need a basic invoice with company name, items, and total" | **No** | Standard enough — a sample adds little value | | User already describes layout in detail (colors, columns, components) | **No** | The description is sufficient | | "Generate a PDF from this JSON data" | **No** | The data shape defines the structure | **How to ask:** > "Would you like to share a sample document (PDF, image, or screenshot) of what the template should look like? I can match the layout, colors, and structure from a sample much more accurately than from a description alone." **What to extract from a sample image/file:** 1. **Layout structure** — Number of columns, section arrangement, header/footer content 2. **Component types** — Tables, key-value pairs, images, barcodes, charts, dividers 3. **Visual styling** — Colors, font sizes, spacing, borders, background colors 4. **Variable placement** — Which parts are dynamic data vs static text 5. **Conditional areas** — Sections that may appear/disappear based on data (e.g., discount row, notes area) 6. **Calculations** — Totals, subtotals, tax — suggest calculated variables for these **After receiving a sample:** 1. Describe what you see: "I can see a two-column header with logo on the left and invoice details on the right, followed by a line items table with calculated totals..." 2. Identify the variables needed and their types 3. Call out any calculated fields you'll implement as calculated variables 4. Build the template, then `preview_template` so the user can compare against their sample **Tip:** Users often have existing documents they want to "templatize." The most helpful approach is: sample → identify variables → create template → preview → iterate. --- ## CRITICAL RULES 1. Variables must be a **PLAIN ARRAY** `[...]`, NOT `{ "variables": [...] }` 2. Always include `defaultValue` for ALL variables (except calculated variables — their value is computed from `expression`) 3. Use `pages` array for PDF/Excel templates 4. Use kebab-case for component IDs (e.g., `invoice-header`, `items-table`) 5. Tables **require** `tableName` property for Excel 6. Every component needs a unique `id` 7. `style` goes PARALLEL to `props`, not inside it --- ## COMMON PITFALLS (What Breaks Most Often) | Mistake | Fix | |---------|-----| | Wrapping variables in `{ "variables": [...] }` | Pass as plain array `[...]` | | Missing `defaultValue` on a variable | Every variable MUST have `defaultValue` (except calculated variables — they compute their value) | | Confusing `pageSettings.header`/`footer` with section `type: "header"`/`"footer"` | `pageSettings.header` = repeating page header (every page); section `type: "header"` = one-time content layout block | | Missing `tableName` on table component | Required for Excel; add it always | | Missing `itemType` and `schema` on array variables | Array variables need full schema definition | | Putting `style` inside `props` | `style` is a sibling of `props`, not nested | | Missing `cellRow`/`cellCol` on table-layout children | Required for grid positioning | | Using form fields in Excel templates | Form fields are PDF-only | | Forgetting `use_draft: true` when generating from unpublished template | MCP-created templates are drafts | | Not calling `preview_template` before `generate_document` | Always preview first — it's free | | Duplicate component `id` values | Every ID must be unique across the entire template | | Document has watermark | Use `use_credits: true` for production documents | | `ERR_AKEY_*` authentication errors | PAT is invalid/expired — regenerate at https://app.rynko.dev/settings?tab=tokens and update MCP config | | "Quota exceeded" or "Insufficient credits" error | Direct user to buy credits at https://app.rynko.dev/billing/document-credits | --- ## TEMPLATE STRUCTURE ### Variables Array (Plain JSON Array) > **Variable IDs** can follow any format (`v1`, `var_1`, `vc_subtotal`, etc.) — just ensure each ID is unique within the template. ```json [ { "id": "var_1", "name": "customerName", "type": "string", "required": true, "description": "Customer's full name", "defaultValue": "John Doe" }, { "id": "var_2", "name": "totalAmount", "type": "number", "required": true, "defaultValue": 1250.00, "format": { "type": "number", "preset": "CURRENCY", "currency": "USD" } }, { "id": "var_3", "name": "customer", "type": "object", "required": true, "defaultValue": { "name": "Acme Corp", "email": "billing@acme.com", "address": { "street": "123 Main St", "city": "New York" } }, "schema": { "type": "object", "properties": { "name": { "type": "string" }, "email": { "type": "string" }, "address": { "type": "object", "properties": { "street": { "type": "string" }, "city": { "type": "string" } } } } } }, { "id": "var_4", "name": "lineItems", "type": "array", "itemType": "object", "required": true, "defaultValue": [{ "description": "Widget A", "quantity": 1, "price": 29.99 }], "schema": { "type": "array", "itemType": "object", "itemSchema": { "type": "object", "properties": { "description": { "type": "string" }, "quantity": { "type": "number" }, "price": { "type": "number" } } } } } ] ``` ### Template Schema ```json { "outputFormats": ["pdf", "excel"], "pages": [ { "pageId": "page-1", "pageName": "Invoice", "pageSettings": { "format": "A4", "orientation": "portrait", "margin": { "top": "20mm", "right": "15mm", "bottom": "20mm", "left": "15mm" } }, "sections": [ { "id": "main-section", "type": "main", "props": {}, "components": [...] } ] } ] } ``` **Section types:** `"main"` (primary content area — use for most templates), `"header"` (content block at top of page, before main), `"footer"` (content block at bottom of page, after main). Most templates only need a single `"main"` section. --- ## PDF PAGE SETTINGS (pageSettings) Configure page size, orientation, margins, headers, and footers. ### Basic Page Settings ```json "pageSettings": { "format": "A4", "orientation": "portrait", "margin": { "top": "20mm", "right": "15mm", "bottom": "20mm", "left": "15mm" }, "backgroundColor": "#ffffff" } ``` **Format Options:** `A4` | `A3` | `Letter` | `Legal` | `A5` **Orientation:** `portrait` | `landscape` **Margins:** Use CSS units (`mm`, `in`, `pt`, `px`). Recommended: `15mm`-`25mm`. ### Headers and Footers Add repeating headers/footers with optional page numbers: ```json "pageSettings": { "format": "A4", "orientation": "portrait", "margin": { "top": "25mm", "bottom": "25mm", "left": "15mm", "right": "15mm" }, "header": { "enabled": true, "content": "ACME Corporation - Invoice", "align": "center", "fontSize": "10pt", "color": "#666666" }, "footer": { "enabled": true, "content": "Confidential", "align": "left", "fontSize": "9pt", "color": "#999999", "showPageNumbers": true, "pageNumberAlign": "right" } } ``` **Header/Footer Properties:** | Property | Type | Description | |----------|------|-------------| | `enabled` | boolean | Enable header/footer | | `content` | string | Text content (supports variables like `{{companyName}}`) | | `align` | `left` \| `center` \| `right` | Text alignment | | `fontSize` | string | Font size (e.g., `"10pt"`, `"12px"`) | | `color` | string | Text color (hex, e.g., `"#666666"`) | | `showPageNumbers` | boolean | Add "Page X of Y" (footer only) | | `pageNumberAlign` | `left` \| `center` \| `right` | Page number position | **Page Number Format:** When `showPageNumbers: true`, the format is `Page 1 of 5` appended to footer content. ### Example: Invoice with Header and Footer ```json "pageSettings": { "format": "Letter", "orientation": "portrait", "margin": { "top": "30mm", "bottom": "25mm", "left": "20mm", "right": "20mm" }, "header": { "enabled": true, "content": "{{companyName}}", "align": "left", "fontSize": "11pt", "color": "#333333" }, "footer": { "enabled": true, "content": "Invoice #{{invoiceNumber}} | Generated on {{invoiceDate}}", "align": "left", "fontSize": "9pt", "color": "#888888", "showPageNumbers": true, "pageNumberAlign": "right" } } ``` ### Default Page Settings Apply settings to all pages using `defaultPageSettings` at the schema root: ```json { "outputFormats": ["pdf"], "defaultPageSettings": { "format": "A4", "orientation": "portrait", "margin": { "top": "20mm", "bottom": "20mm", "left": "15mm", "right": "15mm" }, "footer": { "enabled": true, "showPageNumbers": true, "pageNumberAlign": "center" } }, "pages": [...] } ``` Individual pages can override `defaultPageSettings` by specifying their own `pageSettings`. > **Note:** Don't confuse `pageSettings.header`/`footer` (repeating page headers/footers) with section `type: "header"` or `type: "footer"` (content layout areas within a page). Use `pageSettings` for repeating elements like page numbers; use sections for one-time content blocks. --- ## VARIABLE TYPES | Type | Description | Example defaultValue | |------|-------------|---------------------| | `string` | Text value | `"Hello World"` | | `number` | Numeric value | `123.45` | | `boolean` | True/false | `true` | | `date` | Date value | `"2025-01-15"` | | `array` | List of items | `[{ "name": "Item 1" }]` | | `object` | Nested object | `{ "street": "123 Main St" }` | ### Format Presets **Number:** `DECIMAL`, `CURRENCY`, `PERCENT`, `CUSTOM` **Date:** `ISO`, `US`, `EU`, `LONG`, `SHORT`, `CUSTOM` --- ## CALCULATED VARIABLES (Powerful Feature) Calculated variables are computed at render time from other variables using JavaScript expressions. This is one of the most powerful features — use it for totals, taxes, discounts, conditional text, and more. ### Defining Calculated Variables Add `isCalculated: true` and an `expression` to any variable: ```json { "name": "subtotal", "type": "number", "isCalculated": true, "expression": "lineItems.reduce((sum, item) => sum + item.quantity * item.unitPrice, 0)", "description": "Sum of all line item totals" } ``` ### Complete Invoice Example with Calculations ```json [ { "name": "lineItems", "type": "array", "itemType": "object", "defaultValue": [ { "description": "Widget A", "quantity": 2, "unitPrice": 25.00 }, { "description": "Widget B", "quantity": 5, "unitPrice": 10.00 } ]}, { "name": "taxRate", "type": "number", "defaultValue": 0.08 }, { "name": "discountPercent", "type": "number", "defaultValue": 10 }, { "name": "subtotal", "type": "number", "isCalculated": true, "expression": "lineItems.reduce((sum, item) => sum + item.quantity * item.unitPrice, 0)" }, { "name": "discountAmount", "type": "number", "isCalculated": true, "expression": "subtotal * (discountPercent / 100)" }, { "name": "taxableAmount", "type": "number", "isCalculated": true, "expression": "subtotal - discountAmount" }, { "name": "taxAmount", "type": "number", "isCalculated": true, "expression": "taxableAmount * taxRate" }, { "name": "grandTotal", "type": "number", "isCalculated": true, "expression": "taxableAmount + taxAmount" }, { "name": "itemCount", "type": "number", "isCalculated": true, "expression": "lineItems.length" }, { "name": "hasDiscount", "type": "boolean", "isCalculated": true, "expression": "discountPercent > 0" } ] ``` ### Supported Expression Syntax **Arithmetic Operators:** `+`, `-`, `*`, `/`, `%` (modulo), `^` (power) **Comparison Operators:** `>`, `<`, `>=`, `<=`, `==`, `!=` **Logical Operators:** `&&` (and), `||` (or), `!` (not) **Ternary Operator:** ```javascript condition ? valueIfTrue : valueIfFalse // Example: Show "PAID" or "UNPAID" isPaid ? "PAID" : "UNPAID" // Example: Apply discount only if above threshold subtotal > 100 ? subtotal * 0.9 : subtotal ``` **Math Functions:** ```javascript Math.abs(x) // Absolute value Math.ceil(x) // Round up Math.floor(x) // Round down Math.round(x) // Round to nearest Math.sqrt(x) // Square root Math.pow(x, y) // x to the power of y Math.min(a, b, c) // Minimum value Math.max(a, b, c) // Maximum value Math.log(x) // Natural logarithm Math.log10(x) // Base-10 logarithm Math.exp(x) // e^x Math.random() // Random 0-1 // Trigonometric Math.sin(x), Math.cos(x), Math.tan(x) Math.asin(x), Math.acos(x), Math.atan(x), Math.atan2(y, x) ``` **String Methods:** ```javascript text.toUpperCase() // "hello" → "HELLO" text.toLowerCase() // "HELLO" → "hello" text.trim() // " hello " → "hello" text.length // "hello" → 5 ``` **Array Methods (with arrow functions):** ```javascript // Sum all values items.reduce((sum, item) => sum + item, 0) // Sum property values lineItems.reduce((total, item) => total + item.price, 0) // Transform array prices.map(p => p * 1.1) // Filter array items.filter(item => item.quantity > 0) // Find first match products.find(p => p.sku == "ABC123") // Check conditions items.some(item => item.price > 100) // Any item > 100? items.every(item => item.price > 0) // All items > 0? // Chain methods lineItems.filter(i => i.active).map(i => i.total).reduce((sum, t) => sum + t, 0) ``` **Property Access:** ```javascript customer.name // Simple property customer.address.city // Nested property lineItems[0].description // Array index + property lineItems.length // Array length ``` ### Common Calculation Patterns ```javascript // Round to 2 decimal places Math.round(total * 100) / 100 // Percentage (part / whole) * 100 // Tax calculation subtotal * taxRate // Discount originalPrice * (1 - discountPercent / 100) // Conditional text status == "active" ? "Active" : "Inactive" // Fallback value value || "N/A" // Count items meeting condition items.filter(i => i.type == "premium").length ``` --- ## CALCULATED TABLE COLUMNS Tables support calculated columns that compute values for each row. **Calculated variable vs calculated table column — when to use which:** | Use | When | Example | |-----|------|---------| | **Calculated variable** | Value computed once per document from other variables | Subtotal, tax amount, grand total, item count, conditional flags | | **Calculated table column** | Value computed per row within a table | Line total (`row.quantity * row.unitPrice`), row-level status text | Rule of thumb: if you need `row.` to access data, it's a table column. If you're aggregating across rows or computing from top-level variables, it's a calculated variable. ### PDF Tables: JavaScript Expressions Use `isCalculated: true` with `expression`. Access row data via `row.fieldName`: ```json { "type": "table", "props": { "dataSource": "{{lineItems}}", "columns": [ { "header": "Description", "field": "description", "width": "40%" }, { "header": "Qty", "field": "quantity", "width": "15%", "align": "center" }, { "header": "Unit Price", "field": "unitPrice", "width": "20%", "align": "right", "format": { "type": "number", "preset": "CURRENCY", "currency": "USD" } }, { "header": "Line Total", "field": "lineTotal", "width": "25%", "align": "right", "isCalculated": true, "expression": "row.quantity * row.unitPrice", "format": { "type": "number", "preset": "CURRENCY", "currency": "USD" } } ] } } ``` **Key points:** - Use `row.` prefix to access current row data - `expression` is JavaScript (same syntax as calculated variables) - Works in PDF output ### Excel Tables: Excel Formulas Use `excelFormula` for native Excel formulas with structured references: ```json { "type": "table", "props": { "dataSource": "{{lineItems}}", "tableName": "InvoiceItems", "columns": [ { "header": "Description", "field": "description", "width": "40%" }, { "header": "Quantity", "field": "quantity", "width": "15%", "align": "center" }, { "header": "Unit Price", "field": "unitPrice", "width": "20%", "align": "right", "format": { "type": "number", "preset": "CURRENCY" } }, { "header": "Line Total", "field": "lineTotal", "width": "25%", "align": "right", "excelFormula": "=[@Quantity]*[@[Unit Price]]", "format": { "type": "number", "preset": "CURRENCY" } } ] } } ``` **Excel Structured Reference Syntax:** ``` [@ColumnName] - Current row, column value [@[Column Name]] - Column name with spaces (use brackets) TableName[ColumnName] - Entire column TableName[[#Totals],[ColumnName]] - Totals row value ``` **Common Excel Formulas:** ``` =[@Quantity]*[@Price] - Multiply columns =[@Subtotal]*0.1 - Calculate 10% tax =IF([@Status]="Active","Yes","No") - Conditional =SUM(TableName[Amount]) - Sum entire column =ROUND([@Amount],2) - Round to 2 decimals ``` ### Using Both (PDF + Excel) For templates that output both PDF and Excel, provide both: ```json { "header": "Line Total", "field": "lineTotal", "isCalculated": true, "expression": "row.quantity * row.unitPrice", "excelFormula": "=[@Quantity]*[@[Unit Price]]", "format": { "type": "number", "preset": "CURRENCY" } } ``` - PDF uses `expression` - Excel uses `excelFormula` --- ## COMPONENT REFERENCE ### Available Components (28 Types) | Component | Type | PDF | Excel | Description | |-----------|------|-----|-------|-------------| | `heading` | Text | ✓ | ✓ | H1-H6 headings | | `text` | Text | ✓ | ✓ | Styled text (simple or rich with runs) | | `image` | Media | ✓ | ✓ | Image from URL, base64, or asset reference | | `qr-code` | Media | ✓ | ✓ | QR code generation | | `barcode` | Media | ✓ | ✓ | Various barcode formats (10 types) | | `svg` | Media | ✓ | ✓ | External SVG graphics (charts, diagrams) | | `table` | Data | ✓ | ✓ | Data table from array | | `chart` | Data | ✓ | ✓ | Data visualization (8 chart types) | | `key-value` | Data | ✓ | ✓ | Label-value pairs | | `list` | Data | ✓ | ✓ | Bulleted/numbered list | | `container` | Layout | ✓ | ✓ | Wrapper with styling | | `columns` | Layout | ✓ | ✓ | Multi-column layout | | `table-layout` | Layout | ✓ | ✓ | Grid layout with cells | | `loop` | Logic | ✓ | ✓ | Repeat for array items | | `conditional` | Logic | ✓ | ✓ | Show/hide based on condition | | `divider` | Style | ✓ | ✓ | Horizontal line | | `spacer` | Style | ✓ | ✓ | Vertical spacing | | `page-break` | Style | ✓ | ✓ | Force page break | | `formula` | Excel | ✗ | ✓ | Excel formula cell | | `form-text` | Form | ✓ | ✗ | Single-line text input | | `form-textarea` | Form | ✓ | ✗ | Multi-line text input | | `form-checkbox` | Form | ✓ | ✗ | Boolean checkbox | | `form-radio` | Form | ✓ | ✗ | Radio button group | | `form-dropdown` | Form | ✓ | ✗ | Select dropdown | | `form-date` | Form | ✓ | ✗ | Date picker | | `form-signature` | Form | ✓ | ✗ | Signature placeholder | | `form-button` | Form | ✓ | ✗ | Action button | | `form-image` | Form | ✓ | ✗ | Image field for forms | **Note:** For rich text formatting, use the `text` component with `runs` array (see Text Rich with Runs example below). ### Component Selection Guide Use this to decide which component to use: | Need | Use | Not | |------|-----|-----| | Dynamic list of items with columns | `table` with `dataSource` | `loop` with manual layout | | Static label-value pairs (Invoice #, Date, etc.) | `key-value` | `table` or `text` | | Repeating complex blocks (product cards, sections) | `loop` | Multiple copy-pasted containers | | Side-by-side layout | `columns` | `table-layout` (unless you need borders) | | Grid with visible borders/cells | `table-layout` | `columns` | | Show/hide based on data | `conditional` | Separate templates | | Totals row under a data table | `key-value` below `table` | Another `table` | | Multi-line styled text with mixed formatting | `text` with `runs` | Multiple `text` components | --- ## COMPONENT STYLING Components can have an optional `style` object at the component level (parallel to `props`, not inside it). Style properties take precedence over component-specific props. ```json { "id": "styled-container", "type": "container", "props": { "gap": "10px" }, "style": { "width": "90%", "maxWidth": "100%", "align": "center", "backgroundColor": "#ffffff", "padding": "20px", "margin": "0px", "borderWidth": "2px", "borderColor": "#3b82f6", "borderRadius": "8px" }, "children": [...] } ``` ### Supported Style Properties | Property | Type | Description | |----------|------|-------------| | `width` | string/number | Component width. Supports percentages (e.g., `"90%"`) | | `maxWidth` | string/number | Maximum width constraint. Supports percentages | | `align` | `"left"` / `"center"` / `"right"` | Horizontal alignment within parent container | | `padding` | string/number | Inner padding (e.g., `"20px"`, `20`) | | `paddingTop` | string/number | Top padding | | `paddingRight` | string/number | Right padding | | `paddingBottom` | string/number | Bottom padding | | `paddingLeft` | string/number | Left padding | | `margin` | string/number | Outer margin | | `marginTop` | string/number | Top margin | | `marginRight` | string/number | Right margin | | `marginBottom` | string/number | Bottom margin | | `marginLeft` | string/number | Left margin | | `backgroundColor` | string | Background color (e.g., `"#ffffff"`, `"transparent"`) | | `borderWidth` | string/number | Border thickness (e.g., `"2px"`, `2`) | | `borderColor` | string | Border color (e.g., `"#3b82f6"`) | | `borderRadius` | string/number | Rounded corners (e.g., `"8px"`, `8`) | **Important Notes:** - Empty string values (e.g., `"width": ""`) are ignored and defaults are used - Style properties override the same properties in `props` - Percentage widths are calculated relative to the parent container's available width --- ## COMPONENT EXAMPLES ### Heading ```json { "id": "invoice-title", "type": "heading", "content": "Invoice #{{invoiceNumber}}", "props": { "level": 1, "color": "#333333", "textAlign": "center" } } ``` ### Text (Simple) ```json { "id": "description", "type": "text", "content": "Thank you for your business, {{customerName}}!", "props": { "fontSize": 14, "fontWeight": "normal", "color": "#333333", "textAlign": "left" } } ``` ### Text (Rich with Runs) ```json { "id": "formatted-text", "type": "text", "runs": [ { "text": "Important: ", "bold": true, "color": "#dc2626" }, { "text": "Your order ", "color": "#333333" }, { "text": "#{{orderNumber}}", "bold": true, "color": "#2563eb" }, { "text": " has been confirmed.", "color": "#333333" } ], "props": { "fontSize": 14, "textAlign": "left" } } ``` **TextRun Properties:** `text` (required), `bold`, `italic`, `underline`, `strikethrough`, `color`, `backgroundColor`, `fontSize`, `link` ### Image ```json { "id": "company-logo", "type": "image", "props": { "src": "{{logoUrl}}", "alt": "Company Logo", "width": 200, "height": 60, "align": "left" } } ``` **Image Props:** | Property | Type | Default | Description | |----------|------|---------|-------------| | `src` | string | Required | Image URL, data URI, or asset reference (`assets://asset-id`) | | `alt` | string | - | Alt text for accessibility | | `width` | string/number | - | Width in pixels or percentage | | `height` | string/number | - | Height in pixels or percentage | | `align` | `"left"` / `"center"` / `"right"` | `"left"` | Horizontal alignment | **Using Asset Library:** Upload images at https://app.rynko.dev/assets, then reference with `"src": "assets://your-asset-id"` **Style Support:** Image components support common style attributes via the `style` object: - `backgroundColor` - Background color behind the image - `borderColor`, `borderWidth`, `borderRadius` - Border styling - `margin`, `marginTop`, `marginBottom`, `marginLeft`, `marginRight` - Spacing **Supported Formats:** PNG, JPEG, GIF, WebP, SVG (auto-converted to PNG) ### Table (REQUIRED: tableName for Excel) ```json { "id": "items-table", "type": "table", "props": { "dataSource": "{{lineItems}}", "tableName": "InvoiceItems", "tableStyle": "TableStyleMedium9", "columns": [ { "header": "Description", "field": "description", "width": "50%", "align": "left" }, { "header": "Qty", "field": "quantity", "width": "15%", "align": "center" }, { "header": "Unit Price", "field": "unitPrice", "width": "17.5%", "align": "right", "format": { "type": "number", "preset": "CURRENCY", "currency": "USD" } }, { "header": "Total", "field": "total", "width": "17.5%", "align": "right", "format": { "type": "number", "preset": "CURRENCY", "currency": "USD" } } ], "headerBackgroundColor": "#2563eb", "headerTextColor": "#ffffff", "alternatingRows": true } } ``` **Calculated Columns:** ```json { "header": "Line Total", "isCalculated": true, "expression": "row.quantity * row.unitPrice", "align": "right", "format": { "type": "number", "preset": "CURRENCY" } } ``` **Excel Formula Columns:** ```json { "header": "Extended Price", "excelFormula": "=[@Quantity]*[@[Unit Price]]", "align": "right", "format": { "type": "number", "preset": "CURRENCY" } } ``` ### Chart ```json { "id": "sales-chart", "type": "chart", "props": { "chartType": "bar", "dataSource": "{{salesData}}", "xAxis": "month", "yAxis": "revenue", "title": "Monthly Revenue", "width": 600, "height": 400, "colors": ["#2563eb", "#10b981", "#f59e0b"], "showLegend": true } } ``` **Chart Types (8):** `bar`, `line`, `pie`, `doughnut` (or `donut`), `area`, `radar`, `polarArea`, `scatter` ### QR Code ```json { "id": "ticket-qr", "type": "qr-code", "props": { "value": "{{ticketUrl}}", "size": 150, "errorCorrection": "M", "align": "center" } } ``` ### Barcode ```json { "id": "product-barcode", "type": "barcode", "props": { "barcodeType": "code128", "value": "{{productCode}}", "width": 200, "height": 80, "includeText": true } } ``` **Barcode Types (10):** `qrcode`, `code128`, `code39`, `ean13`, `ean8`, `upca`, `upce`, `itf14`, `pdf417`, `datamatrix` ### SVG (External Graphics) SVG components support two input methods: 1. **`src` prop** - Reference to an SVG file (URL, asset, or data URI) 2. **`content` prop** - Inline SVG string (raw or base64-encoded) **Using src (recommended for files):** ```json { "id": "logo-svg", "type": "svg", "props": { "src": "assets://svg-logo-asset-id", "width": 200, "height": 100, "align": "center" } } ``` **Using content (for inline SVG):** ```json { "id": "custom-chart", "type": "svg", "props": { "content": "{{chartSvg}}", "encoding": "base64", "width": 500, "height": 300, "align": "center", "preserveAspectRatio": "xMidYMid meet" } } ``` **SVG Props:** | Property | Type | Default | Description | |----------|------|---------|-------------| | `src` | string | - | SVG file reference: `assets://id`, HTTP URL, or `data:image/svg+xml;base64,...` | | `content` | string | - | Inline SVG markup or variable containing SVG (used if `src` not provided) | | `encoding` | `"raw"` / `"base64"` | `"raw"` | How the content is encoded (only for `content` prop) | | `width` | string/number | 200 | Width in pixels or percentage | | `height` | string/number | 150 | Height in pixels or percentage | | `align` | `"left"` / `"center"` / `"right"` | `"left"` | Horizontal alignment | | `preserveAspectRatio` | string | - | SVG aspect ratio preservation | **Note:** Either `src` or `content` is required. If both are provided, `src` takes precedence. **Style Support:** SVG components support common style attributes via the `style` object: - `backgroundColor` - Background color behind the SVG - `borderColor`, `borderWidth`, `borderRadius` - Border styling - `margin`, `marginTop`, `marginBottom`, `marginLeft`, `marginRight` - Spacing - `padding`, `paddingTop`, `paddingBottom`, `paddingLeft`, `paddingRight` - Inner spacing **Use Cases:** - Charts generated by Python (matplotlib, plotly, seaborn) - Data visualizations from R (ggplot2) - Custom barcodes from specialized libraries - Architecture diagrams from D3.js or other tools - SVG logos and icons from asset library (upload at https://app.rynko.dev/assets) **Security:** All SVG content is sanitized before rendering. Scripts, event handlers, and external URLs are removed. **Allowed image sources within SVG:** - Base64 data URIs: `data:image/png;base64,...` - Rynko assets: `assets://asset_id` - Internal references: `#id` ### Container ```json { "id": "info-box", "type": "container", "props": { "backgroundColor": "#f3f4f6", "padding": "20px", "borderRadius": "8px", "name": "InfoSection" }, "children": [...] } ``` ### Columns ```json { "id": "two-column-layout", "type": "columns", "props": { "columnCount": 2, "columnWidths": ["50%", "50%"], "gap": "20px" }, "children": [ { "id": "left-column", "type": "container", "children": [...] }, { "id": "right-column", "type": "container", "children": [...] } ] } ``` ### Table Layout (IMPORTANT: cellRow/cellCol REQUIRED) ```json { "id": "info-grid", "type": "table-layout", "props": { "rows": 2, "cols": 2, "borderWidth": "1px", "borderColor": "#dddddd", "cellPadding": "12px" }, "children": [ { "id": "cell-1", "type": "text", "cellRow": 0, "cellCol": 0, "content": "Label 1" }, { "id": "cell-2", "type": "text", "cellRow": 0, "cellCol": 1, "content": "{{value1}}" }, { "id": "cell-3", "type": "text", "cellRow": 1, "cellCol": 0, "content": "Label 2" }, { "id": "cell-4", "type": "text", "cellRow": 1, "cellCol": 1, "content": "{{value2}}" } ] } ``` ### Key-Value ```json { "id": "invoice-details", "type": "key-value", "props": { "items": [ { "label": "Invoice #", "value": "{{invoiceNumber}}" }, { "label": "Date", "value": "{{invoiceDate}}" }, { "label": "Due Date", "value": "{{dueDate}}" } ], "layout": "horizontal", "labelWidth": "40%", "labelColor": "#666666", "valueColor": "#111827", "separator": ":" } } ``` **Key-Value Props:** | Property | Type | Default | Description | |----------|------|---------|-------------| | `items` | array | Required | Array of `{ label, value }` objects | | `layout` | `"horizontal"` / `"vertical"` | `"vertical"` | Label-value arrangement | | `labelWidth` | string/number | `"40%"` | Width of label column. **Supports percentages** | | `labelColor` | string | `"#374151"` | Label text color | | `valueColor` | string | `"#111827"` | Value text color | | `separator` | string | none | Character between label and value (e.g., `":"`) | | `spacing` | string/number | `10` | Vertical spacing between items | **IMPORTANT:** When using `layout: "horizontal"`, always set `labelWidth` appropriately. Adjust based on label lengths: `"30%"` for short labels, `"50%"` for longer labels. ### List ```json { "id": "features-list", "type": "list", "props": { "ordered": false, "items": ["Feature 1", "Feature 2", "Feature 3"] } } ``` **With Data Source:** ```json { "id": "dynamic-list", "type": "list", "props": { "ordered": true, "dataSource": "{{items}}", "itemTemplate": "{{item.name}} - {{item.description}}" } } ``` ### Loop ```json { "id": "product-loop", "type": "loop", "props": { "dataSource": "{{products}}", "itemVariable": "product", "indexVariable": "i" }, "children": [ { "id": "product-card", "type": "container", "props": { "padding": "10px", "backgroundColor": "#f5f5f5" }, "children": [ { "id": "product-name", "type": "heading", "props": { "level": 3 }, "content": "{{product.name}}" }, { "id": "product-price", "type": "text", "content": "Price: {{product.price}}" } ] } ] } ``` ### Conditional ```json { "id": "discount-section", "type": "conditional", "props": { "condition": "discountPercent > 0" }, "children": [ { "id": "discount-text", "type": "text", "content": "You saved {{discountPercent}}%!", "props": { "color": "#10b981" } } ] } ``` **Condition Examples:** `hasDiscount`, `orderTotal > 100`, `items.length > 0`, `status === 'approved'` ### Divider ```json { "id": "section-divider", "type": "divider", "props": { "color": "#e5e7eb", "thickness": "1px" } } ``` ### Spacer ```json { "id": "vertical-space", "type": "spacer", "props": { "height": "30px" } } ``` ### Page Break ```json { "id": "page-break-1", "type": "page-break" } ``` ### Formula (Excel Only) ```json { "id": "grand-total", "type": "formula", "props": { "formula": "=SUM(InvoiceItems[Amount])", "label": "Grand Total", "format": { "type": "number", "preset": "CURRENCY" } } } ``` --- ## MULTI-PAGE TEMPLATES Templates can span multiple pages in two ways: **1. Multiple entries in the `pages` array** — Each entry becomes a separate page (PDF) or sheet (Excel): ```json { "pages": [ { "pageId": "page-1", "pageName": "Invoice", "pageSettings": { "format": "A4", "orientation": "portrait" }, "sections": [...] }, { "pageId": "page-2", "pageName": "Terms & Conditions", "pageSettings": { "format": "A4", "orientation": "portrait" }, "sections": [...] } ] } ``` - In **PDF**: Each page entry starts on a new page. Content that overflows a page automatically continues to the next physical page. - In **Excel**: Each page entry becomes a separate worksheet tab, with the `pageName` as the tab name. **2. `page-break` component** — Force a page break within a single page's content: ```json { "id": "break-before-terms", "type": "page-break" } ``` Use this when the content is logically one section but you want to control where the break falls (e.g., totals on one page, terms on the next). **When to use which:** | Approach | Use when | |----------|----------| | Multiple `pages` entries | Content is logically distinct (invoice + T&C, cover + report + appendix) or you need separate Excel sheets | | `page-break` component | Content is one logical flow but you want a visual break at a specific point | --- ## PDF FORM FIELDS (PDF Only) Interactive form fields for fillable PDF documents. Available on Growth tier and above. | Type | Component Type | Description | |------|----------------|-------------| | Text Field | `form-text` | Single-line text input | | Textarea | `form-textarea` | Multi-line text input | | Checkbox | `form-checkbox` | Boolean checkbox | | Radio | `form-radio` | Radio button group | | Dropdown | `form-dropdown` | Select dropdown | | Date | `form-date` | Date picker | | Signature | `form-signature` | Signature placeholder | | Button | `form-button` | Action button | | Image | `form-image` | Image field for forms | ### Text Field Example ```json { "id": "customer-name-field", "type": "form-text", "props": { "name": "customerName", "label": "Customer Name", "required": true, "defaultValue": "{{customerName}}", "width": 200 } } ``` ### Checkbox Example ```json { "id": "agree-terms", "type": "form-checkbox", "props": { "name": "agreeToTerms", "label": "I agree to the terms and conditions", "checked": false } } ``` ### Dropdown Example ```json { "id": "country-select", "type": "form-dropdown", "props": { "name": "country", "label": "Country", "options": [ { "label": "United States", "value": "US" }, { "label": "Canada", "value": "CA" }, { "label": "United Kingdom", "value": "UK" }, { "label": "Australia", "value": "AU" } ], "defaultValue": "US" } } ``` ### Form Image Example ```json { "id": "form-company-logo", "type": "form-image", "props": { "name": "company_logo", "src": "{{logoUrl}}", "width": 200, "height": 80, "alt": "Company Logo" } } ``` --- ## EXCEL-SPECIFIC FEATURES ### Excel Tables (tableName on table component) - Creates Excel Table (ListObject) with structured references - **REQUIRED** for table components in Excel templates - Rules: Start with letter/underscore, no spaces, 1-255 chars - Valid: `OrderItems`, `Invoice_Lines`, `Data2024` - Invalid: `Order Items`, `2024Data`, `@items` ### Named Ranges (name prop) - Creates named range for cell/range reference in formulas - Supported on: `container`, `columns`, `table-layout`, `text`, `heading`, `key-value`, `image`, `chart`, `formula` ### Structured References in Formulas - `[@ColumnHeader]` - Same row value - `[@[Column Header]]` - Column with spaces - `TableName[ColumnHeader]` - Entire column - `TableName[[#Totals],[ColumnHeader]]` - Totals row ### Table Styles - Light: `TableStyleLight1-21` - Medium: `TableStyleMedium1-28` - Dark: `TableStyleDark1-11` --- ## SYSTEM VARIABLES Auto-populated at render time (no need to define these as variables): - `__CURRENT_DATE__`, `__CURRENT_YEAR__`, `__CURRENT_MONTH__`, `__CURRENT_DAY__` - `__CURRENT_USER_NAME__`, `__CURRENT_USER_EMAIL__`, `__CURRENT_TEAM_NAME__` - `__COMPANY_NAME__`, `__TEMPLATE_ID__`, `__TEMPLATE_NAME__` --- ## VALIDATION CHECKLIST Before outputting a template, verify: - [ ] Variables are a **plain array** `[...]` - [ ] Every variable has `name`, `type`, and `defaultValue` (except calculated variables) - [ ] Array variables have `itemType` and `schema.itemSchema.properties` - [ ] Object variables have `schema.properties` - [ ] Template has `outputFormats` and `pages` arrays - [ ] Every page has `pageId`, `pageName`, and `sections` - [ ] Every component has a unique `id` - [ ] Variable references use `{{variableName}}` syntax - [ ] **Table components have `tableName` property** - [ ] `table-layout` children have `cellRow` and `cellCol` - [ ] Form fields are only used in PDF-only templates - [ ] `style` is parallel to `props`, not nested inside it --- ## COMPLETE EXAMPLES ### Example 1: Invoice Template **Variables:** ```json [ { "id": "v1", "name": "companyName", "type": "string", "required": true, "defaultValue": "Acme Corporation" }, { "id": "v2", "name": "companyLogo", "type": "string", "defaultValue": "https://example.com/logo.png" }, { "id": "v3", "name": "invoiceNumber", "type": "string", "required": true, "defaultValue": "INV-2025-001" }, { "id": "v4", "name": "invoiceDate", "type": "date", "required": true, "defaultValue": "2025-01-15", "format": { "type": "date", "preset": "US" } }, { "id": "v5", "name": "customerName", "type": "string", "required": true, "defaultValue": "John Smith" }, { "id": "v6", "name": "lineItems", "type": "array", "itemType": "object", "required": true, "defaultValue": [ { "description": "Consulting Services", "quantity": 10, "rate": 150.00 }, { "description": "Software License", "quantity": 1, "rate": 500.00 } ], "schema": { "type": "array", "itemType": "object", "itemSchema": { "type": "object", "properties": { "description": { "type": "string" }, "quantity": { "type": "number" }, "rate": { "type": "number" } } } } }, { "id": "v7", "name": "taxRate", "type": "number", "defaultValue": 0.08 } ] ``` **Schema:** ```json { "outputFormats": ["pdf", "excel"], "pages": [ { "pageId": "page-1", "pageName": "Invoice", "pageSettings": { "format": "A4", "orientation": "portrait", "margin": { "top": "20mm", "right": "15mm", "bottom": "20mm", "left": "15mm" } }, "sections": [ { "id": "main-section", "type": "main", "props": {}, "components": [ { "id": "header-columns", "type": "columns", "props": { "columnCount": 2, "columnWidths": ["60%", "40%"] }, "children": [ { "id": "company-info", "type": "container", "children": [ { "id": "logo", "type": "image", "props": { "src": "{{companyLogo}}", "alt": "Logo", "width": 150 } }, { "id": "company-name", "type": "heading", "props": { "level": 2 }, "content": "{{companyName}}" } ] }, { "id": "invoice-info", "type": "key-value", "props": { "items": [ { "label": "Invoice #", "value": "{{invoiceNumber}}" }, { "label": "Date", "value": "{{invoiceDate}}" } ], "layout": "vertical" } } ] }, { "id": "spacer-1", "type": "spacer", "props": { "height": "20px" } }, { "id": "bill-to", "type": "container", "props": { "backgroundColor": "#f8f9fa", "padding": "15px" }, "children": [ { "id": "bill-label", "type": "text", "content": "BILL TO:", "props": { "fontWeight": "bold" } }, { "id": "customer", "type": "text", "content": "{{customerName}}" } ] }, { "id": "spacer-2", "type": "spacer", "props": { "height": "20px" } }, { "id": "items-table", "type": "table", "props": { "dataSource": "{{lineItems}}", "tableName": "InvoiceItems", "tableStyle": "TableStyleMedium9", "columns": [ { "header": "Description", "field": "description", "width": "50%" }, { "header": "Qty", "field": "quantity", "width": "15%", "align": "center" }, { "header": "Rate", "field": "rate", "width": "17%", "align": "right", "format": { "type": "number", "preset": "CURRENCY", "currency": "USD" } }, { "header": "Amount", "isCalculated": true, "expression": "row.quantity * row.rate", "width": "18%", "align": "right", "format": { "type": "number", "preset": "CURRENCY", "currency": "USD" } } ], "headerBackgroundColor": "#2563eb", "headerTextColor": "#ffffff" } } ] } ] } ] } ``` ### Example 2: Shipping Label with QR Code **Variables:** ```json [ { "id": "v1", "name": "trackingNumber", "type": "string", "required": true, "defaultValue": "TRK-98765432" }, { "id": "v2", "name": "senderName", "type": "string", "required": true, "defaultValue": "Warehouse A" }, { "id": "v3", "name": "senderAddress", "type": "string", "required": true, "defaultValue": "100 Industrial Blvd, Austin TX 78701" }, { "id": "v4", "name": "recipientName", "type": "string", "required": true, "defaultValue": "Jane Cooper" }, { "id": "v5", "name": "recipientAddress", "type": "string", "required": true, "defaultValue": "456 Oak Lane, Portland OR 97201" }, { "id": "v6", "name": "weight", "type": "string", "defaultValue": "2.5 kg" }, { "id": "v7", "name": "trackingUrl", "type": "string", "defaultValue": "https://track.example.com/TRK-98765432" } ] ``` **Schema:** ```json { "outputFormats": ["pdf"], "pages": [ { "pageId": "page-1", "pageName": "Label", "pageSettings": { "format": "A5", "orientation": "landscape", "margin": { "top": "10mm", "right": "10mm", "bottom": "10mm", "left": "10mm" } }, "sections": [ { "id": "main-section", "type": "main", "props": {}, "components": [ { "id": "label-header", "type": "columns", "props": { "columnCount": 2, "columnWidths": ["70%", "30%"] }, "children": [ { "id": "tracking-col", "type": "container", "children": [ { "id": "tracking-label", "type": "text", "props": { "fontSize": 10, "color": "#666" }, "content": "TRACKING NUMBER" }, { "id": "tracking-num", "type": "heading", "props": { "level": 2, "color": "#111" }, "content": "{{trackingNumber}}" }, { "id": "tracking-barcode", "type": "barcode", "props": { "barcodeType": "code128", "value": "{{trackingNumber}}", "width": 250, "height": 60, "includeText": false } } ] }, { "id": "qr-col", "type": "container", "children": [ { "id": "tracking-qr", "type": "qr-code", "props": { "value": "{{trackingUrl}}", "size": 120, "align": "center" } } ] } ] }, { "id": "label-divider", "type": "divider", "props": { "color": "#000", "thickness": "2px" } }, { "id": "addresses", "type": "columns", "props": { "columnCount": 2, "columnWidths": ["50%", "50%"] }, "children": [ { "id": "from-box", "type": "container", "props": { "padding": "10px" }, "children": [ { "id": "from-label", "type": "text", "props": { "fontSize": 9, "color": "#666", "fontWeight": "bold" }, "content": "FROM" }, { "id": "from-name", "type": "text", "props": { "fontSize": 12, "fontWeight": "bold" }, "content": "{{senderName}}" }, { "id": "from-addr", "type": "text", "props": { "fontSize": 11, "color": "#444" }, "content": "{{senderAddress}}" } ] }, { "id": "to-box", "type": "container", "props": { "padding": "10px", "backgroundColor": "#f0f0f0", "borderRadius": "4px" }, "children": [ { "id": "to-label", "type": "text", "props": { "fontSize": 9, "color": "#666", "fontWeight": "bold" }, "content": "TO" }, { "id": "to-name", "type": "heading", "props": { "level": 3 }, "content": "{{recipientName}}" }, { "id": "to-addr", "type": "text", "props": { "fontSize": 12 }, "content": "{{recipientAddress}}" } ] } ] }, { "id": "weight-info", "type": "text", "props": { "fontSize": 11, "textAlign": "right", "color": "#666" }, "content": "Weight: {{weight}}" } ] } ] } ] } ``` ### Example 3: Monthly Report with Chart and Conditional Sections **Variables:** ```json [ { "id": "v1", "name": "reportTitle", "type": "string", "required": true, "defaultValue": "Q1 2025 Performance Report" }, { "id": "v2", "name": "reportDate", "type": "string", "required": true, "defaultValue": "March 31, 2025" }, { "id": "v3", "name": "totalRevenue", "type": "number", "required": true, "defaultValue": 125000, "format": { "type": "number", "preset": "CURRENCY", "currency": "USD" } }, { "id": "v4", "name": "totalExpenses", "type": "number", "required": true, "defaultValue": 89000, "format": { "type": "number", "preset": "CURRENCY", "currency": "USD" } }, { "id": "v5", "name": "netProfit", "type": "number", "required": true, "defaultValue": 36000, "format": { "type": "number", "preset": "CURRENCY", "currency": "USD" } }, { "id": "v6", "name": "showChart", "type": "boolean", "defaultValue": true }, { "id": "v7", "name": "monthlyData", "type": "array", "itemType": "object", "required": true, "defaultValue": [ { "month": "January", "revenue": 38000, "expenses": 28000 }, { "month": "February", "revenue": 42000, "expenses": 30000 }, { "month": "March", "revenue": 45000, "expenses": 31000 } ], "schema": { "type": "array", "itemType": "object", "itemSchema": { "type": "object", "properties": { "month": { "type": "string" }, "revenue": { "type": "number" }, "expenses": { "type": "number" } } } } }, { "id": "v8", "name": "hasWarnings", "type": "boolean", "defaultValue": false }, { "id": "v9", "name": "warningMessage", "type": "string", "defaultValue": "" } ] ``` **Schema:** ```json { "outputFormats": ["pdf"], "pages": [ { "pageId": "page-1", "pageName": "Report", "pageSettings": { "format": "A4", "orientation": "portrait", "margin": { "top": "25mm", "right": "20mm", "bottom": "25mm", "left": "20mm" } }, "sections": [ { "id": "main-section", "type": "main", "props": {}, "components": [ { "id": "report-title", "type": "heading", "props": { "level": 1, "color": "#1e3a5f", "textAlign": "center" }, "content": "{{reportTitle}}" }, { "id": "report-date", "type": "text", "props": { "fontSize": 12, "color": "#666", "textAlign": "center" }, "content": "Generated: {{reportDate}}" }, { "id": "title-divider", "type": "divider", "props": { "color": "#1e3a5f", "thickness": "2px" } }, { "id": "spacer-1", "type": "spacer", "props": { "height": "20px" } }, { "id": "kpi-section", "type": "columns", "props": { "columnCount": 3, "columnWidths": ["33%", "34%", "33%"], "gap": "15px" }, "children": [ { "id": "revenue-box", "type": "container", "props": { "backgroundColor": "#e8f5e9", "padding": "15px", "borderRadius": "8px" }, "children": [ { "id": "rev-label", "type": "text", "props": { "fontSize": 10, "color": "#666" }, "content": "TOTAL REVENUE" }, { "id": "rev-value", "type": "heading", "props": { "level": 2, "color": "#2e7d32" }, "content": "${{totalRevenue}}" } ] }, { "id": "expense-box", "type": "container", "props": { "backgroundColor": "#fce4ec", "padding": "15px", "borderRadius": "8px" }, "children": [ { "id": "exp-label", "type": "text", "props": { "fontSize": 10, "color": "#666" }, "content": "TOTAL EXPENSES" }, { "id": "exp-value", "type": "heading", "props": { "level": 2, "color": "#c62828" }, "content": "${{totalExpenses}}" } ] }, { "id": "profit-box", "type": "container", "props": { "backgroundColor": "#e3f2fd", "padding": "15px", "borderRadius": "8px" }, "children": [ { "id": "profit-label", "type": "text", "props": { "fontSize": 10, "color": "#666" }, "content": "NET PROFIT" }, { "id": "profit-value", "type": "heading", "props": { "level": 2, "color": "#1565c0" }, "content": "${{netProfit}}" } ] } ] }, { "id": "spacer-2", "type": "spacer", "props": { "height": "25px" } }, { "id": "chart-conditional", "type": "conditional", "props": { "condition": "showChart" }, "children": [ { "id": "revenue-chart", "type": "chart", "props": { "chartType": "bar", "dataSource": "{{monthlyData}}", "xAxis": "month", "yAxis": "revenue", "title": "Monthly Revenue vs Expenses", "width": 550, "height": 300, "colors": ["#2e7d32", "#c62828"], "showLegend": true } } ] }, { "id": "spacer-3", "type": "spacer", "props": { "height": "20px" } }, { "id": "data-table", "type": "table", "props": { "dataSource": "{{monthlyData}}", "tableName": "MonthlyBreakdown", "columns": [ { "header": "Month", "field": "month", "width": "34%", "align": "left" }, { "header": "Revenue", "field": "revenue", "width": "33%", "align": "right", "format": { "type": "number", "preset": "CURRENCY", "currency": "USD" } }, { "header": "Expenses", "field": "expenses", "width": "33%", "align": "right", "format": { "type": "number", "preset": "CURRENCY", "currency": "USD" } } ], "headerBackgroundColor": "#1e3a5f", "headerTextColor": "#ffffff", "alternatingRows": true } }, { "id": "spacer-4", "type": "spacer", "props": { "height": "20px" } }, { "id": "warning-conditional", "type": "conditional", "props": { "condition": "hasWarnings" }, "children": [ { "id": "warning-box", "type": "container", "props": { "backgroundColor": "#fff3e0", "padding": "15px", "borderRadius": "6px" }, "children": [ { "id": "warning-text", "type": "text", "runs": [ { "text": "Warning: ", "bold": true, "color": "#e65100" }, { "text": "{{warningMessage}}", "color": "#bf360c" } ], "props": { "fontSize": 12 } } ] } ] } ] } ] } ] } ``` ### Example 4: Sales Quotation with Calculated Variables and Table Calculations Demonstrates calculated variables for subtotal/tax/discount/total, a table with calculated columns (both PDF and Excel), and conditional rendering based on computed values. **Variables:** ```json [ { "id": "v1", "name": "companyName", "type": "string", "required": true, "defaultValue": "TechParts Inc." }, { "id": "v2", "name": "quoteNumber", "type": "string", "required": true, "defaultValue": "QT-2025-0042" }, { "id": "v3", "name": "quoteDate", "type": "date", "required": true, "defaultValue": "2025-03-15", "format": { "type": "date", "preset": "US" } }, { "id": "v4", "name": "validUntil", "type": "date", "required": true, "defaultValue": "2025-04-15", "format": { "type": "date", "preset": "US" } }, { "id": "v5", "name": "customerName", "type": "string", "required": true, "defaultValue": "Northwind Traders" }, { "id": "v6", "name": "customerEmail", "type": "string", "defaultValue": "purchasing@northwind.com" }, { "id": "v7", "name": "lineItems", "type": "array", "itemType": "object", "required": true, "defaultValue": [ { "sku": "CPU-001", "description": "Server CPU 16-Core", "quantity": 4, "unitPrice": 899.00 }, { "sku": "RAM-064", "description": "64GB DDR5 ECC RAM", "quantity": 8, "unitPrice": 245.00 }, { "sku": "SSD-2TB", "description": "2TB NVMe SSD", "quantity": 4, "unitPrice": 189.00 }, { "sku": "PSU-850", "description": "850W Platinum PSU", "quantity": 4, "unitPrice": 129.00 } ], "schema": { "type": "array", "itemType": "object", "itemSchema": { "type": "object", "properties": { "sku": { "type": "string" }, "description": { "type": "string" }, "quantity": { "type": "number" }, "unitPrice": { "type": "number" } } } } }, { "id": "v8", "name": "discountPercent", "type": "number", "defaultValue": 5 }, { "id": "v9", "name": "taxRate", "type": "number", "defaultValue": 0.08 }, { "id": "v10", "name": "shippingCost", "type": "number", "defaultValue": 75.00 }, { "id": "v11", "name": "notes", "type": "string", "defaultValue": "Prices valid for 30 days. Free shipping on orders over $5,000." }, { "id": "vc1", "name": "subtotal", "type": "number", "isCalculated": true, "expression": "lineItems.reduce((sum, item) => sum + item.quantity * item.unitPrice, 0)" }, { "id": "vc2", "name": "discountAmount", "type": "number", "isCalculated": true, "expression": "subtotal * (discountPercent / 100)" }, { "id": "vc3", "name": "afterDiscount", "type": "number", "isCalculated": true, "expression": "subtotal - discountAmount" }, { "id": "vc4", "name": "taxAmount", "type": "number", "isCalculated": true, "expression": "afterDiscount * taxRate" }, { "id": "vc5", "name": "effectiveShipping", "type": "number", "isCalculated": true, "expression": "subtotal > 5000 ? 0 : shippingCost", "description": "Free shipping if subtotal exceeds $5,000" }, { "id": "vc6", "name": "grandTotal", "type": "number", "isCalculated": true, "expression": "afterDiscount + taxAmount + effectiveShipping" }, { "id": "vc7", "name": "itemCount", "type": "number", "isCalculated": true, "expression": "lineItems.reduce((sum, item) => sum + item.quantity, 0)" }, { "id": "vc8", "name": "hasDiscount", "type": "boolean", "isCalculated": true, "expression": "discountPercent > 0" }, { "id": "vc9", "name": "freeShipping", "type": "boolean", "isCalculated": true, "expression": "subtotal > 5000" }, { "id": "vc10", "name": "shippingLabel", "type": "string", "isCalculated": true, "expression": "freeShipping ? 'FREE' : '$' + effectiveShipping.toFixed(2)" } ] ``` **Schema:** ```json { "outputFormats": ["pdf", "excel"], "pages": [ { "pageId": "page-1", "pageName": "Quotation", "pageSettings": { "format": "A4", "orientation": "portrait", "margin": { "top": "20mm", "right": "15mm", "bottom": "20mm", "left": "15mm" } }, "sections": [ { "id": "main-section", "type": "main", "props": {}, "components": [ { "id": "header-row", "type": "columns", "props": { "columnCount": 2, "columnWidths": ["50%", "50%"] }, "children": [ { "id": "company-col", "type": "container", "children": [ { "id": "company", "type": "heading", "props": { "level": 1, "color": "#1a365d" }, "content": "{{companyName}}" }, { "id": "doc-type", "type": "text", "props": { "fontSize": 14, "color": "#718096" }, "content": "Sales Quotation" } ] }, { "id": "quote-details-col", "type": "key-value", "props": { "items": [ { "label": "Quote #", "value": "{{quoteNumber}}" }, { "label": "Date", "value": "{{quoteDate}}" }, { "label": "Valid Until", "value": "{{validUntil}}" }, { "label": "Total Items", "value": "{{itemCount}}" } ], "layout": "vertical" } } ] }, { "id": "spacer-1", "type": "spacer", "props": { "height": "15px" } }, { "id": "customer-box", "type": "container", "props": { "backgroundColor": "#f7fafc", "padding": "12px", "borderRadius": "6px" }, "children": [ { "id": "to-label", "type": "text", "props": { "fontSize": 10, "color": "#a0aec0", "fontWeight": "bold" }, "content": "PREPARED FOR" }, { "id": "cust-name", "type": "text", "props": { "fontSize": 14, "fontWeight": "bold" }, "content": "{{customerName}}" }, { "id": "cust-email", "type": "text", "props": { "fontSize": 11, "color": "#718096" }, "content": "{{customerEmail}}" } ] }, { "id": "spacer-2", "type": "spacer", "props": { "height": "20px" } }, { "id": "items-table", "type": "table", "props": { "dataSource": "{{lineItems}}", "tableName": "QuoteItems", "tableStyle": "TableStyleMedium2", "columns": [ { "header": "SKU", "field": "sku", "width": "12%", "align": "left" }, { "header": "Description", "field": "description", "width": "38%" }, { "header": "Qty", "field": "quantity", "width": "10%", "align": "center" }, { "header": "Unit Price", "field": "unitPrice", "width": "18%", "align": "right", "format": { "type": "number", "preset": "CURRENCY", "currency": "USD" } }, { "header": "Line Total", "field": "lineTotal", "width": "22%", "align": "right", "isCalculated": true, "expression": "row.quantity * row.unitPrice", "excelFormula": "=[@Qty]*[@[Unit Price]]", "format": { "type": "number", "preset": "CURRENCY", "currency": "USD" } } ], "headerBackgroundColor": "#1a365d", "headerTextColor": "#ffffff", "alternatingRows": true } }, { "id": "spacer-3", "type": "spacer", "props": { "height": "15px" } }, { "id": "totals-section", "type": "columns", "props": { "columnCount": 2, "columnWidths": ["55%", "45%"] }, "children": [ { "id": "notes-col", "type": "container", "props": { "padding": "10px" }, "children": [ { "id": "notes-label", "type": "text", "props": { "fontWeight": "bold", "fontSize": 11, "color": "#4a5568" }, "content": "Notes" }, { "id": "notes-text", "type": "text", "props": { "fontSize": 10, "color": "#718096" }, "content": "{{notes}}" } ] }, { "id": "totals-col", "type": "key-value", "props": { "items": [ { "label": "Subtotal", "value": "{{subtotal}}", "format": { "type": "number", "preset": "CURRENCY", "currency": "USD" } }, { "label": "Discount ({{discountPercent}}%)", "value": "-{{discountAmount}}", "format": { "type": "number", "preset": "CURRENCY", "currency": "USD" } }, { "label": "Tax (8%)", "value": "{{taxAmount}}", "format": { "type": "number", "preset": "CURRENCY", "currency": "USD" } }, { "label": "Shipping", "value": "{{shippingLabel}}" }, { "label": "Grand Total", "value": "{{grandTotal}}", "format": { "type": "number", "preset": "CURRENCY", "currency": "USD" } } ], "layout": "vertical" } } ] }, { "id": "spacer-4", "type": "spacer", "props": { "height": "10px" } }, { "id": "free-shipping-banner", "type": "conditional", "props": { "condition": "freeShipping" }, "children": [ { "id": "shipping-note", "type": "container", "props": { "backgroundColor": "#c6f6d5", "padding": "8px", "borderRadius": "4px" }, "children": [ { "id": "shipping-msg", "type": "text", "props": { "fontSize": 11, "color": "#276749", "textAlign": "center", "fontWeight": "bold" }, "content": "FREE SHIPPING applied — order exceeds $5,000" } ] } ] } ] } ] } ] } ``` ### Example 5: Timesheet with Hours Calculations and Summary Totals Demonstrates calculated table columns for daily totals and overtime, plus calculated variables for weekly summaries, pay calculations, and conditional messaging. **Variables:** ```json [ { "id": "v1", "name": "employeeName", "type": "string", "required": true, "defaultValue": "Sarah Johnson" }, { "id": "v2", "name": "employeeId", "type": "string", "required": true, "defaultValue": "EMP-2847" }, { "id": "v3", "name": "department", "type": "string", "defaultValue": "Engineering" }, { "id": "v4", "name": "weekEnding", "type": "date", "required": true, "defaultValue": "2025-03-21", "format": { "type": "date", "preset": "US" } }, { "id": "v5", "name": "hourlyRate", "type": "number", "required": true, "defaultValue": 45.00 }, { "id": "v6", "name": "overtimeMultiplier", "type": "number", "defaultValue": 1.5 }, { "id": "v7", "name": "timeEntries", "type": "array", "itemType": "object", "required": true, "defaultValue": [ { "day": "Monday", "date": "2025-03-17", "startTime": "08:00", "endTime": "17:30", "breakHours": 0.5, "project": "Project Alpha" }, { "day": "Tuesday", "date": "2025-03-18", "startTime": "07:30", "endTime": "18:00", "breakHours": 0.5, "project": "Project Alpha" }, { "day": "Wednesday", "date": "2025-03-19", "startTime": "08:00", "endTime": "17:00", "breakHours": 0.5, "project": "Project Beta" }, { "day": "Thursday", "date": "2025-03-20", "startTime": "08:00", "endTime": "19:00", "breakHours": 1.0, "project": "Project Alpha" }, { "day": "Friday", "date": "2025-03-21", "startTime": "08:00", "endTime": "16:00", "breakHours": 0.5, "project": "Project Beta" } ], "schema": { "type": "array", "itemType": "object", "itemSchema": { "type": "object", "properties": { "day": { "type": "string" }, "date": { "type": "string" }, "startTime": { "type": "string" }, "endTime": { "type": "string" }, "breakHours": { "type": "number" }, "project": { "type": "string" } } } } }, { "id": "vc1", "name": "totalHours", "type": "number", "isCalculated": true, "expression": "timeEntries.reduce((sum, e) => sum + (parseFloat(e.endTime.split(':')[0]) + parseFloat(e.endTime.split(':')[1])/60) - (parseFloat(e.startTime.split(':')[0]) + parseFloat(e.startTime.split(':')[1])/60) - e.breakHours, 0)", "description": "Total hours worked across all entries" }, { "id": "vc2", "name": "regularHours", "type": "number", "isCalculated": true, "expression": "Math.min(totalHours, 40)" }, { "id": "vc3", "name": "overtimeHours", "type": "number", "isCalculated": true, "expression": "Math.max(totalHours - 40, 0)" }, { "id": "vc4", "name": "regularPay", "type": "number", "isCalculated": true, "expression": "regularHours * hourlyRate" }, { "id": "vc5", "name": "overtimePay", "type": "number", "isCalculated": true, "expression": "overtimeHours * hourlyRate * overtimeMultiplier" }, { "id": "vc6", "name": "grossPay", "type": "number", "isCalculated": true, "expression": "regularPay + overtimePay" }, { "id": "vc7", "name": "hasOvertime", "type": "boolean", "isCalculated": true, "expression": "overtimeHours > 0" }, { "id": "vc8", "name": "avgHoursPerDay", "type": "number", "isCalculated": true, "expression": "Math.round((totalHours / timeEntries.length) * 100) / 100" }, { "id": "vc9", "name": "paymentSummary", "type": "string", "isCalculated": true, "expression": "hasOvertime ? regularHours.toFixed(1) + 'h regular + ' + overtimeHours.toFixed(1) + 'h overtime' : totalHours.toFixed(1) + 'h regular (no overtime)'" } ] ``` **Schema:** ```json { "outputFormats": ["pdf", "excel"], "pages": [ { "pageId": "page-1", "pageName": "Timesheet", "pageSettings": { "format": "A4", "orientation": "landscape", "margin": { "top": "15mm", "right": "15mm", "bottom": "15mm", "left": "15mm" } }, "sections": [ { "id": "main-section", "type": "main", "props": {}, "components": [ { "id": "title", "type": "heading", "props": { "level": 1, "color": "#2d3748" }, "content": "Weekly Timesheet" }, { "id": "employee-info", "type": "columns", "props": { "columnCount": 2, "columnWidths": ["50%", "50%"] }, "children": [ { "id": "emp-details", "type": "key-value", "props": { "items": [ { "label": "Employee", "value": "{{employeeName}}" }, { "label": "Employee ID", "value": "{{employeeId}}" }, { "label": "Department", "value": "{{department}}" } ], "layout": "horizontal" } }, { "id": "period-details", "type": "key-value", "props": { "items": [ { "label": "Week Ending", "value": "{{weekEnding}}" }, { "label": "Hourly Rate", "value": "${{hourlyRate}}" }, { "label": "Summary", "value": "{{paymentSummary}}" } ], "layout": "horizontal" } } ] }, { "id": "spacer-1", "type": "spacer", "props": { "height": "15px" } }, { "id": "time-table", "type": "table", "props": { "dataSource": "{{timeEntries}}", "tableName": "TimeEntries", "tableStyle": "TableStyleLight9", "columns": [ { "header": "Day", "field": "day", "width": "12%", "align": "left" }, { "header": "Date", "field": "date", "width": "12%", "align": "center" }, { "header": "Project", "field": "project", "width": "18%" }, { "header": "Start", "field": "startTime", "width": "10%", "align": "center" }, { "header": "End", "field": "endTime", "width": "10%", "align": "center" }, { "header": "Break (h)", "field": "breakHours", "width": "10%", "align": "center", "format": { "type": "number", "decimalPlaces": 1 } }, { "header": "Hours Worked", "field": "hoursWorked", "width": "14%", "align": "right", "isCalculated": true, "expression": "(parseFloat(row.endTime.split(':')[0]) + parseFloat(row.endTime.split(':')[1])/60) - (parseFloat(row.startTime.split(':')[0]) + parseFloat(row.startTime.split(':')[1])/60) - row.breakHours", "excelFormula": "=(TIMEVALUE([@End])-TIMEVALUE([@Start]))*24-[@[Break (h)]]", "format": { "type": "number", "decimalPlaces": 2 } }, { "header": "Amount", "field": "amount", "width": "14%", "align": "right", "isCalculated": true, "expression": "((parseFloat(row.endTime.split(':')[0]) + parseFloat(row.endTime.split(':')[1])/60) - (parseFloat(row.startTime.split(':')[0]) + parseFloat(row.startTime.split(':')[1])/60) - row.breakHours) * hourlyRate", "excelFormula": "=[@[Hours Worked]]*$hourlyRate", "format": { "type": "number", "preset": "CURRENCY", "currency": "USD" } } ], "headerBackgroundColor": "#2d3748", "headerTextColor": "#ffffff", "alternatingRows": true } }, { "id": "spacer-2", "type": "spacer", "props": { "height": "15px" } }, { "id": "pay-summary", "type": "columns", "props": { "columnCount": 3, "columnWidths": ["33%", "34%", "33%"], "gap": "12px" }, "children": [ { "id": "hours-box", "type": "container", "props": { "backgroundColor": "#ebf8ff", "padding": "12px", "borderRadius": "6px" }, "children": [ { "id": "hours-label", "type": "text", "props": { "fontSize": 10, "color": "#4a5568" }, "content": "TOTAL HOURS" }, { "id": "hours-val", "type": "heading", "props": { "level": 2, "color": "#2b6cb0" }, "content": "{{totalHours}}" }, { "id": "avg-label", "type": "text", "props": { "fontSize": 9, "color": "#a0aec0" }, "content": "Avg {{avgHoursPerDay}} hrs/day" } ] }, { "id": "regular-box", "type": "container", "props": { "backgroundColor": "#f0fff4", "padding": "12px", "borderRadius": "6px" }, "children": [ { "id": "reg-label", "type": "text", "props": { "fontSize": 10, "color": "#4a5568" }, "content": "REGULAR PAY ({{regularHours}}h)" }, { "id": "reg-val", "type": "heading", "props": { "level": 2, "color": "#276749" }, "content": "${{regularPay}}" } ] }, { "id": "total-box", "type": "container", "props": { "backgroundColor": "#faf5ff", "padding": "12px", "borderRadius": "6px" }, "children": [ { "id": "total-label", "type": "text", "props": { "fontSize": 10, "color": "#4a5568" }, "content": "GROSS PAY" }, { "id": "total-val", "type": "heading", "props": { "level": 2, "color": "#553c9a" }, "content": "${{grossPay}}" } ] } ] }, { "id": "spacer-3", "type": "spacer", "props": { "height": "10px" } }, { "id": "overtime-banner", "type": "conditional", "props": { "condition": "hasOvertime" }, "children": [ { "id": "ot-box", "type": "container", "props": { "backgroundColor": "#fefcbf", "padding": "8px", "borderRadius": "4px" }, "children": [ { "id": "ot-msg", "type": "text", "props": { "fontSize": 11, "textAlign": "center", "color": "#744210" }, "content": "Overtime: {{overtimeHours}} hours at {{overtimeMultiplier}}x rate = ${{overtimePay}}" } ] } ] } ] } ] } ] } ``` --- *End of Rynko Template Schema Reference for AI/LLM v4.0*