Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.photon.codes/docs/llms.txt

Use this file to discover all available pages before exploring further.

im.groups covers iMessage group-only operations: display names, participants, group icons, leaving, and group events. Group methods use the group chat’s chat.guid. If you only have email addresses or phone numbers, create the group first with im.chats.create(...). Direct chats cannot use group APIs. Shared chat features, such as chat creation, read state, typing, and chat backgrounds, are documented under chats.

What You Can Do

NeedUse this when
Rename a groupYou want to change the group display name
Add participantsYou want to invite new email addresses or phone numbers
Remove participantsYou want to remove existing group members
Set an iconYou want to use image bytes as the group avatar
Download an iconYou need the current custom group avatar
Remove an iconYou want to clear the custom group avatar
Leave a groupThe current account should leave the group
Subscribe to eventsYou need live group name, participant, or icon changes

Group Chat GUIDs

Except for subscribeEvents(...), which takes an optional { chat } filter, group methods take the group chat.guid as their first argument. Create or fetch a group chat first:
const { chat: group } = await im.chats.create(["alice@example.com", "bob@example.com"]);
Then pass group.guid to group operations:
await im.groups.setDisplayName(group.guid, "Weekend");
Group chat GUIDs usually look like any;+;group-id. Do not pass email addresses, phone numbers, or direct-chat GUIDs to group methods.

Rename

const updated = await im.groups.setDisplayName(group.guid, "Weekend");
Returns the updated Chat. The display name is trimmed and must not be empty after trimming.

Add Participants

const updated = await im.groups.addParticipants(group.guid, ["carol@example.com", "+15551234567"]);
Returns the updated Chat.
InputRule
chatGroup chat.guid
addressesNon-empty array; each item must be a full email address or E.164 phone number
The server rejects duplicate addresses, addresses that are already in the group, and addresses it cannot resolve.

Remove Participants

const updated = await im.groups.removeParticipants(group.guid, ["carol@example.com"]);
Returns the updated Chat.
InputRule
chatGroup chat.guid
addressesNon-empty array; each item must already be a participant
Removing participants usually requires the current account to have permission to manage the group. The server rejects unknown addresses, duplicate addresses, and removals the current account is not allowed to perform.

Group Icons

Group icons use raw image bytes. They are not attachment GUIDs, and you do not upload them through im.attachments first.

Set an Icon

await im.groups.setIcon(group.guid, await readFile("icon.png"));
Returns void. data must not be empty. The server detects the image type from the bytes. PNG, JPEG, GIF, HEIC, and HEIF are supported.

Download an Icon

const icon = await im.groups.getIcon(group.guid);

console.log(icon.mimeType, icon.data.byteLength);
Returns GroupIcon:
{
  "data":     [137, 80, 78, 71],  // Uint8Array; raw image bytes
  "mimeType": "image/png"         // Server-detected MIME type
}
When no custom icon exists, getIcon(...) throws NotFoundError, with error.code === ErrorCode.groupIconNotFound.

Remove an Icon

await im.groups.removeIcon(group.guid);
Returns void. Removing an icon is safe to repeat, even when no custom icon is set.

Leave a Group

await im.groups.leave(group.guid);
Returns void.
leave(...) makes the current account leave the group. It cannot rejoin by itself; another participant must invite it again.
Group write methods accept optional { clientMessageId } for idempotent retries from your job system. Most direct calls can omit it. See error handling for details.

Group Events

subscribeEvents(...) returns TypedEventStream<GroupEvent>. Use the stream to observe changes made by other people, other devices, or another part of your system. Immediately after your code calls a write method, use that method’s Chat result or completion status.

Scope

Only one group:
const stream = im.groups.subscribeEvents({
  chat: group.guid,
});
All visible group events:
const stream = im.groups.subscribeEvents();

Outer Event

Every group event has type: "group.changed". The outer fields answer which group changed, who triggered it, and when:
{
  "type":       "group.changed",          // Fixed value for group change events
  "chatGuid":   "any;+;group-id",         // Group chat that changed
  "sequence":   123,                      // Event sequence for ordering and catch-up
  "isFromMe":   false,                    // Triggered by the current account
  "occurredAt": "2026-01-01T12:00:00Z",   // Event time
  "actor": {                              // Participant that triggered the event; may be absent
    "address": "alice@example.com",
    "service": "iMessage"
  },
  "change": {                             // The actual group change
    "type": "displayNameChanged",
    "displayName": "Weekend"
  }
}

Change Types

event.change.type tells you what changed. Each type carries different fields:
event.change.typeMeaningExtra fields
displayNameChangedThe group was renameddisplayName
participantAddedA participant joinedparticipant
participantRemovedA participant was removedparticipant
participantLeftA participant left on their ownparticipant
iconChangedThe group icon was set or replacedNone
iconRemovedThe group icon was clearedNone
{
  "type":        "displayNameChanged",  // The group was renamed
  "displayName": "Weekend"              // New group name
}

Handle Events

Switch on event.change.type. When you subscribe to one group, you do not need to check chatGuid again:
for await (const event of im.groups.subscribeEvents({ chat: group.guid })) {
  switch (event.change.type) {
    case "displayNameChanged":
      console.log(event.change.displayName);
      break;

    case "participantAdded":
    case "participantRemoved":
    case "participantLeft":
      console.log(event.change.type, event.change.participant.address);
      break;

    case "iconChanged":
    case "iconRemoved":
      console.log(event.change.type);
      break;
  }
}
When you subscribe to all visible groups, use event.chatGuid to identify the group:
for await (const event of im.groups.subscribeEvents()) {
  console.log(event.chatGuid, event.change.type);
}
If the stream disconnects, use events to catch up on missed durable events, then continue consuming the live stream.

Next Steps

  1. Chats — create a group chat and get chat.guid
  2. Messages — send messages, attachments, and reactions in a group chat
  3. Events — catch up on durable events after a disconnect
  4. Error Handling — handle NotFoundError, ValidationError, and idempotent retries