# Deleting Attachments

## Overview

Attachment deletion is **not built into Unifi by default** because it is not a common integration requirement.

Where attachment deletion is required, it is typically driven by **constraints in legacy target systems**, such as:

* Hard limits on attachment counts
* Strict retention or compliance rules
* Target systems that require explicit attachment lifecycle management

For these scenarios, Unifi supports attachment deletion through a **Business Rule–driven outbound pattern**.

***

## Recommended Approach

In most integrations, attachment deletion is **not required** and should be avoided unless explicitly mandated by the target system.

Where deletion is required, Unifi treats it as an **explicit event** that must be communicated to the target system, rather than an automatic or implicit operation.

***

## Watching Deleted Attachments

Since Unifi does not include anything to support deletions by default, an update set is available which contains a **single Business Rule** that executes when an attachment is deleted.

The Business Rule:

* Runs on attachment deletion
* Performs **one query** against the `x_snd_eb_attachment` table
* Only proceeds if a corresponding **Unifi Bonded Attachment** exists
* Updates the Bonded Attachment record to indicate the attachment has been deleted
* Allows outbound message processing to continue as normal

This ensures that **only attachments known to Unifi** are considered.

{% file src="<https://content.gitbook.com/content/Dk6VgKBXe6rYT8F9wuaY/blobs/9wlAeruP43lRlj1aCQc9/ShareLogic%20Unifi%20-%20Deleted%20Attachments%20Patch.xml>" %}

***

## Deletion Context Exposed by Unifi

When an attachment deletion is detected, Unifi exposes a variable on the source record:

```
source.$deleted_attachment
```

This variable is an object containing:

* `sys_id`
* `file_name`

**Important clarification:**

* The **source** is the original business record (for example, an Incident or Case)
* The source is **not** the attachment record itself

***

## Triggering an “Attachment Deleted” Message

The trigger condition for an outbound *attachment deleted* message should simply be:

```
source.$deleted_attachment
```

No additional conditions are required.

***

## Source to Stage Mapping

In the **Source to Stage** script, map the deleted attachment directly:

```javascript
$stage.deleted_attachment = source.$deleted_attachment;
```

This makes the deletion context available for payload construction, URL parameters, or headers.

***

## Using the Deleted Attachment in the Payload

You can use the data to support any payload structure you require. For example:

```javascript
payload.deleted_attachment = $stage.deleted_attachment;
```

or

```json
{
  "attachment": {
    "sys_id": $stage.deleted_attachment.sys_id,
    "file_name": $stage.deleted_attachment.file_name
  }
}
```

{% hint style="info" %}
**Note:** It is not necessary to share attachment `sys_id` values unless both systems explicitly track attachments by `sys_id`; in most cases, attachment deletion can be performed using the file name alone.
{% endhint %}

***

## Example: Deleting Attachments in Another Unifi Instance

{% hint style="info" %}
This example uses minimal message scripts to illustrate the core behaviour. In practice, we recommend using **Fields** and **Field Maps** to improve maintainability and produce clearer, more self-documenting integrations.
{% endhint %}

### Conceptual Clarification

Unifi does **not** synchronise attachment state.

Another Unifi instance must be treated exactly like any other target system and explicitly instructed to delete an attachment.

There is no automatic or implicit Unifi-to-Unifi attachment deletion.

***

### Instance A — Outbound Configuration

Create a new message named **`AttachmentDelete`**. This should be configured as an **Update** message, as it notifies the target system that an attachment deletion has occurred.

#### Trigger

```
source.$deleted_attachment
```

#### Source to Stage

```javascript
$stage.deleted_attachment = source.$deleted_attachment;
```

#### Example Payload

```json
{
  "header": {
    "name": "AttachmentDelete",
    "source_reference": "INC00001337",
    "target_reference": "INC00031416",
    "source": "instance-A.example",
    "source_id": "TXN0001055964.01",
    "destination": "instance-B.example"
  },
  "attachment": {
    "sys_id": $stage.deleted_attachment.sys_id,
    "file_name": $stage.deleted_attachment.file_name
  }
}
```

***

### Instance B — Inbound Configuration

Instance B must be configured with an **Inbound Message** that matches on the message name from Instance A and explicitly deletes the attachment once the target record has been found.

Message processing should run the same as a standard **Update** message, staging the payload data first and only once the target record has been found (in order to update) should the attachment be identified and deleted in the Stage to Target script.

#### Example Inbound Payload to Stage Script

