# Extracting Attachments

{% hint style="info" %}
We strongly recommend streaming attachments where possible.

Streaming allows attachments to be received directly by Unifi without embedding them in the payload, bypassing the need for extraction. It supports binary file transfer and attachment sizes of up to 50MB, subject to instance and integration configuration.

Inbound attachment streaming is available for all integrations created in Unifi 3.0 and later.

For more information on configuring inbound streaming, please see our [How to Handle Attachments](https://docs.sharelogic.com/unifi/configure/how-to-guides/how-to-handle-attachments) guide.
{% endhint %}

## Overview

Inbound attachments can be **extracted from the inbound payload and saved as ServiceNow attachments before the payload is stored**. This is strongly recommended, as it avoids storing large Base64-encoded attachment data directly on the HTTP Request record.

When extracting attachments:

1. Attachment data is detected in the inbound payload
2. The attachment is saved as a ServiceNow attachment
3. The attachment data in the payload is replaced with an attachment reference in the form:

```xml
<x-attachment-data sys_id="..."></x-attachment-data>
```

Unifi will then automatically:

* Collect the attachment `sys_id` values from the rewritten payload
* Create **Bonded Attachments** for each extracted attachment
* Move the attachments to the **target record** during processing

***

## Enable Attachment Extraction

Attachment extraction is enabled via **Message → Inbound → Settings → Extract attachments**.\
When enabled, Unifi executes the **Extract attachments** script against the **raw inbound payload**, before any parsing, mapping, or staging occurs.

The script is expected to extract any embedded attachment data, persist it as a ServiceNow attachment, and rewrite the payload by replacing the embedded data with a Unifi attachment pointer in the form `<x-attachment-data sys_id="..." />`.

Unifi then continues processing using the rewritten payload, automatically detecting attachment pointers, creating bonded attachments, and moving the extracted attachments to the resolved target record.

***

## Behavioural Requirements

The extract logic must:

* Locate attachment data in the inbound payload
* Save attachment content as a ServiceNow attachment
* Replace the original attachment data with an attachment pointer
* Return the rewritten payload to Unifi to be saved as the HTTP Request payload

Unifi relies on the `<x-attachment-data sys_id="..."/>` pointer to identify and process extracted attachments.

## Extraction Pattern

Attachments must be located and the attachment content replaced within the payload structure. The extraction workflow remains the same regardless of type (JSON or XML):

1. **Parse the inbound payload** into a structure you can traverse
2. **Locate each embedded attachment** (e.g. an `Attachment` node/object)
3. **Extract attachment metadata and content** (for example file name, MIME type, Base64 data)
4. **Save the attachment** as a ServiceNow attachment
5. **Replace the embedded attachment content** in the payload with an attachment pointer:

   ```xml
   <x-attachment-data sys_id="..." /></x-attachment-data>
   ```

***

## JSON Example

This example demonstrates extracting attachments from a JSON payload, saving the attachment data, and replacing it in the payload with an attachment reference.

{% tabs %}
{% tab title="Inbound payload" %}

```json
{
  "DocumentID": "DOC-12345", 
  "Attachments": [
     {
        "FileName": "Hello world.txt",
        "MimeCode": "text/plain",
        "Data": "SGVsbG8gd29ybGQudHh0"
     }
  ]
}
```

{% endtab %}

{% tab title="Extract attachments script" %}

```javascript
// Example extract attachments script for extracting Base64 from an XML payload.
// We rewrite the payload to pass back to Unifi.
(function extractAttachments(payload, request) {
  
  // ------------------------------------------------------------------
  // XML node name configuration
  // ------------------------------------------------------------------
  var ATTACHMENTS_CONTAINER = 'Attachments';
  var ATTACHMENT            = 'Attachment';
  var FILE_NAME             = 'FileName';
  var MIME_CODE             = 'MimeCode';
  var DATA                  = 'Data';


  // ------------------------------------------------------------------
  // Parse inbound JSON payload
  // ------------------------------------------------------------------
  var obj = JSON.parse(payload);
  var attachments = obj[ATTACHMENTS_CONTAINER];
  
  if (attachments && attachments.length) {
    for (var i = 0; i < attachments.length; i++) {
      var att = attachments[i];
      if (!att || typeof att !== 'object')
        continue;

      // Replace only the attachment Data value with the sys_attachment pointer
      att[DATA] = saveAttachment(
        request,
        att[FILE_NAME],
        att[MIME_CODE],
        att[DATA]
      );
    }
  }
  
  payload = JSON.stringify(obj);


  // ------------------------------------------------------------------
  // Helpers
  // ------------------------------------------------------------------
  
  // Create an attachment and link it to a record
  function saveAttachment(record, filename, content_type, data) {
    var id;

    if (typeof filename === 'undefined') {
      x_snd_eb.ws_console.warn('Ignoring attachment with missing filename attribute.');
      id = 'x-attachment-error-missing-filename';
    } else if (!filename) {
      x_snd_eb.ws_console.warn('Ignoring attachment with empty filename attribute.');
      id = 'x-attachment-error-filename-is-empty';
    } else {
      // use x_snd_eb scope to force using the scoped version of GSA
      id = new x_snd_eb.GlideSysAttachment().writeBase64(record, filename, content_type, data);
      x_snd_eb.ws_console.trace('Extracted attachment [' + filename + ':' + content_type + ']' +
        ' to record [' + id + ']' +
        ' for [' + record.getTableName() + ':' + record.sys_id + ']');
    }

    // return the original tag with the sys_id of the new attachment record
    // so Unifi can pick it up in processing later on
    return '<x-attachment-data sys_id="' + id + '" />';
  }

})(payload, request);
```

{% endtab %}

{% tab title="Processed payload" %}

```javascript
{
  "DocumentID": "DOC-12345", 
  "Attachments": [
     {
        "FileName": "Hello world.txt",
        "MimeCode": "text/plain",
        "Data": "<x-attachment-data sys_id=\"8ede849b47def25056f6edba216d4358\"></x-attachment-data>"
     }
  ]
}
```

{% endtab %}
{% endtabs %}

***

## XML Example

This example demonstrates extracting attachments from an XML (SOAP) payload, saving the attachment data, and replacing the attachment data element with an attachment reference.

{% tabs %}
{% tab title="Inbound payload" %}

```xml
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                  xmlns:eb="https://sharelogic.com/unifi">
  <soapenv:Header/>
  <soapenv:Body>
    <eb:UpdateIncident>
      <eb:DocumentId>DOC-12345</eb:DocumentId>
      <eb:Attachments>
          <eb:Attachment>
            <eb:FileName>Hello world.txt</eb:FileName>
            <eb:MimeCode>text/plain</eb:MimeCode>
            <eb:Data>SGVsbG8gd29ybGQudHh0</eb:Data>
          </eb:Attachment>
      </eb:Attachments>
    </eb:UpdateIncident>
  </soapenv:Body>
</soapenv:Envelope>
```

{% endtab %}

{% tab title="Extract attachments script" %}

```javascript
// Example extract attachments script for extracting Base64 from an XML payload.
// We rewrite the payload to pass back to Unifi.
(function extractAttachments(payload, request) {
  
  // ------------------------------------------------------------------
  // XML node name configuration
  // ------------------------------------------------------------------
  var ATTACHMENTS_CONTAINER_NODE = 'eb:Attachments';
  var ATTACHMENT_NODE            = 'eb:Attachment';
  var FILE_NAME_NODE             = 'eb:FileName';
  var MIME_CODE_NODE             = 'eb:MimeCode';
  var DATA_NODE                  = 'eb:Data';


  // ------------------------------------------------------------------
  // Parse inbound XML payload
  // ------------------------------------------------------------------
  var xml_doc = new XMLDocument(payload.toString());
  var attachment_container;
  
  attachment_container = xml_doc.getElementByTagName(ATTACHMENTS_CONTAINER_NODE);
  if (!attachment_container) {
    _console.log('No attachments found.');
    return; // No attachments
  }
  
  forEachNode(attachment_container.getChildNodes(), extract);
  payload = xml_doc.toString();


  // ------------------------------------------------------------------
  // Helpers
  // ------------------------------------------------------------------

  // Attempt to extract an attachment from a given attachment node.
  function extract(node) {
    if (node.getNodeName() == ATTACHMENT_NODE) {
      var data = {};
      
      // Collect attachment data
      forEachNode(node.getChildNodes(), function (attr) {
        var name = attr.getNodeName();
        if (name == '#text') return;
        data[name] = '' + attr.getTextContent();
        
        if (name == DATA_NODE) {
          attr.setTextContent(''); // remove the attachment data
        }
      });

      // Save the attachment with some validation.
      // Returns '<x-attachment-data sys_id="..." />';
      var pointer = x_snd_eb.AttachmentHandler.saveAttachment(
        request,
        data[FILE_NAME_NODE],
        data[MIME_CODE_NODE],
        decodeData(data[DATA_NODE])
      );

      // Replace the data node with the Unifi attachment pointer
      insertPointer(node, pointer);
    }
  }

  // Insert the attachment pointer for Unifi to pick up
  function insertPointer(node, tag) {
    var id;
    var data;

    data = node.getElementsByTagName(DATA_NODE);
    if (data.getLength() > 0) {
      xml_doc.setCurrent(data.item(0));
      xml_doc.setCurrent(xml_doc.createElement('x-attachment-data'));
      id = tag.match(/sys_id="(.+)"/);
      xml_doc.setAttribute('sys_id', id ? id[1] : 'unknown');
    }
  }

  // Execute a function on each node in an XML node list.
  function forEachNode(node_list, fn) {
    var len = node_list.getLength(),
        i;
    for (i = 0; i < len; i++) {
      fn(node_list.item(i), i);
    }
  }

  // Decode Base64 data using the best approach.
  // Unifi global util is recommended for supporting binary attachments.
  function decodeData(data) {
    data = ('' + data).replace(/\s/g, '');
    if (global.snd_eb_util && global.snd_eb_util.writeAttachment) {
      return GlideStringUtil.base64DecodeAsBytes(data);
    } else {
      return GlideStringUtil.base64Decode(data);
    }
  }

})(payload, request);
```

{% endtab %}

{% tab title="Processed payload" %}

```xml
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                  xmlns:eb="https://sharelogic.com/unifi">
  <soapenv:Header/>
  <soapenv:Body>
    <eb:UpdateIncident>
      <eb:DocumentId>DOC-12345</eb:DocumentId>
      <eb:Attachments>
          <eb:Attachment>
            <eb:FileName>Hello world.txt</eb:FileName>
            <eb:MimeCode>text/plain</eb:MimeCode>
            <eb:Data>
              <x-attachment-data sys_id="8ede849b47def25056f6edba216d4358"></x-attachment-data>
            </eb:Data>
          </eb:Attachment>
      </eb:Attachments>
    </eb:UpdateIncident>
  </soapenv:Body>
</soapenv:Envelope>
```

{% endtab %}
{% endtabs %}
