LLM-readable documentation index
Function Types

Render Functions

Turn structured JSON and a .docx template into a finished document

Hand off to an LLM

A Render function turns structured JSON into a finished .docx using a document template you provide.

Create the template in any editor that can save a .docx, including Microsoft Word, Google Docs export, or another document editor. Add placeholders for the data you want to fill, then upload the file when you create the function. When Render runs, it checks your JSON against the template, fills the placeholders, applies the requested styles, and stores the finished document. The JSON can come from your own system or from an upstream function such as Extract.

Render currently produces .docx output. PDF and HTML output aren't supported yet.

When to use

Use a Render function when you need one of these flows:

  • Standalone. You already have the data: invoice line items, a database record, or an API payload. Pass it as JSON and Render produces the document. No extraction step is needed.
  • After Extract. Use Extract to pull structured data out of a document, scan, or photo. Then use Render to place that data into a contract, report, letter, or other template.

In both flows, the template controls the layout.

Configuration fields

Required fields

FieldTypeDescription
functionNamestringUnique identifier for the function (per environment)
typestringMust be "render"
renderConfig.template.base64stringThe .docx template, base64-encoded

Optional fields

FieldTypeDefaultDescription
displayNamestring-Human-readable display name
tagsstring[]-Tags for organization
renderConfig.template.namestring-Original filename, shown in the dashboard

When you create a Render function, either pass the template's base64-encoded bytes in renderConfig.template.base64 or upload the .docx in the dashboard. bem validates the template, stores it, and reads the placeholder set and style catalog from the file. You don't provide that contract yourself.

The API response returns a single renderConfig.template object: the original name, a short-lived downloadURL you can open to download the stored .docx, and the derived contract (placeholders, styleIds, tableStyleIds, listKinds). The Render node menu shows the same. The private storage location is never exposed.

Example POST /v3/functions request:

{
  "functionName": "invoice-render",
  "type": "render",
  "renderConfig": {
    "template": {
      "name": "invoice-template.docx",
      "base64": "UEsDBBQABgAIAAAAIQ...base64-encoded .docx..."
    }
  }
}

Template

A template is a .docx with two important parts:

  • Placeholders mark where data goes.
  • Styles define the formatting your data can reference.

The data that fills the template uses the primitives below. Its keys must match the template placeholders exactly.

A .docx is a ZIP package of XML files. The document body, styles, numbering rules, images, and relationships live in separate files inside that package. For Render, the important files are usually word/document.xml for placeholders, word/styles.xml for style definitions, and word/numbering.xml for numbered and bulleted list definitions.

Placeholders

Templates support two placeholder forms:

  • {{ KEY }} fills inline text. The matching value must be a string, and Render inserts it where the placeholder appears.
  • {{p KEY }} fills a block. The matching value must be an ordered array of primitives, and Render replaces the placeholder paragraph with those blocks.

These are the only supported forms. Render doesn't support filters, loops, or other template expressions. Each key must be unique, so the same key can't appear in both inline and block form.

A template is rejected at upload if it uses an unsupported placeholder form or has no placeholders.

Match data keys to placeholders

Your data keys and placeholder keys must match exactly. Every placeholder needs a data key, and every data key needs a placeholder. If either side has an extra key, the render fails. Render doesn't partially fill a template.

A field set to null counts as missing. If the template references a key and the data sends null, Render rejects the call with a missing-key error. Send a value for every placeholder. For optional text with no content, send an empty string "" instead of null.

Styles

The template defines the paragraph styles and table styles Render can use. Your data references each style by its style ID, such as Heading1 or TableNormal. The style ID is the canonical value stored inside the .docx; it isn't always the same as the display name shown in Word.

Every style ID in your data must exist in the template. If the data names a style the template doesn't define, Render fails before it produces output.

When Extract feeds Render, a useful pattern is to list the allowed template styles as enums in your Extract schema. Add a short description to each enum so the model has clear targets and only emits styles the template defines. After uploading the template, use the Defined styles list in the Render node menu to find the exact IDs.

{
  "$defs": {
    "paragraphStyleIds": {
      "type": "string",
      "enum": ["Heading1"],
      "description": "Paragraph style IDs defined in the template. Use Heading1 for section titles."
    },
    "tableStyleIds": {
      "type": "string",
      "enum": ["TableGrid"],
      "description": "Table style IDs defined in the template. Use TableGrid for extracted tables."
    }
  }
}