```javascript
stage.internal_reference = payload.header.target_reference;
stage.external_reference = payload.header.source_reference;

$stage.deleted_attachment = payload.deleted_attachment;
```

#### Example Inbound Stage to Target Script

```javascript
(function deleteAttachment() {

  // ------------------------------------------------------------------
  // Configuration
  // ------------------------------------------------------------------
  // Behaviour when multiple attachments exist with the same file_name:
  //  - 'first' : delete the first match only
  //  - 'none'  : delete nothing
  //  - 'all'   : delete all matches
  var MULTIPLE_MATCH_BEHAVIOUR = 'first';

  // ------------------------------------------------------------------
  // Guards
  // ------------------------------------------------------------------
  if (!$stage.deleted_attachment) {
    _console.warn('Attachment delete skipped: no deleted_attachment context found on stage.');
    return;
  }

  var file_name = stage.deleted_attachment.file_name;
  if (!file_name) {
    _console.warn('Attachment delete skipped: deleted_attachment.file_name is missing.');
    return;
  }

  // ------------------------------------------------------------------
  // Target identity
  // ------------------------------------------------------------------
  var target_table = target.getTableName();
  var target_id = target.getUniqueValue();

  // ------------------------------------------------------------------
  // Query all matches on this target record for this file name
  // ------------------------------------------------------------------
  var attachment_ids = [];
  var attachment = new GlideRecord('sys_attachment');
  attachment.addQuery('table_name', target_table);
  attachment.addQuery('table_sys_id', target_id);
  attachment.addQuery('file_name', file_name);
  attachment.query();

  while (attachment.next()) {
    attachment_ids.push(attachment.getUniqueValue());
  }

  if (!attachment_ids.length) {
    _console.info(
      'Attachment delete skipped: no attachment named [' + file_name +
      '] found on [' + target_table + ':' + target_id + '].'
    );
    return;
  }

  // ------------------------------------------------------------------
  // Multiple match handling
  // ------------------------------------------------------------------
  if (attachment_ids.length > 1) {

    if (MULTIPLE_MATCH_BEHAVIOUR === 'none') {
      _console.info(
        'Multiple attachments found (' + attachment_ids.length + ') named [' +
        file_name + '] on [' + target_table + ':' + target_id +
        ']. Behaviour=none, no deletions performed.'
      );
      return;
    }

    if (MULTIPLE_MATCH_BEHAVIOUR === 'first') {
      _console.info(
        'Multiple attachments found (' + attachment_ids.length + ') named [' +
        file_name + '] on [' + target_table + ':' + target_id +
        ']. Behaviour=first, deleting first match only (sys_id=' + attachment_ids[0] + ').'
      );
      attachment_ids = [attachment_ids[0]];
    } else if (MULTIPLE_MATCH_BEHAVIOUR === 'all') {
      _console.info(
        'Multiple attachments found (' + attachment_ids.length + ') named [' +
        file_name + '] on [' + target_table + ':' + target_id +
        ']. Behaviour=all, deleting all matches.'
      );
      // keep attachment_ids as-is
    } else {
      _console.warn(
        'Attachment delete skipped: invalid MULTIPLE_MATCH_BEHAVIOUR [' +
        MULTIPLE_MATCH_BEHAVIOUR + ']. Expected "first", "none", or "all".'
      );
      return;
    }
  }

  // ------------------------------------------------------------------
  // Delete selected attachment(s)
  // ------------------------------------------------------------------
  var gsa = new GlideSysAttachment();
  for (var i = 0; i < attachment_ids.length; i++) {
    gsa.deleteAttachment(attachment_ids[i]);
  }

  _console.info(
    'Attachment delete complete: deleted ' + attachment_ids.length + ' attachment(s) named [' +
    file_name + '] on [' + target_table + ':' + target_id + '].'
  );

})();

```

This deletion is:

* Explicit
* Local to Instance B
* Governed by Instance B’s access controls and rules

***

### Key Points to Understand

* Attachment deletion is **event-driven**, not automatic
* Unifi does **not** synchronise attachments between instances
* Each instance:
  * Detects its own deletions
  * Sends its own events
  * Performs its own attachment operations
* Another Unifi instance is just a **target system**, not a peer

***

### Summary

* Attachment deletion is intentionally not a core Unifi feature
* It is supported where required via a Business Rule
* Deletion events expose `$deleted_attachment` on the source
* Outbound messages notify target systems
* Target systems (including other Unifi instances) must explicitly delete attachments

This approach keeps Unifi predictable, explicit, and aligned with real-world integration constraints.
