## File: README.md

# imessage-exporter

This crate provides both a library to interact with iMessage data as well as a binary that can perform some useful read-only operations using that data. The aim of this project is to provide the most comprehensive and accurate representation of iMessage data available.

This free and open-source software can:

- Save, export, backup, and archive iMessage data to open, portable formats
- Preserve multimedia content (images, videos, audio) from conversations
- Facilitate easy migration of message history between devices and platforms
- Run diagnostics on the iMessage database
- Give you full ownership and control over your communication history
- Support compliance with data retention policies or legal requirements
- Run on macOS, Linux, and Windows

## Example Export



## Binary

The `imessage-exporter` binary exports iMessage data to `txt` or `html` formats. It can also run diagnostics to find problems with the iMessage database.

Installation instructions for the binary are located [here](imessage-exporter/README.md).

## Library

The `imessage_database` library provides models that allow us to access iMessage information as native, cross-platform data structures.

Documentation for the library is located [here](imessage-database/README.md).

### Supported Features

This crate supports every iMessage feature as of macOS Golden Gate 27.0 and iOS 27.0:

- iMessage, RCS, SMS, and MMS
- Multi-part messages
- Replies/Threads
- Formatted text
- Attachments
- Expressives
- Tapbacks
- Stickers
- Apple Pay
- Group chats
- Digital Touch
- URL Previews
- Audio messages
- App Integrations
- Edited messages
- Business messages
- Handwritten messages

See more detail about supported features [here](docs/features.md).

## Frequently Asked Questions

The FAQ document is located [here](/docs/faq.md).

## Special Thanks