A paragraph without a style ID inherits the template's default paragraph style, usually Normal. Set a style ID only when you want to override that default.

List numbering

To render a multi-level list, for example 1.1 or 1.1.1, the template must include a numbering definition for each list kind your data uses: decimal or bullet. A numbering definition is the hidden .docx rule that tells the renderer how list levels should look and indent. If the template has no matching numbering definition, the render fails.

To add numbering, create a numbered list and a bulleted list once in the document body, then save or export the file as .docx. The list may look like ordinary editor formatting, but the editor writes the actual rules into word/numbering.xml inside the .docx package. After upload, the Render node menu shows which definitions the template contains.

Reserved underscore prefix

Placeholder keys can't start with _. That prefix is reserved for the platform. A template with a placeholder like {{ _notes }} is rejected at upload, and the error names the key.

Primitives

Render block data is built from four primitives: paragraph, table, image, and list. Each primitive is wrapped in a key with the same name as its kind. Your code, or an Extract schema, composes these primitives into the block values that fill {{p KEY }} placeholders.

Render validates every primitive. Anything outside the schema is rejected.

A {{p KEY }} placeholder expects an array of primitives. Each element in the array is one block to render. Render writes those blocks in the same order they appear in the array.

For example, this value for body renders one paragraph:

[
  { "paragraph": { "text": "Total due on receipt.", "styleId": "Normal" } }
]

This value renders a paragraph, then a list, then a table:

[
  { "paragraph": { "text": "Summary", "styleId": "Heading1" } },
  { "list": { "kind": "decimal", "items": [{ "contents": [{ "paragraph": { "text": "Review the order." } }] }] } },
  { "table": { "rows": [["Item", "Qty"], ["Widget", "3"]], "styleId": "TableGrid" } }
]

The array must contain at least one primitive.

paragraph

A paragraph inserts one paragraph of text.

{ "paragraph": { "text": "Total due on receipt.", "styleId": "Normal" } }
FieldTypeRequiredDescription
textstringYesText for the paragraph
styleIdstring | nullNoA paragraph style ID defined in the template. Null or absent inherits the template default (see Styles).

table

A table is a list of rows, and each row is a list of scalar cells. The data doesn't carry a separate header row; the table style controls the visual distinction. Render stringifies each cell, and null becomes an empty cell.

{ "table": { "rows": [["Item", "Qty"], ["Widget", "3"]], "styleId": "TableGrid" } }
FieldTypeRequiredDescription
rowsarray of arraysYesAt least one row. Each cell is a string, number, boolean, or null.
styleIdstringYesA table style ID defined in the template. There is no fallback.

table.styleId is required. A table that omits it fails validation.

image

An image comes from the upstream source document. source is a 1-based positional reference in the form image_N. Render walks the source document in order and resolves image_N to the N-th image's bytes.

{ "image": { "source": "image_1", "alt": "Signed page", "caption": "Exhibit A" } }
FieldTypeRequiredDescription
sourcestringYesA reference of the form image_N, where N is 1-based. image_1 is the first image in document order.
altstring | nullNoAlt text for accessibility
captionstring | nullNoCaption rendered below the image

list

A list contains numbered or bulleted entries. kind selects the numbering style, and items holds the entries. Render starts every numbered list at 1 and keeps counters independent across lists.

{
  "list": {
    "kind": "decimal",
    "items": [
      { "contents": [{ "paragraph": { "text": "First clause." } }] },
      { "contents": [{ "paragraph": { "text": "Second clause." } }] }
    ]
  }
}
FieldTypeRequiredDescription
kindenumYesOne of "decimal" (1., 1.1, 1.1.1) or "bullet"
itemsarrayYesThe list entries, each an item

A list requires the template to define numbering for its kind; see List numbering.

The item sub-primitive

A list is built from items. An item is one list entry. It has a required contents array with paragraphs, tables, or images in document order. It can also have an optional items array for nested children one level deeper.

The first paragraph in contents carries the list marker. Later primitives render under the same entry with no marker. If an item's contents holds only a table or only an image, that block renders without a marker and still keeps its place in the list.

Use an item's items array for nesting. Don't put a list inside contents. Each level of items becomes one deeper indent level.

