Developers

Insight

How a chat record is stored

A chat is stored as separate rows for the message, the conversation, and the address. Messages is the cited example. Other apps are not described here.

Published 10/19/2026  ·  3 min read

DEVI Ops Team

A conversation on screen looks like one list. The store behind Messages, in the public notes cited below, is not one list. It is a message row, a conversation row, and an address row, tied together by join tables. That split is the mechanism. What it means for a case is the other view of this topic.

What the feature is for

Messages has to show a thread, send on more than one transport, and attach a file without copying the file into every row. The 09/25/2026 MessagingForensics note describes one SQLite database that holds iMessage, SMS, and RCS. One conversation can mix those transports. The transport is a column on the message, service, not a separate database per transport.

A usage log is a different feature. The 08/06/2018 mac4n6.com note on knowledgeC.db describes rows written when an app is in front, and extra metadata for some activities. That log can mention a chat. It is not the message store. The KnowledgeC pages cover it.

How the rows are divided

The MessagingForensics note and the 07/17/2024 Atomic Object note agree on the division.

  • message is one row per message, reaction, or event.
  • chat is one conversation, direct or group.
  • handle is one remote address, a phone number or an email, plus the service.
  • chat_message_join ties a message to a conversation.
  • attachment is the file metadata. message_attachment_join ties it to the message.

The message row does not carry the conversation id. The join does. A group’s members are not a single column on the chat. They are handle rows reached through the joins. That is why a query that reads only message cannot name the thread.

When a row is written

The notes describe date as the message time in Cocoa units, counting from 01/01/2001 00:00:00 UTC. They do not describe a separate column for the moment SQLite inserted the row. The September 2026 note says a write can land in the WAL file first and reach the main database later. A reader that opens the main file and skips the WAL sees an older set of rows.

What changed between versions

The same note says the unit of date changed around macOS High Sierra and iOS 11, from seconds to nanoseconds. It says that on macOS Ventura and the matching iOS versions, text is often null and the body is in attributedBody. It says edits and unsends, from iOS 16 and macOS Ventura, set date_edited and date_retracted and leave the older wording in structures the note calls undocumented.

Not independently verified

Those version boundaries are the public notes, re-read on 10/19/2026. They were not checked against a current phone for this page, and they are not an Apple specification.

The field-by-field reading, the backup file name, and the court limit are on the forensics view.

Indexed under

  • Developers
  • Insights
  • Records

A first-person educational note. Developer insights record something that stuck while learning to build software. They are not Digital Forensics findings, not product claims, and not career advice.

Check your own project by what actually happens when you skip a step. Read how these insights are written.

← All insights