Read

Chats

imsg chats lists conversations sorted by most recent activity. imsg group zooms in on one chat. Both work for direct chats and group threads.

#List recent chats

imsg chats --limit 20
imsg chats --limit 20 --json | jq -s

Columns (text mode): id, name, service, last_message_at.

name is the resolved display name when available — group title, contact match, or raw handle as a fallback.

#Inspect one chat

imsg group --chat-id 42
imsg group --chat-id 42 --json

Use this before scripting a send. It returns identifier, GUID, service, participants, group/direct flag, and account routing hints in one shot.

imsg group works for direct chats too, despite the name. Treat it as "chat detail," not "groups only."

#Chat object

Every chat object — from chats, group, or any nested chat metadata in history/watch — includes:

FieldTypeNotes
idintchat.ROWID. Stable within one Messages database. Preferred routing handle.
namestringDisplay name, contact match, or raw handle fallback.
display_namestringchat.display_name (group title) when set.
contact_namestringResolved Contacts name when permission granted.
identifierstringchat.chat_identifier — Messages' portable handle.
guidstringchat.guid — Messages' portable GUID.
servicestringiMessage, SMS, etc.
last_message_atISO8601Newest activity in the chat.
is_groupboolTrue when identifier or guid contains ;+;. See Groups.
participantsarrayExternal handles only. The local user is implicit; see below.
account_idstringRouting diagnostic. Read-only.
account_loginstringRouting diagnostic. Read-only.
last_addressed_handlestringRouting diagnostic. Read-only.

#Routing identifiers — which one to use

Three handles can identify a chat. Pick by use case:

  • chat_id (rowid): preferred. Fastest, most stable within one database. Use this whenever both reader and sender are on the same machine.
  • chat_identifier: portable across DBs/installs. Use when you store handles externally and need to tolerate a Messages reset.
  • chat_guid: also portable. Same use cases as chat_identifier.

For sends, imsg send --chat-id is preferred. --chat-identifier and --chat-guid are fallbacks for callers that only have the portable handle.

#Participants vs. local identity

participants lists external handles only. The local user is intentionally absent because Messages stores it implicitly per-message rather than on the chat row.

To distinguish your own messages from others':

  • Use is_from_me on each message.
  • For multi-number Apple IDs, check destination_caller_id on outgoing messages — it tells you which of your numbers Messages routed through.

account_id, account_login, and last_addressed_handle are diagnostic reads from Messages. AppleScript's send does not let imsg force a specific outbound number when several phone numbers share one Apple ID. The fields are there so you can audit what Messages picked, not steer it.

#Filtering tips

imsg chats does not take filter flags — it's designed to be cheap. Pipe through jq or grep for ad-hoc filtering:

imsg chats --json | jq -s 'map(select(.is_group == true))'
imsg chats --json | jq -s 'map(select(.service == "SMS"))'

For more targeted history queries with date and participant filters, use imsg history.