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

# HTML fields

> Add custom-styled sections to a document with sanitized HTML and inline CSS

In this guide, you add `HTML` fields to a document: content blocks written in HTML with inline CSS, for branded headers, styled sections, and layouts that a `TEXT` field can't express. sajn sanitizes the HTML on the server, and the sealed PDF renders it as written.

`HTML` fields are an API feature. Use them for content your code generates. For content that people edit in the sajn app, use `TEXT` fields or a template.

## Before you begin

* Store your API key in the `SAJN_API_KEY` environment variable. To create a key, go to workspace settings in the sajn app, then **Utvecklare** (Developer) > **API-nycklar** (API keys).
* Have a `DRAFT` document. For more information, see [Create a document](/guides/documents/create-document).
* To send a full HTML file, escape it as a JSON string first. For more information, see [Format HTML for JSON](/guides/fields/formatting-html-content).

## Add HTML content

<Steps>
  <Step title="Create the HTML field">
    Send a `POST` request to `/api/v1/documents/DOCUMENT_ID/fields` with the field in a `fields` array. Both `type` and `fieldMeta.type` are `HTML`, and `fieldMeta.content` holds the HTML:

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST https://app.sajn.se/api/v1/documents/DOCUMENT_ID/fields \
        -H "Authorization: Bearer $SAJN_API_KEY" \
        -H "Sajn-Version: 2026-10" \
        -H "Content-Type: application/json" \
        -d '{
          "fields": [
            {
              "type": "HTML",
              "position": 0,
              "fieldMeta": {
                "type": "HTML",
                "content": "<div style=\"text-align: center; padding: 20px;\"><h1 style=\"color: #003366;\">Service agreement</h1><p style=\"color: #666666;\">Effective from 2026-11-01</p></div>"
              }
            }
          ]
        }'
      ```

      ```javascript Node.js theme={null}
      const documentId = "DOCUMENT_ID";
      const content = `
        <div style="text-align: center; padding: 20px;">
          <h1 style="color: #003366;">Service agreement</h1>
          <p style="color: #666666;">Effective from 2026-11-01</p>
        </div>`;

      const response = await fetch(`https://app.sajn.se/api/v1/documents/${documentId}/fields`, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.SAJN_API_KEY}`,
          "Sajn-Version": "2026-10",
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          fields: [{ type: "HTML", position: 0, fieldMeta: { type: "HTML", content } }],
        }),
      });
      const body = await response.json();
      if (!response.ok) {
        throw new Error(`${body.code}: ${body.message} (request ${body.requestId})`);
      }
      console.log(body.data[0].id);
      ```

      ```python Python theme={null}
      import os

      import requests

      document_id = "DOCUMENT_ID"
      content = """
      <div style="text-align: center; padding: 20px;">
        <h1 style="color: #003366;">Service agreement</h1>
        <p style="color: #666666;">Effective from 2026-11-01</p>
      </div>"""

      response = requests.post(
          f"https://app.sajn.se/api/v1/documents/{document_id}/fields",
          headers={
              "Authorization": f"Bearer {os.environ['SAJN_API_KEY']}",
              "Sajn-Version": "2026-10",
          },
          json={
              "fields": [
                  {"type": "HTML", "position": 0, "fieldMeta": {"type": "HTML", "content": content}},
              ],
          },
      )
      body = response.json()
      if not response.ok:
          raise RuntimeError(f"{body['code']}: {body['message']} (request {body['requestId']})")
      print(body["data"][0]["id"])
      ```
    </CodeGroup>

    Replace `DOCUMENT_ID` with the document ID. The response has the created field in `data`, and `fieldMeta.content` holds the sanitized HTML:

    ```json theme={null}
    {
      "fields": [
        {
          "id": "cm4k2xd5s0005abcd7890uvwx",
          "documentId": "cm4k2x9p10001abcd1234efgh",
          "templateId": null,
          "type": "HTML",
          "position": 0,
          "fieldMeta": {
            "type": "HTML",
            "content": "<div style=\"text-align:center;padding:20px\"><h1 style=\"color:#003366\">Service agreement</h1><p style=\"color:#666666\">Effective from 2026-11-01</p></div>"
          },
          "createdAt": "2026-10-01T10:00:00.000Z",
          "updatedAt": "2026-10-01T10:00:00.000Z"
        }
      ]
    }
    ```

    Compare the stored content with what you sent. sajn removes tags, attributes, and CSS values that aren't in the allowlists that follow, without an error.
  </Step>

  <Step title="Add several fields in order">
    To add a header and a footer around other content, send several fields in the `fields` array. `position` is each field's 0-based place among the document's content blocks:

    ```bash theme={null}
    curl -X POST https://app.sajn.se/api/v1/documents/DOCUMENT_ID/fields \
      -H "Authorization: Bearer $SAJN_API_KEY" \
      -H "Sajn-Version: 2026-10" \
      -H "Content-Type: application/json" \
      -d '{
        "fields": [
          { "type": "HTML", "position": 0,
            "fieldMeta": { "type": "HTML", "content": "<div style=\"padding: 20px;\"><h1>Header</h1></div>" } },
          { "type": "HTML", "position": 1,
            "fieldMeta": { "type": "HTML", "content": "<div style=\"border-top: 1px solid #cccccc;\"><p>Footer</p></div>" } }
        ]
      }'
    ```

    The response has the created fields in `data`, in request order.
  </Step>

  <Step title="Update the content">
    To replace the HTML of a field, send a `PATCH` request with the field's `id` and the full `fieldMeta`:

    ```bash theme={null}
    curl -X PATCH https://app.sajn.se/api/v1/documents/DOCUMENT_ID/fields/FIELD_ID \
      -H "Authorization: Bearer $SAJN_API_KEY" \
      -H "Sajn-Version: 2026-10" \
      -H "Content-Type: application/json" \
      -d '{ "type": "HTML", "fieldMeta": { "type": "HTML", "content": "<h1>Updated header</h1>" } }'
    ```

    Replace `FIELD_ID` with the field's `id`. The response is the updated field. The page's look, such as orientation, page size, and background, is set on the document in `documentStyle`. For its properties, see [Create a new document](/api-reference/create-a-new-document).
  </Step>
