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.
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.
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.
Frequently Asked Questions
The FAQ document is located here.
Special Thanks
- All of my friends, for putting up with me sending them random messages to test things
- SQLiteFlow, the SQL viewer I used to explore and reverse engineer the iMessage database
- Xplist, an invaluable tool for reverse engineering the
payload_dataplist format - Compart, an amazing resource for looking up esoteric unicode details
- GNU Project and Archive.org, for hosting source code referenced to reverse engineer the
typedstreamformat
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 IDs.
Duplicate Conversations
Because of the aforementioned handle problem, some conversations with the same handle are mapped into separate Chat IDs.
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 Handles 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
{
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:
- Receive a reference to the hashmap generated for the
Handletable - Participants to Unique Chat ID:
Set(Person Centric ID)-> Set(Chat ID) - Chat to Unique Chat:
Chat ID->Unique Chat ID
The steps to generate this unique chat identifier are as follows:
- Generate hashmap
1that contains all of the sets of participants - Generate hashmap
2by iterating through the values of1, inserting each new set of participants - Generate hashmap
3by inverting hashmap2
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:
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: ffmpegHandle 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 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 for more detail.
Can it export messages from third-party apps that integrate with iMessage?
Yes. See here 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 "[email protected],5558675309". Substring matching is supported, so -t "+1555" matches all numbers with that prefix. All conversations containing the matched participants are included.
See here 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 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 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 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.
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 pathclone: Copies all attachment files as-is, preserving original formats and qualitybasic: Copies all files, convertingHEICimages toJPEGfor broader compatibilityfull: Copies all files, convertingHEICtoJPEG,CAF/AMRaudio toMP4,MOVvideo toMP4, and animated stickerHEICStoGIF
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 and here.
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
- Jailbroken iOS filesystem data
- Uses
sms.db, which follows the same schema as macOSchat.db - Resolved as a macOS database with an alternate attachment root
- Uses
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
typedstreammessage body data usingcrabstep - 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
typedstreammessage body data - Supports all iMessage text format ranges:
- Mentions
- Hyperlinks
- OTP/2FA
- Animations and Styles
- Data detectors, parsed from
DDScannerResultpayloads:- 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)
- Parses formatted text ranges from
- Edited and Unsent messages
- Detects if messages components were edited or unsent
- Edited messages
- Parses
typedstreamedited 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
- Parses
- Unsent messages
- No content, but are noted in context
- Edited messages
- Recently deleted messages are recovered when available
- Detects if messages components were edited or unsent
- Multi-part messages
- iMessages can have multiple parts, denoted by ranges in
typedstreammessage body data - Parts are displayed as
- New lines in TXT exports
- Separate balloons in HTML exports
- Handles Edited and Unsent parts
- iMessages can have multiple parts, denoted by ranges in
- Threads and Message Replies
- Threads 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
- Threads are displayed both threaded under the parent as well as in-place
- 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
HEICfiles convert toJPEG - Sticker
HEICfiles convert toPNG - Animated Sticker
HEICS(HEIC sequence) files convert toGIF - Video
MOVfiles convert tomp4 - Audio
CAFfiles convert tomp4 - Audio
AMRfiles convert tomp4
- Image
- Attachments are displayed as
- File paths in TXT exports
- Embeds in HTML exports (including ``,
<video>, and<audio>)- Audio messages include embedded transcripts
- Attachment date metadata is set to the date and time of message receipt
- Expressives
- Detects both bubble and screen effects
- Messages sent with expressives are annotated
- Tapbacks
- Detects tapbacks 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 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
NSKeyedArchiverpayload 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
Placemarkdata) - App Store (including app metadata)
- Rich Collaboration
- Parses the
- App Integrations
- Parses the
NSKeyedArchiverpayload to extract balloon data - Supports system message types as well as third party applications
- 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
- Parses the
- Handwritten Messages
- Parses the protobuf payload to extract handwritten message data
- Displayed as embedded
svgin HTML exports - TXT export behavior depends on attachment settings:
disabled: embedded inline as anasciigraphicclone, basic, full: saved as ansvgfile
- Displayed as embedded
- Parses the protobuf payload to extract handwritten message data
- Digital Touch
- Parses the protobuf payload to extract Digital Touch 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
svgon a black4:5canvas- 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
- Supports all Digital Touch effects:
- Parses the protobuf payload to extract Digital Touch message data
- 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_lookuptable are combined
- Announcements
- Handles all types of chatroom announcements
- Group photo changes
- Chat participant modifications
- Phone number changes
- Chat background settings
- Handles all types of chatroom announcements
File: imessage-database/README.md
imessage-database
This library provides interfaces to interact with iMessage Databases.
Installation
This library is available on crates.io.
Documentation
Documentation is available on docs.rs.
Example
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(())
}