{
  "list": {
    "kind": "decimal",
    "items": [
      {
        "contents": [{ "paragraph": { "text": "Prepare the sample." } }],
        "items": [
          {
            "contents": [{ "paragraph": { "text": "Label the container." } }],
            "items": [
              { "contents": [{ "paragraph": { "text": "Record the lot number." } }] }
            ]
          },
          { "contents": [{ "paragraph": { "text": "Weigh the sample." } }] }
        ]
      },
      { "contents": [{ "paragraph": { "text": "Submit for analysis." } }] }
    ]
  }
}

This renders as a three-level numbered list:

1.Prepare the sample.
1.1.Label the container.
1.1.1.Record the lot number.
1.2.Weigh the sample.
2.Submit for analysis.

Valid block examples

These examples are valid render inputs for a template whose only placeholder is {{p body }}. If your template also has {{ title }} or other placeholders, include those keys too.

A paragraph followed by a table:

{
  "body": [
    { "paragraph": { "text": "Invoice summary" } },
    {
      "table": {
        "rows": [["Item", "Qty"], ["Widget", "3"]],
        "styleId": "TableGrid"
      }
    }
  ]
}

A numbered list with one nested level:

{
  "body": [
    {
      "list": {
        "kind": "decimal",
        "items": [
          {
            "contents": [{ "paragraph": { "text": "Prepare the sample." } }],
            "items": [
              { "contents": [{ "paragraph": { "text": "Label the container." } }] }
            ]
          },
          { "contents": [{ "paragraph": { "text": "Submit for analysis." } }] }
        ]
      }
    }
  ]
}

A mixed block with an image from an upstream Extract document:

{
  "body": [
    { "paragraph": { "text": "Evidence", "styleId": "Heading1" } },
    { "image": { "source": "image_1", "alt": "Signed page", "caption": "Exhibit A" } },
    { "paragraph": { "text": "The signed page is attached above." } }
  ]
}

Extract to Render workflow

Extract and Render are designed to chain. The end-to-end path:

  1. A source document goes into an Extract function.
  2. Extract produces structured JSON shaped to the render primitives, with keys matching the template's placeholders.
  3. Render lays that JSON into the template and stores the finished .docx.
  4. A downstream Send node delivers the document.

The Extract schema has a large effect on the finished document. Two practices matter most.

Decompose nested structure into explicit levels. When the output needs nested content, such as multi-level lists or numbered sub-procedures, define each level as its own named schema step. Avoid a single recursive definition. A recursive $ref that points back at itself makes every level look identical, so the model often flattens the output. Explicit levels, such as section, step, and sub-step, give the model a clear structure to fill.

Write strong field descriptions. Precise descriptions are extremely helpful. Describe each field, explain where the content should come from, and name the styles it may emit. Better descriptions give Extract clearer targets, which gives Render cleaner input.

Where images come from

There is no image upload field in Render. In an Extract to Render chain, images come from the same document Extract processed. The original document is carried to Render through the call lineage.

An image's source is a positional reference. image_N means the N-th image in that original document, in document order.

This gives two render shapes:

  • Shape A: workflow. An upstream document feeds Render through the chain. Image primitives resolve against that document.
  • Shape B: direct call, no upstream. No source document is available, so any image primitive fails with a render_image_unresolved error.

A direct render supports paragraph, table, and list. The image primitive needs an upstream document.

Worked example: Extract → Render -> Send

This example renders a short quality-procedure document with a heading, a multi-level numbered list, a table, and an image in one block placeholder. It then delivers the finished .docx with Send.

The template

Create a .docx with this body:

{{ title }}
{{p body }}

{{ title }} is a string placeholder. {{p body }} is a block placeholder that takes a sequence of primitives. The template also defines the Heading1 paragraph style, the TableGrid table style, and a decimal numbering definition.

The Extract output schema

The Extract OutputSchema emits exactly the keys title and body. title is a string. body is the array of primitives that fills {{p body }}.

This schema is complete enough to copy into an Extract function and adapt. It defines the primitive shapes, the allowed style IDs, and a finite four-level list structure that avoids recursive $ref loops.