</Steps>

## Allowed HTML tags

HTML fields support a safe subset of HTML tags for formatting and layout. All HTML content is automatically sanitized server-side.

### Text formatting

* `<p>`, `<span>`, `<div>` - Basic containers
* `<b>`, `<strong>` - Bold text
* `<i>`, `<em>` - Italic text
* `<u>` - Underlined text
* `<s>` - Strikethrough text
* `<mark>` - Highlighted text
* `<small>`, `<sub>`, `<sup>` - Size and position

### Headings

* `<h1>`, `<h2>`, `<h3>`, `<h4>`, `<h5>`, `<h6>` - All heading levels

### Lists

* `<ul>` - Unordered lists
* `<ol>` - Ordered lists
* `<li>` - List items

### Tables

* `<table>`, `<thead>`, `<tbody>`, `<tfoot>` - Table structure
* `<tr>`, `<td>`, `<th>` - Table rows and cells
* `<caption>` - Table caption

### Images

* `<img>` - Images with `src`, `alt`, `width`, `height`, `title` attributes

### Links

* `<a>` - Links with `href`, `target`, `rel`, `title` (allowed schemes: http, https, mailto, tel only; dangerous schemes like javascript:, data:, vbscript: are stripped; all links forced to `target="_blank"` and `rel="noopener noreferrer"` for security)

### Other

* `<br>`, `<hr>` - Line breaks and horizontal rules
* `<label>`, `<input>` - Checkbox lists; `<input>` keeps only `type`, `checked`, and `disabled`
* `<blockquote>` - Quoted text
* `<pre>`, `<code>` - Code blocks

### Allowed attributes

**All elements:**

* `style` - Inline CSS styles
* `class` - CSS class names

**Images (`<img>`):**

* `src` - Image source (HTTP/HTTPS URLs or data URIs)
* `alt` - Alternative text
* `width`, `height` - Dimensions
* `title` - Image title

**Tables:**

* `<table>`: `border`, `cellpadding`, `cellspacing`, `width`
* `<td>`, `<th>`: `colspan`, `rowspan`, `align`, `valign`

**Text elements:**

* `<p>`, `<div>`, `<h1>` through `<h6>`: `align`

**Links (`<a>`):**

* `href` - URL (http, https, mailto, tel only)
* `target` - Forced to `_blank` for security
* `rel` - Forced to `noopener noreferrer` for security
* `title` - Link title

