| 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 | rowid. Use as the --since-rowid cursor in watch. |
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 imsg watch --reactions events:
| 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 reaction rows so they don't duplicate the reacted message. Reaction events only surface in the live watch stream.
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 update, with option_id, participant, and event_type when present. |
votes |
array | All decoded vote entries when the payload carries more than one. |
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.