{
  "type": "object",
  "additionalProperties": false,
  "description": "Extract a short procedure document into a Render-ready JSON object. Preserve the document's meaning and order. Use plain text, not markdown. Emit every field required by the schema.",
  "required": ["title", "body"],
  "properties": {
    "title": {
      "type": "string",
      "description": "The document title, shown in the inline {{ title }} placeholder."
    },
    "body": {
      "type": "array",
      "minItems": 1,
      "description": "The document body, rendered into the {{p body }} block as an ordered sequence of primitives.",
      "items": { "$ref": "#/$defs/primitive" }
    }
  },
  "$defs": {
    "paragraphStyleIds": {
      "type": "string",
      "enum": ["Heading1"],
      "description": "Paragraph style IDs defined in the template. Use Heading1 for major headings."
    },
    "tableStyleIds": {
      "type": "string",
      "enum": ["TableGrid"],
      "description": "Table style IDs defined in the template. Use TableGrid for tables."
    },
    "paragraph": {
      "type": "object",
      "description": "One paragraph of plain text.",
      "properties": {
        "paragraph": {
          "type": "object",
          "properties": {
            "text": {
              "type": "string",
              "description": "The paragraph text. Strip markdown control syntax and keep the prose itself."
            },
            "styleId": {
              "description": "A paragraph style ID defined in the template. Use Heading1 only for headings.",
              "anyOf": [
                { "type": "null" },
                { "$ref": "#/$defs/paragraphStyleIds" }
              ]
            }
          },
          "required": ["text"]
        }
      },
      "required": ["paragraph"]
    },
    "table": {
      "type": "object",
      "description": "One table. The first row can be a header row. Drop empty padding rows.",
      "properties": {
        "table": {
          "type": "object",
          "properties": {
            "rows": {
              "type": "array",
              "minItems": 1,
              "items": {
                "type": "array",
                "minItems": 1,
                "items": {
                  "anyOf": [
                    { "type": "string" },
                    { "type": "number" },
                    { "type": "boolean" },
                    { "type": "null" }
                  ]
                }
              }
            },
            "styleId": { "$ref": "#/$defs/tableStyleIds" }
          },
          "required": ["rows", "styleId"]
        }
      },
      "required": ["table"]
    },
    "image": {
      "type": "object",
      "description": "One image from the upstream source document.",
      "properties": {
        "image": {
          "type": "object",
          "properties": {
            "source": {
              "type": "string",
              "pattern": "^image_[1-9][0-9]*$",
              "description": "A positional image reference, such as image_1."
            },
            "alt": {
              "type": ["string", "null"],
              "description": "Alt text for accessibility."
            },
            "caption": {
              "type": ["string", "null"],
              "description": "Caption rendered below the image."
            }
          },
          "required": ["source"]
        }
      },
      "required": ["image"]
    },
    "nonListPrimitive": {
      "description": "A primitive that can appear inside a list item.",
      "anyOf": [
        { "$ref": "#/$defs/paragraph" },
        { "$ref": "#/$defs/table" },
        { "$ref": "#/$defs/image" }
      ]
    },
    "level3Item": {
      "type": "object",
      "description": "Fourth/deepest list level. Keep any deeper source text at this level instead of dropping it.",
      "properties": {
        "contents": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/nonListPrimitive" }
        }
      },
      "required": ["contents"]
    },
    "level2Item": {
      "type": "object",
      "description": "Third list level. Put child items in items.",
      "properties": {
        "contents": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/nonListPrimitive" }
        },
        "items": {
          "type": "array",
          "items": { "$ref": "#/$defs/level3Item" }
        }
      },
      "required": ["contents"]
    },
    "level1Item": {
      "type": "object",
      "description": "Second list level. Put child items in items.",
      "properties": {
        "contents": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/nonListPrimitive" }
        },
        "items": {
          "type": "array",
          "items": { "$ref": "#/$defs/level2Item" }
        }
      },
      "required": ["contents"]
    },
    "level0Item": {
      "type": "object",
      "description": "Top-level list item. Put nested list content in items, not in contents.",
      "properties": {
        "contents": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/nonListPrimitive" }
        },
        "items": {
          "type": "array",
          "items": { "$ref": "#/$defs/level1Item" }
        }
      },
      "required": ["contents"]
    },
    "list": {
      "type": "object",
      "description": "One numbered or bulleted list.",
      "properties": {
        "list": {
          "type": "object",
          "properties": {
            "kind": {
              "type": "string",
              "enum": ["decimal", "bullet"],
              "description": "Use decimal for numbered lists and bullet for bulleted lists."
            },
            "items": {
              "type": "array",
              "minItems": 1,
              "items": { "$ref": "#/$defs/level0Item" }
            }
          },
          "required": ["kind", "items"]
        }
      },
      "required": ["list"]
    },
    "primitive": {
      "description": "One Render primitive.",
      "anyOf": [
        { "$ref": "#/$defs/paragraph" },
        { "$ref": "#/$defs/table" },
        { "$ref": "#/$defs/image" },
        { "$ref": "#/$defs/list" }
      ]
    }
  }
}