### Removed tags

The following tags are automatically removed for security:

* `<script>` - JavaScript (XSS protection)
* `<iframe>` - Embedded content
* `<form>` - Forms
* `<style>` - Style tags (use inline styles instead)

## Allowed CSS properties

CSS works only in inline `style` attributes. The following properties are supported:

### Colors

```html theme={null}
<div style="color: #003366; background-color: #f8f9fa;">
  Styled text
</div>
```

* `color` - Text color (hex, rgb, rgba)
* `background-color` - Background color
* `background` - Background shorthand

### Typography

```html theme={null}
<p style="font-size: 16px; font-weight: bold; font-family: Arial, sans-serif; line-height: 1.5;">
  Custom typography
</p>
```

* `font-size` - Font size (px, rem, %, pt)
* `font-weight` - Font weight (normal, bold, 100-900)
* `font-style` - Font style (normal, italic)
* `font-family` - Font family
* `line-height` - Line height
* `letter-spacing` - Letter spacing
* `text-align` - Text alignment (left, right, center, justify)
* `text-decoration` - Text decoration (underline, line-through)
* `text-transform` - Text transform (uppercase, lowercase, capitalize)

### Spacing

```html theme={null}
<div style="margin: 20px; padding: 15px;">
  Spaced content
</div>
```

* `margin`, `margin-top/right/bottom/left` - Outer spacing
* `padding`, `padding-top/right/bottom/left` - Inner spacing

### Borders

```html theme={null}
<div style="border: 2px solid #003366; border-radius: 8px;">
  Bordered box
</div>
```

* `border` - Border shorthand (format: `widthpx style #color`, e.g., `2px solid #003366`)
* `border-top/right/bottom/left` - Individual borders (same format as border shorthand)
* `border-color` - Border color (hex format)
* `border-width` - Border width (px values)
* `border-style` - Border style (solid, dashed, dotted, none)
* `border-radius` - Rounded corners (px, em, rem, %)

### Layout

```html theme={null}
<div style="width: 100%; max-width: 600px; display: block;">
  Layout container
</div>
```

* `width`, `height` - Element dimensions
* `max-width`, `max-height` - Maximum dimensions
* `min-width`, `min-height` - Minimum dimensions
* `display` - Display type (block, inline, inline-block, flex, grid, none)
* `vertical-align` - Vertical alignment (top, middle, bottom, baseline)
* `box-sizing` - Box model (border-box, content-box)

### Flexbox

```html theme={null}
<div style="display: flex; flex-direction: row; justify-content: space-between; align-items: center; gap: 20px;">
  <div>Item 1</div>
  <div>Item 2</div>
  <div>Item 3</div>
</div>
```

* `flex-direction` - Direction of flex items (row, row-reverse, column, column-reverse)
* `flex-wrap` - Wrapping behavior (nowrap, wrap, wrap-reverse)
* `align-items` - Cross-axis alignment (flex-start, flex-end, center, baseline, stretch, start, end)
* `align-content` - Multi-line alignment (flex-start, flex-end, center, space-between, space-around, stretch, start, end)
* `justify-content` - Main-axis alignment (flex-start, flex-end, center, space-between, space-around, space-evenly, start, end)
* `gap` - Gap between flex items (px, em, rem, %)

### Grid

```html theme={null}
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px;">
  <div>Column 1</div>
  <div>Column 2</div>
  <div>Column 3</div>
</div>
```

* `grid-template-columns` - Column track sizes (e.g., `1fr 1fr 1fr`, `repeat(3, 1fr)`, `200px auto 1fr`)
* `grid-template-rows` - Row track sizes
* `grid-column` - Column placement for grid items
* `grid-row` - Row placement for grid items
* `grid-area` - Shorthand for grid placement
* `grid-gap` - Gap between grid items (legacy, use `gap` instead)
* `row-gap` - Gap between rows
* `column-gap` - Gap between columns
* `gap` - Gap between grid items (px, em, rem, %, fr)

### List markers

```html theme={null}
<ul style="list-style-type: circle;">
  <li>Item 1</li>
</ul>
```

* `list-style-type` - List marker style (disc, circle, square, decimal, lower-alpha, upper-alpha, lower-roman, upper-roman, none)

## Image hosting

