> ## Documentation Index
> Fetch the complete documentation index at: https://docs.craftkit.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Images & signatures

> How to bind a dynamic image or a signature image into a render — the validation rule that rejects base64, and which pipelines actually render a data-bound image.

A signed document with no signature is not a signed record. Binding an image — a logo, a photo, a
rasterized signature — has two constraints you must design around: an **ingress validation rule**
and a **pipeline capability**.

## Constraint 1 — `image` variables accept URLs, not base64

A variable with `dataType: "image"` (or `"url"`) is validated as `z.string().url()` at the render
endpoint. A `data:image/png;base64,…` value **fails that check** and the render is rejected with
`invalid_input_data`. So an `image` variable must carry an **http(s) URL**.

## Constraint 2 — the signature block is text-only

The dedicated signature block renders a line plus name/date **text** — it has **no image slot** on
any pipeline. A signature *image* must therefore be modeled as a **separate `image` variable**,
not the signature block.

And document-mode image blocks are **static** (they render the builder's `src`, never your data),
so a data-bound image needs the **Canvas** or **PDF overlay** pipeline — see
[Render pipelines](/guides/render-pipelines).

## Recommended: host the image, pass an https URL

The portable approach that works within the current validation:

<Steps>
  <Step title="Author on a data-binding pipeline">
    Use **Canvas (Handlebars)** with an `image` variable bound to an image node, or **PDF overlay**
    with an image field. Document mode will not render a data-bound image.
  </Step>

  <Step title="Host the rasterized image">
    Upload the signature/image to your own bucket or CDN and get an https URL. (There is no
    non-embed, key-authenticated upload endpoint — see below.)
  </Step>

  <Step title="Pass the URL as an image variable">
    ```json theme={null}
    {
      "data": {
        "handover": { "signedBy": "Jane Doe" },
        "signatureImageUrl": "https://cdn.example.com/sig/abc.png"
      }
    }
    ```
  </Step>
</Steps>

## Alternative: inline base64 without external hosting

If you cannot host the image, you can send it inline — but **not** as an `image`/`url` variable
(those reject data-URIs). Declare the field as `dataType: "text"` so any string passes validation,
then render it as an image on a pipeline that accepts a data-URI source:

* **Canvas (Handlebars):** the HTML compiler accepts `data:image/...` image sources.
* **PDF overlay:** the pdfme image field accepts a base64 data-URI.

```json theme={null}
{ "data": { "signatureData": "data:image/png;base64,iVBORw0KGgo..." } }
```

<Warning>
  Declaring the field as `text` means you lose URL validation — the template must be authored to
  render that variable as an image (an image node bound to the key, or a pdfme image field). Pick
  one mechanism per field and document it with the template.
</Warning>

## Why there is no headless upload endpoint

The only image-upload endpoint today is **embed-session-scoped** (`fill`-mode sessions only) and is
not reachable with a plain `ck_live_` key on a headless path. For server-to-server flows, host the
image yourself and pass a URL, or use the inline base64 alternative above. A key-authenticated,
non-embed upload endpoint is on the roadmap.