The JSON the run produces

When the workflow runs, Extract emits JSON shaped by that schema.

{
  "title": "Sample Preparation",
  "body": [
    { "paragraph": { "text": "Sample Preparation", "styleId": "Heading1" } },
    {
      "list": {
        "kind": "decimal",
        "items": [
          {
            "contents": [{ "paragraph": { "text": "Prepare the sample." } }],
            "items": [
              { "contents": [{ "paragraph": { "text": "Label the container." } }] }
            ]
          },
          { "contents": [{ "paragraph": { "text": "Submit for analysis." } }] }
        ]
      }
    },
    { "table": { "rows": [["Step", "Owner"], ["Prepare", "Lab"], ["Submit", "QA"]], "styleId": "TableGrid" } },
    { "image": { "source": "image_1", "caption": "Filled sample form" } }
  ]
}

The produced .docx

Render fills {{ title }} with Sample Preparation, then replaces {{p body }} with the four primitives in order. This preview shows the document structure; the exact fonts, spacing, table borders, and image rendering come from the uploaded .docx template.

Sample Preparation

1.Prepare the sample.
1.1.Label the container.
2.Submit for analysis.
StepOwner
PrepareLab
SubmitQA

Embedded source image: image_1

Filled sample form

Output delivery

Deliver the rendered .docx with a downstream Send node. Add Send after Render and point it at a webhook, S3 bucket, or Google Drive folder. Send fetches the stored document, presigns it, and delivers {"s3URL": "<presigned-url>"}.

Render output doesn't have a subscription type. The output is a binary .docx, not JSON, so Send is the delivery path.

For webhook destinations, the Send output includes the delivery status, destination type, delivered render event, and webhook response.

{
  "deliveryStatus": "success",
  "destinationType": "webhook",
  "deliveredContent": {
    "eventID": "evt_example_render_delivery_001",
    "createdAt": "2026-01-15T18:30:00.000000Z",
    "referenceID": "example-render-workflow",
    "eventType": "render",
    "functionCallID": "fc_example_render_001",
    "functionID": "fn_example_render_template",
    "functionName": "render-quality-procedure",
    "functionVersionNum": 1,
    "callID": "call_example_workflow_001",
    "callType": "workflow",
    "workflowID": "wf_example_extract_render_send",
    "workflowName": "extract-render-send-example",
    "workflowVersionNum": 1,
    "metadata": {
      "durationFunctionToEventSeconds": 2.5
    },
    "functionCallTryNumber": 1,
    "outputDownloadURL": "https://files.example.com/render-outputs/rendered-quality-procedure.docx?expires=1800&signature=example",
    "validationSeconds": 0.02,
    "docxRenderSeconds": 0.08
  },
  "webhookOutput": {
    "httpStatusCode": 200,
    "httpResponseBody": "OK"
  }
}

Errors

A failed render appears as an error event on the workflow call with a kind and a msg. The msg names the offending key, type, or style. For example: data carries keys the template does not reference: ['bar'].

The error kinds are:

KindCauseFix
render_validationThe data does not satisfy the contract: a key mismatch, wrong primitive type, missing or undefined style, or template authoring error. The msg names the specific reason.Compare the msg against the Fields and Defined styles lists in the Render node menu. Add or remove keys, fix the primitive shape, or use a defined style ID.
render_image_unresolvedAn image primitive is present, but no source document is available.Confirm a document-bearing node sits upstream so the platform can recover the source by lineage, or remove the image primitives. See Where images come from.
render_exceptionAn unexpected server-side failure during render execution. Not a contract error in your data or template.Retry the call. If it persists, contact support with the call ID and the error msg.

All three errors are terminal. Render doesn't retry. Fix the cause and run the call again.

On this page