HTML fields support both hosted image URLs and inline base64 data URIs. **Use hosted URLs.**

### Host images instead of inlining them

<Warning>
  Always reference images by a hosted `https://` URL. **Do not embed images as base64
  `data:` URIs** in HTML field content.

  Base64 inflates the image by \~33%, can't be cached or deduplicated, and is stored
  inline in every document you create from a template — so the same image is copied
  on every send. A handful of multi-MB base64 fields can dominate your account's
  storage and slow document rendering. Inline base64 images are **strongly
  discouraged and may be rejected in a future API version.**
</Warning>

To host an image, upload it once with the [presigned file-upload flow](/guides/documents/file-uploads)
or any public URL you control, and reference the resulting `https://` URL.
The same URL can be reused across every document and template — no per-document copies.

### Use a hosted HTTPS URL

```html theme={null}
<img src="https://cdn.example.com/logo.png"
     alt="Company Logo"
     style="max-width: 200px; display: block; margin: 0 auto;" />
```

### Avoid inline base64 data URIs

Accepted today for backwards compatibility, but avoid it — prefer a hosted URL:

```html theme={null}
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAA..."
     alt="Embedded Image"
     style="width: 100px;" />
```

<Note>
  HTML field content can be up to 100 MB, so existing integrations keep working.
  We recommend keeping content small, with text and hosted image URLs, because
  the limit can be lowered in a future API version.
</Note>

## Sanitization

HTML fields are automatically sanitized server-side for security:

### Kept

* Formatting tags (`<p>`, `<div>`, `<span>`, headings)
* Links (`<a>`) with http, https, mailto, tel URLs (forced `target="_blank"` and `rel="noopener noreferrer"`)
* Inline CSS styles (colors, fonts, spacing)
* Images from HTTP/HTTPS/data URIs
* Tables, lists, and basic structure

### Removed

* `<script>` tags - JavaScript removed
* `<iframe>` tags - No embedded content
* `<style>` tags - No global styles
* `onclick`, `onerror` - No event handlers
* `javascript:`, `data:`, `vbscript:` - Dangerous URL schemes stripped from links

### Example

**Input:**

```html theme={null}
<div>
  <h1>Title</h1>
  <script>alert('xss')</script>
  <a href="javascript:alert('xss')">Malicious</a>
  <a href="https://example.com">Safe Link</a>
  <img src="https://safe.com/image.png" alt="Safe" />
</div>
```

**Output (Sanitized):**

```html theme={null}
<div>
  <h1>Title</h1>

  <a href="https://example.com" target="_blank" rel="noopener noreferrer">Safe Link</a>
  <img src="https://safe.com/image.png" alt="Safe" />
</div>
```

The malicious `javascript:` link is stripped; the safe https link is preserved with `target="_blank"` and `rel="noopener noreferrer"` added.

## Handle errors

Validation errors are `400 VALIDATION_FAILED`, and `issues` lists every problem with its path. The most common one is a `type` that doesn't match `fieldMeta.type`:

```json theme={null}
{
  "code": "VALIDATION_FAILED",
  "message": "fieldMeta.type is HTML, but the field type is TEXT. They must match.",
  "userMessage": "Förfrågan innehåller ogiltiga värden.",
  "requestId": "req_V1StGXR8Z5jdHi6BmyT2",
  "issues": [
    {
      "path": "fields.0.fieldMeta.type",
      "code": "INVALID_VALUE",
      "message": "fieldMeta.type is HTML, but the field type is TEXT. They must match."
    }
  ]
}
```

A field change on a document that isn't a `DRAFT` returns `409 INVALID_STATE`. Disallowed HTML isn't an error: sajn removes it. For the error shape and every code, see [Errors](/api-fundamentals/errors).

## Next steps

<CardGroup cols={2}>
  <Card title="Format HTML for JSON" icon="code" href="/guides/fields/formatting-html-content">
    Turn an HTML file into a valid request body.
  </Card>

  <Card title="Upload files" icon="file-arrow-up" href="/guides/documents/file-uploads">
    Host images and attach PDF files.
  </Card>

  <Card title="Send for signing" icon="paper-plane" href="/guides/documents/send-for-signing">
    Send the document to its parties.
  </Card>

  <Card title="Create document fields" icon="code" href="/api-reference/create-document-fields">
    See every field type in the API reference.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.