| title | JSON output |
|---|---|
| description | The stable JSON schema imsg emits for chats, messages, attachments, and reaction events. |
Every read command supports --json. Output is newline-delimited JSON (NDJSON): one self-contained JSON object per line. This shape works equally well for streaming consumers and for batch readers that pipe through jq -s to materialize an array.
imsg chats --json | jq -s
imsg history --chat-id 42 --json | jq -s
imsg watch --chat-id 42 --jsonHuman progress, prompts, and warnings are written to stderr, not stdout. Stdout is reserved for parseable JSON so pipelines stay clean.
Returned by imsg chats --json and JSON-RPC chats.list.
| Field | Type | Notes |
|---|---|---|
id |
int | chat.ROWID. Stable within one DB. Preferred routing handle. |
name |
string | Display name, contact match, or raw handle fallback. |
display_name |
string | Group title from chat.display_name. Empty for direct chats without a custom name. |
contact_name |
string | Resolved Contacts name (when permission granted). |
identifier |
string | chat.chat_identifier. Portable. |
guid |
string | chat.guid. Portable. |
service |
string | iMessage, SMS, etc. |
last_message_at |
ISO8601 | Newest activity time. |
is_group |
bool | True when identifier or guid contains ;+;. |
participants |
array of strings | External handles only; local user implicit. |
account_id |
string | Routing diagnostic. Read-only. |
account_login |
string | Routing diagnostic. Read-only. |
last_addressed_handle |
string | Routing diagnostic. Read-only. |
unread_count |
int | Count of unread inbound messages in the chat. Omitted on older database schemas without read state. |
Returned by imsg history, imsg search, imsg watch, and the JSON-RPC messages.history and watch.subscribe notifications.
| Field | Type | Notes |
|---|---|---|
id |
int | Database-instance-scoped rowid. Use as the --since-rowid cursor in watch, but discard saved cursors after replacing or restoring the Messages database. |
chat_id |
int | Always present. Preferred routing handle. |
chat_identifier |
string | Portable handle. |
chat_guid |
string | Portable GUID. |
chat_name |
string | Display name for the chat. |
participants |
array | External handles. |
is_group |
bool | True for group threads. |
guid |
string | Message GUID. Stable across machines. |
reply_to_guid |
string | When set, this message is an inline reply to that GUID. |
destination_caller_id |
string | Outgoing only — which of your numbers Messages routed through. |
balloon_bundle_id |
string | Raw Messages message.balloon_bundle_id, when present. URL preview rows use com.apple.messages.URLBalloonProvider, which lets consumers recognize link-preview payload rows without inferring from message text. |
url_preview |
object | Present when imsg folds an Apple URL-preview balloon row into its originating text row. The outer message keeps the text row's id, guid, text, and created_at. |
sender |
string | Raw handle. Empty for some self-sent messages. |
sender_name |
string | Resolved Contacts name when permission granted. |
is_from_me |
bool | True for outbound. |
text |
string | Plain text. Recovered from attributedBody when text column is empty. |
created_at |
ISO8601 | Message timestamp. |
attachments |
array | Present when --attachments is set. See below. |
thread_originator_guid |
string | For inline-reply threads. |
poll |
object | Present for native Apple Messages Polls creation and vote rows. See below. |
is_read |
bool | Inbound only — omitted when is_from_me is true. |
date_read |
ISO8601 | Inbound only — present when is_read is true. |
Read state is a database snapshot. A watch notification includes the state present when that message row is emitted; reading it later does not generate a second message notification.
Returned by imsg scheduled list --json and in the JSON-RPC messages.scheduled result. Only future outbound rows with native scheduling state are included.
| Field | Type | Notes |
|---|---|---|
id |
int | Scheduled message rowid. |
guid |
string | Native message GUID. |
chat_id |
int | Local chat rowid. |
chat_identifier |
string | Portable chat identifier. |
chat_guid |
string | Portable chat GUID. |
chat_name |
string | Display name or identifier fallback. |
text |
string | Scheduled message body. |
service |
string | Message service, normally iMessage. |
scheduled_at |
ISO8601 | Requested delivery time. |
schedule_type |
int | Raw Messages scheduling type. |
schedule_state |
int | Raw Messages scheduling state. |
Returned by imsg chat-background status --chat-id <id> --json. This surface is read-only and does not require the IMCore bridge.
| Field | Type | Notes |
|---|---|---|
chat_id |
int | Local chat rowid. |
chat_guid |
string | Portable chat GUID. |
background_set |
bool | Whether the chat properties contain a background channel. |
background_channel_guid |
string | Background channel identifier; omitted when no background is set. |
asset_url |
string | Raw background asset URL when present. |
asset_id |
string | Raw background asset identifier when present. |
object_id |
string | Raw background object identifier when present. |
file_size |
int | Raw asset size when present. |
poster_version |
int | Raw PosterKit version when present. |
communication_safety_state |
int | Raw communication-safety state when present. |
version |
int | Raw background metadata version when present. |
cache_path |
string | Cache path next to the selected Messages database. |
cache_exists |
bool | Whether cache_path exists. |
watch_background_path |
string | Watch-background cache path when a background channel exists. |
watch_background_exists |
bool | Whether watch_background_path exists. |
latest_event |
object | Newest background set/clear event by message date, with row_id, guid, action, and date. |
Messages may store a link send as two rows: the user's text row and a later com.apple.messages.URLBalloonProvider preview row. history, search, watch, messages.history, and watch.subscribe coalesce those rows into one logical message when the preview immediately follows a same-chat/same-sender text row containing the preview URL. In batch reads the coalesced message includes:
| Field | Type | Notes |
|---|---|---|
id |
int | Preview rowid that was folded into the outer text message. |
guid |
string | Preview row GUID. |
balloon_bundle_id |
string | com.apple.messages.URLBalloonProvider. |
created_at |
ISO8601 | Preview row timestamp. |
Live watch calls do not delay the text message waiting for a preview. If the preview row arrives in a later poll after the text row was already emitted, imsg suppresses the preview row so consumers still receive one notification.
Present on standalone reaction rows emitted by imsg watch --reactions,
watch.subscribe with include_reactions: true, and messages.after with
include_reactions: true:
| Field | Type | Notes |
|---|---|---|
is_reaction |
bool | true for tapback events. |
reaction_type |
string | love, like, dislike, laugh, emphasis, question, or a custom emoji marker. |
reaction_emoji |
string | Custom emoji, when present. |
is_reaction_add |
bool | true for add, false for remove. |
reacted_to_guid |
string | The message guid this tapback targets. |
history deliberately hides standalone reaction rows so they don't duplicate
the reacted message. Live watch surfaces emit them only when reactions are
enabled; messages.after includes them in ROWID order only when
include_reactions is true.
Native Apple Messages polls are emitted as normal messages with a poll object. Existing message fields stay present and unchanged; poll rows often have an empty text field because the useful data is stored in the Messages extension payload. When a native created-poll payload has no title, imsg may backfill poll.question from the earliest clean caption row that replies to the poll.
| Field | Type | Notes |
|---|---|---|
kind |
string | created, vote, or unknown. |
event |
string | Route-friendly value: imessage.poll.created, imessage.poll.voted, or imessage.poll.unknown. |
poll_guid |
string | The poll's source message GUID when known. |
question |
string | Poll title or question when decoded. For native created polls with an empty payload title, this may be backfilled from the poll's plain caption row. |
options |
array | Poll options, each with id and text. |
vote |
object | First decoded vote entry for compatibility. This is not necessarily the option that changed. |
votes |
array | Authoritative full selected-option snapshot carried by the update payload. |
original_guid |
string | For vote rows, the original poll message GUID from associated_message_guid. |
creator |
string | Creator handle when the payload includes it. Creation rows may fall back to the sender handle. |
participants |
array | Handles seen in decoded poll metadata. |
metadata |
object | Raw-safe diagnostics only: bundle id, payload byte counts, URL scheme/host, query keys, and associated message type. Raw private payload bytes are never emitted. |
Example:
{
"poll": {
"kind": "created",
"event": "imessage.poll.created",
"poll_guid": "A1B2",
"question": "Dinner?",
"options": [
{ "id": "opt-1", "text": "Pizza" },
{ "id": "opt-2", "text": "Sushi" }
]
}
}Inside the attachments array on a message:
| Field | Type | Notes |
|---|---|---|
filename |
string | Stored filename. |
transfer_name |
string | Original filename as sent. |
uti |
string | Apple UTI. |
mime_type |
string | Best-effort MIME. |
byte_size |
int | Size in bytes. |
is_sticker |
bool | Sticker-pack attachments. |
missing |
bool | Underlying file not on disk. |
path |
string | Resolved absolute path. |
converted_path |
string | Present with --convert-attachments. |
converted_mime_type |
string | Present with --convert-attachments. |
Returned as one object by imsg stats --json and as the result of JSON-RPC messages.stats.
| Field | Type | Notes |
|---|---|---|
total_messages |
int | Logical messages after reaction exclusion and URL-preview coalescing. |
sent_messages |
int | Outbound logical messages. |
received_messages |
int | Inbound logical messages. |
time_zone |
string | Resolved timezone used for dates. |
chats |
array | Counts grouped by chat. |
senders |
array | Inbound counts grouped by sender handle. |
services |
array | Counts grouped by message service. |
dates |
array | Counts grouped by local YYYY-MM-DD date. |
media |
object | Present only when --media / include_media is requested. Distinct attachment totals plus type/chat groups. |
See Statistics for filtering, ordering, and timezone behavior.
- Every numeric field is a JSON number.
id,chat_id, andbyte_sizeare integers; nothing requires 64-bit JSON-string encoding. - Times are ISO 8601 with explicit timezone (typically
Z). - Strings that aren't applicable are omitted, not set to
null. Test withfield in obj, notobj.field === null. - Booleans are explicit
true/false, never 0/1. - Arrays are always present when documented (possibly empty).
The JSON schema is treated as a public API. Field renames or removals are tracked in CHANGELOG.md with a "change" or "deprecation" note and gated to a minor release.
The 0.2.0 → 0.3.0 cycle did one large rename (camelCase → snake_case). Since 0.3.0 the schema has been additive only.