- All of my friends, for putting up with me sending them random messages to test things
- [SQLiteFlow](https://www.sqliteflow.com), the SQL viewer I used to explore and reverse engineer the iMessage database
- [Xplist](https://github.com/ic005k/Xplist), an invaluable tool for reverse engineering the `payload_data` plist format
- [Compart](https://www.compart.com/en/unicode/), an amazing resource for looking up esoteric unicode details
- [GNU Project](https://github.com/gnustep/libobjc) and [Archive.org](https://archive.org/details/darwin_0.1), for hosting source code referenced to reverse engineer the `typedstream` format

---

## File: docs/tables/attachment.md

# Attachment Table Structure

| cid | name                           | type    | notnull | dflt_value | pk |
|:----|:-------------------------------|:--------|:--------|:-----------|:---|
| 0   | ROWID                          | INTEGER | 0       |            | 1  |
| 1   | guid                           | TEXT    | 1       |            | 0  |
| 2   | created_date                   | INTEGER | 0       | 0          | 0  |
| 3   | start_date                     | INTEGER | 0       | 0          | 0  |
| 4   | filename                       | TEXT    | 0       |            | 0  |
| 5   | uti                            | TEXT    | 0       |            | 0  |
| 6   | mime_type                      | TEXT    | 0       |            | 0  |
| 7   | transfer_state                 | INTEGER | 0       | 0          | 0  |
| 8   | is_outgoing                    | INTEGER | 0       | 0          | 0  |
| 9   | user_info                      | BLOB    | 0       |            | 0  |
| 10  | transfer_name                  | TEXT    | 0       |            | 0  |
| 11  | total_bytes                    | INTEGER | 0       | 0          | 0  |
| 12  | is_sticker                     | INTEGER | 0       | 0          | 0  |
| 13  | sticker_user_info              | BLOB    | 0       |            | 0  |
| 14  | attribution_info               | BLOB    | 0       |            | 0  |
| 15  | hide_attachment                | INTEGER | 0       | 0          | 0  |
| 16  | ck_sync_state                  | INTEGER | 0       | 0          | 0  |
| 17  | ck_server_change_token_blob    | BLOB    | 0       |            | 0  |
| 18  | ck_record_id                   | TEXT    | 0       |            | 0  |
| 19  | original_guid                  | TEXT    | 1       |            | 0  |
| 20  | is_commsafety_sensitive        | INTEGER | 0       | 0          | 0  |
| 21  | emoji_image_content_identifier | TEXT    | 0       | NULL       | 0  |
| 22  | emoji_image_short_description  | TEXT    | 0       | NULL       | 0  |
| 23  | preview_generation_state       | INTEGER | 0       | 0          | 0  |

---

## File: docs/tables/chat.md

# Chat Table Structure

| cid | name                          | type    | notnull | dflt_value | pk |
|:----|:------------------------------|:--------|:--------|:-----------|:---|
| 0   | ROWID                         | INTEGER | 0       |            | 1  |
| 1   | guid                          | TEXT    | 1       |            | 0  |
| 2   | style                         | INTEGER | 0       |            | 0  |
| 3   | state                         | INTEGER | 0       |            | 0  |
| 4   | account_id                    | TEXT    | 0       |            | 0  |
| 5   | properties                    | BLOB    | 0       |            | 0  |
| 6   | chat_identifier               | TEXT    | 0       |            | 0  |
| 7   | service_name                  | TEXT    | 0       |            | 0  |
| 8   | room_name                     | TEXT    | 0       |            | 0  |
| 9   | account_login                 | TEXT    | 0       |            | 0  |
| 10  | is_archived                   | INTEGER | 0       | 0          | 0  |
| 11  | last_addressed_handle         | TEXT    | 0       |            | 0  |
| 12  | display_name                  | TEXT    | 0       |            | 0  |
| 13  | group_id                      | TEXT    | 0       |            | 0  |
| 14  | is_filtered                   | INTEGER | 0       | 0          | 0  |
| 15  | successful_query              | INTEGER | 0       |            | 0  |
| 16  | engram_id                     | TEXT    | 0       |            | 0  |
| 17  | server_change_token           | TEXT    | 0       |            | 0  |
| 18  | ck_sync_state                 | INTEGER | 0       | 0          | 0  |
| 19  | original_group_id             | TEXT    | 0       |            | 0  |
| 20  | last_read_message_timestamp   | INTEGER | 0       | 0          | 0  |
| 21  | cloudkit_record_id            | TEXT    | 0       |            | 0  |
| 22  | last_addressed_sim_id         | TEXT    | 0       |            | 0  |
| 23  | is_blackholed                 | INTEGER | 0       | 0          | 0  |
| 24  | syndication_date              | INTEGER | 0       | 0          | 0  |
| 25  | syndication_type              | INTEGER | 0       | 0          | 0  |
| 26  | is_recovered                  | INTEGER | 0       | 0          | 0  |
| 27  | is_deleting_incoming_messages | INTEGER | 0       | 0          | 0  |
| 28  | is_pending_review             | INTEGER | 0       | 0          | 0  |

---

## File: docs/tables/duplicates.md

# The Duplicate Problem

iMessage has had a long history of splitting both one-on-one and group chats into separate chats. This creates a problem for this tool: in order to iterate over the table of Messages to export them, the tool has to understand where to sort them.

## Types of Duplicated Data

Both handles (participants) and chats (threads) can become duplicated, where handle duplication leads to chat duplication.

### Duplicate Handles

Handles (contacts) are often duplicated, usually when a contact sends messages from a phone number and an iCloud account. As a result. the phone number and the email address for the same contact may exist under different `Handle ID`s.

### Duplicate Conversations

Because of the aforementioned handle problem, some conversations with the same handle are mapped into separate `Chat ID`s.

## Sample Data

These sample tables help visualize the problem.

### Handles Table

Contact `A` has an email `Y` and a phone number `X`. Each are listed in the table under a different `Handle ID`. Contact `B` has a single phone number `Z`. The iMessage database makes no connection to your Contacts app here, it just knows these items exist.

| Handle ID | Contact | Person Centric ID |
| -- | -- | -- |
| 1 | Y | A |
| 2 | X | A |
| 3 | Z | B |

The `Person Centric ID` is a unique identifier for each contact. Here, we can use it to determine which `Handle`s belong to `A` and which belong to `B`.

### Chats Table

When you receive a message from `A`, you either receive from the address `Y` or `Z`. Each of these is listed under a separate chat (thread):

| Chat ID | Handle ID | Group ID |
| -- | -- | -- |
| 10 | 1 | A |
| 11 | 2 | A |
| 12 | 3 | B |
| 12 | 2 | B |

Note: The `Chat ID` is not unique, it represents group chats as well, so in this example there is a group chat `12` with 3 participants, yourself, `A`, and `B`.

## The Many-To-Many Problem

Chats between yourself and `A` *should* go to the same chat, because `Y` and `X` are actually the same person. Further, any messages sent to chat `12` from any of the addresses of the participants *should* go to the same chat, not a separate one with the same participants. This does not happen, because the iMessage Database uses the `Handle ID` field here, not the `Person Centric ID`.

If contact `A` sends messages from `Y` and `X`, you will see two separate conversations. Given the above table, messages from `Y` will go to `10` and messages from `X` will go to `11`, even though said messages should belong to the same chat.

Further, if contact `A` sends messages from `Y` and `X` to you and `B`, only messages from `X` will go to chat `12`. Messages `A` sends from `Y` will go to a new chat, `13`, that contains the same participants but with `A` under a second ID:

| Chat ID | Handle ID |
| -- | -- |
| 10 | 1 |
| 11 | 2 |
| 12 | 3 |
| 12 | 2 |
| 13 | 1 |
| 13 | 3 |

Chats `12` and `13` are the same group of people, but because the IDs of the participants are different, the messages get sorted into a separate chat.

## Solving the Problems

### Handles Solution

Applying a small two-pass check solves this problem, since the handles table only contains a few columns of metadata.

First, generate a map of each `Person Centric ID` to its corresponding handles. The second pass iterates over each handle in the table, building a hashmap of `Handle ID` to a string that combines all of the corresponding handles' metadata. For example, for the above table, we would generate a hashmap that looks like

```json
{
    1: "X, Y",
    2: "X, Y",
    3: "Z"
}
```

Now, any lookups against a contact ID will result in the same metadata.

### Chats Solution

`Person Centric ID` gives us a unique field that exists on a per-contact basis. In the above handles table, all of the handles associated with `A` have the same `Person Centric ID`. If we want to map our conversations against this, we need to fix this many-to-many problem. We can solve it with a few hash tables, with the second containing a new field:

1. Receive a reference to the hashmap generated for the `Handle` table
2. Participants to Unique Chat ID: `Set(Person Centric ID)` -> Set(`Chat ID`)
3. Chat to Unique Chat: `Chat ID` -> `Unique Chat ID`

The steps to generate this unique chat identifier are as follows:

- Generate hashmap `1` that contains all of the sets of participants
- Generate hashmap `2` by iterating through the values of `1`, inserting each new set of participants
- Generate hashmap `3` by inverting hashmap `2`

From here, when we iterate over messages, we can use the `Chat to Unique Chat` table to organize our messages into the proper chats.

---

## File: docs/tables/handle.md

# Handle Table Structure

| cid | name               | type    | notnull | dflt_value | pk |
|:----|:-------------------|:--------|:--------|:-----------|:---|
| 0   | ROWID              | INTEGER | 0       |            | 1  |
| 1   | id                 | TEXT    | 1       |            | 0  |
| 2   | country            | TEXT    | 0       |            | 0  |
| 3   | service            | TEXT    | 1       |            | 0  |
| 4   | uncanonicalized_id | TEXT    | 0       |            | 0  |
| 5   | person_centric_id  | TEXT    | 0       |            | 0  |

---

## File: docs/tables/messages.md

# Messages Table Structure

| cid | name                              | type    | notnull | dflt_value | pk |
|:----|:----------------------------------|:--------|:--------|:-----------|:---|
| 0   | ROWID                             | INTEGER | 0       |            | 1  |
| 1   | guid                              | TEXT    | 1       |            | 0  |
| 2   | text                              | TEXT    | 0       |            | 0  |
| 3   | replace                           | INTEGER | 0       | 0          | 0  |
| 4   | service_center                    | TEXT    | 0       |            | 0  |
| 5   | handle_id                         | INTEGER | 0       | 0          | 0  |
| 6   | subject                           | TEXT    | 0       |            | 0  |
| 7   | country                           | TEXT    | 0       |            | 0  |
| 8   | attributedBody                    | BLOB    | 0       |            | 0  |
| 9   | version                           | INTEGER | 0       | 0          | 0  |
| 10  | type                              | INTEGER | 0       | 0          | 0  |
| 11  | service                           | TEXT    | 0       |            | 0  |
| 12  | account                           | TEXT    | 0       |            | 0  |
| 13  | account_guid                      | TEXT    | 0       |            | 0  |
| 14  | error                             | INTEGER | 0       | 0          | 0  |
| 15  | date                              | INTEGER | 0       |            | 0  |
| 16  | date_read                         | INTEGER | 0       |            | 0  |
| 17  | date_delivered                    | INTEGER | 0       |            | 0  |
| 18  | is_delivered                      | INTEGER | 0       | 0          | 0  |
| 19  | is_finished                       | INTEGER | 0       | 0          | 0  |
| 20  | is_emote                          | INTEGER | 0       | 0          | 0  |
| 21  | is_from_me                        | INTEGER | 0       | 0          | 0  |
| 22  | is_empty                          | INTEGER | 0       | 0          | 0  |
| 23  | is_delayed                        | INTEGER | 0       | 0          | 0  |
| 24  | is_auto_reply                     | INTEGER | 0       | 0          | 0  |
| 25  | is_prepared                       | INTEGER | 0       | 0          | 0  |
| 26  | is_read                           | INTEGER | 0       | 0          | 0  |
| 27  | is_system_message                 | INTEGER | 0       | 0          | 0  |
| 28  | is_sent                           | INTEGER | 0       | 0          | 0  |
| 29  | has_dd_results                    | INTEGER | 0       | 0          | 0  |
| 30  | is_service_message                | INTEGER | 0       | 0          | 0  |
| 31  | is_forward                        | INTEGER | 0       | 0          | 0  |
| 32  | was_downgraded                    | INTEGER | 0       | 0          | 0  |
| 33  | is_archive                        | INTEGER | 0       | 0          | 0  |
| 34  | cache_has_attachments             | INTEGER | 0       | 0          | 0  |
| 35  | cache_roomnames                   | TEXT    | 0       |            | 0  |
| 36  | was_data_detected                 | INTEGER | 0       | 0          | 0  |
| 37  | was_deduplicated                  | INTEGER | 0       | 0          | 0  |
| 38  | is_audio_message                  | INTEGER | 0       | 0          | 0  |
| 39  | is_played                         | INTEGER | 0       | 0          | 0  |
| 40  | date_played                       | INTEGER | 0       |            | 0  |
| 41  | item_type                         | INTEGER | 0       | 0          | 0  |
| 42  | other_handle                      | INTEGER | 0       | 0          | 0  |
| 43  | group_title                       | TEXT    | 0       |            | 0  |
| 44  | group_action_type                 | INTEGER | 0       | 0          | 0  |
| 45  | share_status                      | INTEGER | 0       | 0          | 0  |
| 46  | share_direction                   | INTEGER | 0       | 0          | 0  |
| 47  | is_expirable                      | INTEGER | 0       | 0          | 0  |
| 48  | expire_state                      | INTEGER | 0       | 0          | 0  |
| 49  | message_action_type               | INTEGER | 0       | 0          | 0  |
| 50  | message_source                    | INTEGER | 0       | 0          | 0  |
| 51  | associated_message_guid           | TEXT    | 0       |            | 0  |
| 52  | associated_message_type           | INTEGER | 0       | 0          | 0  |
| 53  | balloon_bundle_id                 | TEXT    | 0       |            | 0  |
| 54  | payload_data                      | BLOB    | 0       |            | 0  |
| 55  | expressive_send_style_id          | TEXT    | 0       |            | 0  |
| 56  | associated_message_range_location | INTEGER | 0       | 0          | 0  |
| 57  | associated_message_range_length   | INTEGER | 0       | 0          | 0  |
| 58  | time_expressive_send_played       | INTEGER | 0       |            | 0  |
| 59  | message_summary_info              | BLOB    | 0       |            | 0  |
| 60  | ck_sync_state                     | INTEGER | 0       | 0          | 0  |
| 61  | ck_record_id                      | TEXT    | 0       |            | 0  |
| 62  | ck_record_change_tag              | TEXT    | 0       |            | 0  |
| 63  | destination_caller_id             | TEXT    | 0       |            | 0  |
| 64  | is_corrupt                        | INTEGER | 0       | 0          | 0  |
| 65  | reply_to_guid                     | TEXT    | 0       |            | 0  |
| 66  | sort_id                           | INTEGER | 0       |            | 0  |
| 67  | is_spam                           | INTEGER | 0       | 0          | 0  |
| 68  | has_unseen_mention                | INTEGER | 0       | 0          | 0  |
| 69  | thread_originator_guid            | TEXT    | 0       |            | 0  |
| 70  | thread_originator_part            | TEXT    | 0       |            | 0  |
| 71  | syndication_ranges                | TEXT    | 0       |            | 0  |
| 72  | synced_syndication_ranges         | TEXT    | 0       |            | 0  |
| 73  | was_delivered_quietly             | INTEGER | 0       | 0          | 0  |
| 74  | did_notify_recipient              | INTEGER | 0       | 0          | 0  |
| 75  | date_retracted                    | INTEGER | 0       | 0          | 0  |
| 76  | date_edited                       | INTEGER | 0       | 0          | 0  |
| 77  | was_detonated                     | INTEGER | 0       | 0          | 0  |
| 78  | part_count                        | INTEGER | 0       |            | 0  |
| 79  | is_stewie                         | INTEGER | 0       | 0          | 0  |
| 80  | is_kt_verified                    | INTEGER | 0       | 0          | 0  |
| 81  | is_sos                            | INTEGER | 0       | 0          | 0  |
| 82  | is_critical                       | INTEGER | 0       | 0          | 0  |
| 83  | bia_reference_id                  | TEXT    | 0       | NULL       | 0  |
| 84  | fallback_hash                     | TEXT    | 0       | NULL       | 0  |
| 85  | associated_message_emoji          | TEXT    | 0       | NULL       | 0  |
| 86  | is_pending_satellite_send         | INTEGER | 0       | 0          | 0  |
| 87  | needs_relay                       | INTEGER | 0       | 0          | 0  |
| 88  | schedule_type                     | INTEGER | 0       | 0          | 0  |
| 89  | schedule_state                    | INTEGER | 0       | 0          | 0  |
| 90  | sent_or_received_off_grid         | INTEGER | 0       | 0          | 0  |
| 91  | date_recovered                    | INTEGER | 0       | 0          | 0  |
| 92  | is_time_sensitive                 | INTEGER | 0       | 0          | 0  |
| 93  | ck_chat_id                        | TEXT    | 0       |            | 0  |

---

## File: docs/diagnostics.md

# Diagnostics

Diagnostic output from `imessage-exporter` looks like:

```txt
iMessage Database Diagnostics

Handle diagnostic data:
    Total handles: 552
    Handles with more than one ID: 2
    Total duplicated handles: 100
Message diagnostic data:
    Total messages: 183453
    Messages not associated with a chat: 43210
    Messages belonging to more than one chat: 36
    Recoverable messages: 1
    Date range: Sep 20, 2019  1:53:14 PM to Mar 09, 2026  5:03:14 PM
                6 years, 170 days, 4 hours, 58 minutes, 24 seconds
Attachment diagnostic data:
    Total attachments: 49422
        Data referenced in table: 44.13 GB
        Data present on disk: 31.31 GB
    Missing files: 15037 (30%)
        No path provided: 14929
        No file located: 108
Thread diagnostic data:
    Total chats: 432
    Total duplicated chats: 11
    Chats with no handles: 2
Global diagnostic data:
    Total database size: 339.88 MB
    Handles with resolved names: 231/452 (51%)
    Schema capabilities:
        Recoverable deleted messages: Detected
        Reply threads: Detected
        Tapbacks and poll votes: Detected
        Message filter categories: Detected

Environment Diagnostics

Detected converters:
    Image converter: sips
    Audio converter: afconvert
    Video converter: ffmpeg
```

## Handle diagnostic data

### Total handles

The total number of handles present in the provided iMessage database.

### Handles with more than one ID

The number of contacts that have multiple entries in the `handle` table, deduplicated by matching their `person_centric_id` across rows. The `person_centric_id` is a field used by Apple to disambiguate contacts. Further deduplication also happens, as noted in the next line.

### Total duplicated handles

In addition to the foregoing `person_centric_id`, other deduplication steps map identical handles across services, number formats, and email formats to the same handle ID. The value reflects the number of handles that had multiple entries in the iMessage database coalesed into a single handle.

## Message diagnostic data

### Total messages

The total number of rows in the `messages` table.

### Messages not associated with a chat

If a message exists in the `messages` table but does not have an entry in the `chat_message_join` table, it is considered orphaned and will be listed in either the `Orphaned.html` or `Orphaned.txt` file in the export directory. Likely, these come from messages that were deleted and the chat removed from the `chat_message_join` table, but the corresponding messages were not removed from the `messages` table.

### Messages belonging to more than one chat

If a message exists in the `messages` table and maps to multiple chats in `chat_message_join`, the message will exist in all of those chats when exported.

## Recoverable messages

The number of messages that were marked as deleted from conversations but are recoverable and will be rendered in-line in exports.

## Date range

The range of total available messages in the database in both exact and relative format.

## Attachment diagnostic data

### Total attachments

The total number of rows in the `attachments` table

#### Data referenced in table

The sum of the `total_bytes` column in the `attachments` table. I don't know why they are different, but the former is the actual storage taken up by iMessage attachments.

#### Data present on disk

Represents the total size of the attachments listed in the `attachments` when following the listed path to the respective file. Missing files may have been removed by the user or not properly downloaded from iCloud.

### Missing files

The first line shows the count and the percentage of files missing. In the example above, `15037 (30%)` means that `15,037` files (`30%` of the total number of attachments) are referenced in the table but do not exist.

There are two different types of missing files:

#### No path provided

This means there was a row in the `attachments` table that did not contain a path to a file.

#### No file located

This means there was a path provided, but there was no file at the specified location.

## Thread diagnostic data

### Total chats

The total number of chats present in the table.

### Total duplicated chats

The number of split group chats that have been coalesced together.

### Chats with no handles

Emits the count of chats that contain no chat participants.

## Global diagnostic data

### Total database size

The total size of the database file on the disk.

### Handles with resolved names

The number of handles in the database that were successfully matched to contact names from the contacts index, out of the total number of handles found. This is followed by the match ratio as a percentage.

### Schema capabilities

`imessage-database` probes the database schema once and builds its queries from what the schema actually supports, so databases missing optional features still read correctly. This section shows which optional features the reader detected.

#### Recoverable deleted messages

Whether the `chat_recoverable_message_join` table exists. When present, messages deleted within the last 30 days can be read and filtered.

#### Reply threads

Whether the `message.thread_originator_guid` column exists. When present, replies can be grouped under the messages they respond to.

#### Tapbacks and poll votes

Whether the `message.associated_message_guid` column exists. When present, tapbacks and poll votes can be resolved to the messages they target.

#### Message filter categories

Whether the `message.filter_action` and `message.filter_sub_action` columns exist. When present, the filter category each message was received under is read alongside the row.

## Detected converters

`imessage-exporter` uses third-party tools to convert images when using `--copy-method basic` or `--copy-method full`. This section shows what programs are detected on the current system.

### Image converter

The currently detected image converter, if present.

One of:

- `sips` (macOS Builtin)
- `magick` (`imagemagick`)
- None

### Audio converter

The currently detected audio converter, if present.

One of:

- `afconvert` (macOS Builtin)
- `ffmpeg`
- None

### Video converter

The currently detected video converter, if present.

One of:

- `ffmpeg`
- None

---

## File: docs/faq.md

# Frequently Asked Questions

## I cannot connect to the messages database. What do I do?

Ensure your terminal emulator has [full disk access](https://kb.synology.com/en-us/C2/tutorial/How_to_enable_Full_Disk_Access_on_a_Mac) if using the default location or ensure that the path to the database file is correct. You can enable this in System Settings > Privacy & Security > Full Disk Access.

***

## Are emojis, tapbacks (reactions), and other special message features preserved in the export?

Yes, all iMessage features are supported. See [here](features.md) for more detail.

***

## Can it export messages from third-party apps that integrate with iMessage?

Yes. See [here](features.md) for more detail on supported features.

***

## Does `imessage-exporter` export message conversations that are in iCloud or on a user's iPhone/iPad but not on the user's Mac?

`imessage-exporter` only reads data present in the provided source, which can be a macOS `chat.db`, a local iOS backup (encrypted or unencrypted), or a jailbroken iOS `sms.db`. It cannot read data that is only stored in iCloud.

***

## Can I force iCloud to download attachments that were offloaded?

In the Messages app, if you click the info (`ⓘ`) button for a conversation and scroll to the bottom, there is a button that downloads all of the attachments for that conversation. This works on both macOS and iOS.



## Can it export group conversations as well as individual chats?

Yes.

***

## Can I export only specific conversations?

Yes, the `--conversation-filter` (`-t`) argument filters by contact name, phone number, or email. Multiple filters can be comma-separated, e.g., `-t "steve@apple.com,5558675309"`. Substring matching is supported, so `-t "+1555"` matches all numbers with that prefix. All conversations containing the matched participants are included.

See [here](../imessage-exporter/README.md#how-to-use) for details on `imessage-exporter` arguments.

***

## How does the exporter handle previously exported messages?

If files with the current output type exist in the output directory, `imessage-exporter` will alert the user and the export will not start. If the export directory is clear, `imessage-exporter` will export all messages by default. Alternatively, it will export messages between the dates specified by the `--start-date` and `--end-date` arguments.

See [here](../imessage-exporter/README.md#how-to-use) for details on `imessage-exporter` arguments.

***

## Is it possible to export a conversation and re-integrate it back onto another Apple ID?

No, I do not want to be trusted with write access to your iMessage data. This software is *read only*.

***

## Is there a search function?

No, this software just builds exports. I use [`ripgrep`](https://github.com/BurntSushi/ripgrep) to search though the exported files.

***

## Can it export messages between a specific date range?

Yes, the `--start-date` and `--end-date` arguments specify date ranges for exports.

See [here](../imessage-exporter/README.md#how-to-use) for details on `imessage-exporter` arguments.

***

## Are voice messages saved?

Expired ones cannot be because they are deleted from disk by the system. If you kept them, they are included in the exports.

***

## Are messages deleted from the messages app erased from the database?

This software can recover some, but not all, deleted messages.

Messages removed by deleting an entire conversation or by deleting a single message from a conversation are moved to a separate collection for up to 30 days. Messages present in this collection are restored to the conversations they belong to. Apple details this process [here](https://support.apple.com/en-us/HT202549#delete).

Messages that have expired from this restoration process are permanently deleted and cannot be recovered.

In some instances, deleted messages are removed from the `chat_message_join` table but not from the `messages` table. These messages will populate in `orphaned.html` or `orphaned.txt`.

***

## What is the `orphaned` file in my export?

Messages that cannot be associated with any conversation are written to `orphaned.html` or `orphaned.txt`. This can happen when a message's chat has been deleted from the `chat_message_join` table, or when the database has inconsistencies. These messages are preserved so no data is lost.

***

## What export formats are supported? Can I export to PDF?

`imessage-exporter` supports `txt` and `html` export formats. There is no native PDF export, but you can export as HTML and then print to PDF from Safari. Use the `--no-lazy` flag when exporting for PDF, as it disables lazy-loading of images which is required for the print-to-PDF workflow to include all images.

***

## How do I customize the appearance of HTML exports?

Each HTML export file links to an external `style.css` in the export directory. You can create this file to override the default styles. Since custom styles load after the embedded defaults, they take precedence at the same specificity.

***

## What does `--copy-method` do and which should I choose?

- `disabled` (default): Attachments are not copied; the export references them in-place by filesystem path
- `clone`: Copies all attachment files as-is, preserving original formats and quality
- `basic`: Copies all files, converting `HEIC` images to `JPEG` for broader compatibility
- `full`: Copies all files, converting `HEIC` to `JPEG`, `CAF`/`AMR` audio to `MP4`, `MOV` video to `MP4`, and animated sticker `HEICS` to `GIF`

`basic` and `full` require external tools (`sips` or ImageMagick for images, `afconvert` or `ffmpeg` for audio, `ffmpeg` for video). The `--diagnostics` output shows which converters are detected on your system.

***

## What does the `--diagnostics` flag show?

Diagnostics mode prints a summary of the database without exporting anything:

- Handle data: total handles, duplicated handles
- Message data: total messages, orphaned messages, messages in multiple chats, recoverable deleted messages, and the date range of all messages
- Attachment data: total attachments, referenced vs. on-disk data sizes, and missing file counts
- Thread data: total chats, duplicated chats, chats with no handles
- Global data: database file size, percentage of handles with resolved contact names, and detected media converters

***

## What does `--attachment-root` do?

This option overrides where the app looks for attachment files. It is useful when attachment data is stored separately from the database, such as on an external drive. The provided path replaces the default `~/Library/Messages` root and affects both the `Attachments` and `StickerCache` directories. It also works with jailbroken iOS `sms.db` databases (use `--platform macOS`). This option has no effect on iOS backups.

***

## Why do I see "Unable to build contacts index"?

This warning means the Contacts database could not be read. The export will continue, but participants will be labeled with phone numbers and email addresses instead of resolved contact names. This commonly happens due to missing Full Disk Access permissions, since the contacts database is also protected. You can also specify a custom contacts database path with `--contacts-path`.

***

## How fast is `imessage-exporter`?

This is a complicated question that depends on CPU, database size, chosen export type, encryption state, and chosen attachment handling style.

On my M1 Max MacBook Pro, approximate performance is as follows:

| `--copy-method` | Messages exported per second |
|---|---|
| `disabled` | > 145,000 |
| `clone` | ≈ 42,000 |
| `basic` | ≈ 350 |
| `full` | ≈ 250 |

For more information on `--copy-method`, see [here](../imessage-exporter/README.md#how-to-use) and [here](./features.md#supported-message-features).

However, if you recently deleted a large amount of data from Messages, the database will be slow for awhile, resulting in significantly reduced performance from `imessage-exporter`.

---

## File: docs/features.md

# Features

The library and binary crates aim to provide the most comprehensive and accurate representation of iMessage data available.

## Targeted Versions

This tool targets the current latest public release for Messages.app. It may work with older databases, but all features may not be available.

## Supported data sources

- Local macOS messages
- Encrypted or unencrypted local iOS backups
  - Unencrypted backups are resolved normally
  - Encrypted backups are resolved with [`crabapple`](https://github.com/ReagentX/crabapple)
- Jailbroken iOS filesystem data
  - Uses `sms.db`, which follows the same schema as macOS `chat.db`
  - Resolved as a macOS database with an alternate attachment root

## Supported Message Features

- Plain Text
  - Correctly extracts time-zone corrected timestamps
  - Detects when a message was read and calculates the time until read for both parties
    - Humanizes display of time-until-read duration
  - Parses `typedstream` message body data using [`crabstep`](https://github.com/ReagentX/crabstep)
  - Detects the service a message was sent from
    - In HTML exports, balloons are colored correctly for the service they were sent with
    - Supports iMessage, SMS, MMS, RCS, and Satellite (iMessage Lite)
  - Displays translated message content alongside the original text
  - Resolves contact names from the Contacts database when available
- Formatted Text
  - Parses formatted text ranges from `typedstream` message body data
  - Supports all iMessage text format ranges:
    - [Mentions](https://support.apple.com/guide/messages/mention-a-person-icht306ee34b/mac)
    - Hyperlinks
    - OTP/2FA
    - [Animations and Styles](https://support.apple.com/guide/iphone/style-and-animate-messages-iphe5c5af4d4/ios)
    - Data detectors, parsed from `DDScannerResult` payloads:
      - Unit and timezone conversions
      - Currency amounts (symbol and amount)
      - Postal addresses (street, city, state, postal code, and country)
      - Package tracking (carrier and tracking number)
      - Flights (airline and flight number)
- Edited and Unsent messages
  - Detects if messages components were edited or unsent
    - [Edited messages](https://support.apple.com/guide/iphone/unsend-and-edit-messages-iphe67195653/ios)
      - Parses `typedstream` edited body data
      - Displays content and timestamps for each edit
      - Humanizes display of edit timestamp gaps
      - Edited messages received before Ventura display as normal messages without history
    - Unsent messages
      - No content, but are noted in context
  - Recently deleted messages are recovered when available
- Multi-part messages
  - iMessages can have multiple parts, denoted by ranges in `typedstream` message body data
  - Parts are displayed as
    - New lines in TXT exports
    - Separate balloons in HTML exports
  - Handles Edited and Unsent parts
- Threads and Message Replies
  - [Threads](https://support.apple.com/en-us/104974) are displayed both threaded under the parent as well as in-place
    - This is to preserve context, which can be lost if replying to older messages
    - Messages from a thread and were rendered in-place are annotated as such
    - In HTML exports, threaded messages are hyperlinked to allow for easy reading in context
  - For multi-part messages, replies are threaded under the correct message part
- Attachments
  - Any type of attachment that can be displayed on the web is embedded in the HTML exports
  - Attachments can be copied to the export directory or referenced in-place
  - Less-compatible attachments can be converted for even more portable exports:
    - Image `HEIC` files convert to `JPEG`
    - Sticker `HEIC` files convert to `PNG`
    - Animated Sticker `HEICS` (HEIC sequence) files convert to `GIF`
    - Video `MOV` files convert to `mp4`
    - Audio `CAF` files convert to `mp4`
    - Audio `AMR` files convert to `mp4`
  - Attachments are displayed as
    - File paths in TXT exports
    - Embeds in HTML exports (including ``, `<video>`, and `<audio>`)
      - [Audio messages](https://support.apple.com/guide/messages/send-an-audio-message-icht204ef108/mac) include embedded transcripts
  - Attachment date metadata is set to the date and time of message receipt
- Expressives
  - Detects both bubble and screen [effects](https://support.apple.com/en-us/104970)
  - Messages sent with expressives are annotated
- Tapbacks
  - Detects [tapbacks](https://support.apple.com/guide/iphone/react-with-tapbacks-iph018d3c336/ios) to messages
  - Messages sent or received with tapbacks are annotated
  - For multi-part messages, tapbacks are placed under the correct message part
- Stickers
  - Detects [stickers](https://support.apple.com/guide/iphone/send-stickers-iph37b0bfe7b/ios) sent or placed on messages
  - Messages sent with stickers are
    - Displayed in HTML exports
    - Annotated in TXT exports
  - For multi-part messages, stickers are placed under the correct message part
  - Sticker effects are annotated in all exports
  - Sticker tapbacks are also supported
  - Genmoji are displayed with their prompt descriptions
  - Inline stickers (Memoji, Genmoji, and custom stickers placed within the message text) render inline in HTML exports, scaled like jumbo emoji (larger when only a few are present)
- Apple Pay
  - Displays Apple Pay transaction (source, amount, type) messages
- URL previews
  - Parses the `NSKeyedArchiver` payload to extract preview data
    - Extracts cached metadata for each URL
    - Preview images display in HTML exports
    - URLs that have rotten may still retain some context if they have cached data
  - Handles cases where URL messages are overloaded with other message types
    - Apple Music (including preview streams and lyrics)
    - Apple Maps (including `Placemark` data)
    - App Store (including app metadata)
    - Rich Collaboration
- App Integrations
  - Parses the `NSKeyedArchiver` payload to extract balloon data
  - Supports system message types as well as third party [applications](https://support.apple.com/en-us/104969)
    - Apple Fitness messages
    - Photo Slideshow messages
    - SharePlay/Facetime messages
    - Check In messages
    - Find My messages
    - Polls and Votes
    - Business Chat messages
      - Quick Replies
      - Interactive Forms
      - List Pickers
- Handwritten Messages
  - Parses the protobuf payload to extract [handwritten](https://support.apple.com/en-my/guide/iphone/iph3d4cb79c9/ios) message data
    - Displayed as embedded `svg` in HTML exports
    - TXT export behavior depends on attachment settings:
      - `disabled`: embedded inline as an `ascii` graphic
      - `clone, basic, full`: saved as an `svg` file
- Digital Touch
  - Parses the protobuf payload to extract [Digital Touch](https://support.apple.com/guide/ipod-touch/send-a-digital-touch-effect-iph3fadba219/ios) message data
    - Supports all Digital Touch effects:
      - Taps
      - Sketches
      - Kisses
      - Heartbeats, including heartbreaks
      - Fireballs
      - Photos and videos
    - HTML exports render a static frame depicting the captured data as an embedded `svg` on a black `4:5` canvas
      - A photo is shown as the canvas backdrop, with any sketch drawn over it
      - A video is shown as an embedded video player
    - TXT exports describe each effect on a single line, including stroke and point counts or beats per minute
- Duplicated group chats
  - Handles (participants) and chats (threads) can become duplicated
  - On startup:
    - Different handles that belong to the same person are combined
    - Chatrooms that contain identical contacts (i.e., duplicated handles) are combined
    - Divergent chatrooms that map to the same conversation in the `chat_lookup` table are combined
- Announcements
  - Handles all types of chatroom announcements
    - Group photo changes
    - Chat participant modifications
    - Phone number changes
    - Chat background settings

---

## File: imessage-database/README.md

# imessage-database

This library provides interfaces to interact with iMessage Databases.

## Installation

This library is available on [crates.io](https://crates.io/crates/imessage-database).

## Documentation

Documentation is available on [docs.rs](https://docs.rs/imessage-database/).

## Example

```rust,no_run
use imessage_database::{
    error::table::TableError,
    tables::{
        messages::Message,
        table::{get_connection, Table},
    },
    util::dirs::default_db_path,
};

fn iter_messages() -> Result<(), TableError> {
    // Create a read-only connection to an iMessage database
    let db = get_connection(&default_db_path()).unwrap();

    // Iterate over a stream of messages
    Message::stream(&db, |message_result| {
        match message_result {
            Ok(mut message) => {
                // Deserialize the message body
                if let Ok(body) = message.parse_body(&db) {
                    message.apply_body(body);
                }

                // Emit debug info for each message
                println!("Message: {:#?}", message)
            },
            Err(e) => eprintln!("Error: {:?}", e),
        };

        // You can substitute your own closure error type
        Ok::<(), TableError>(())
    })?;

    Ok(())
}
```