# Groups (/docs/concepts/groups)



A group is a named list of members, such as your VIPs, your ambassadors, or the creators you've approved. You keep one list, and it can control three things at once:

* **Which quests a member can take.** For example, a VIP-only quest.
* **Who ranks on a leaderboard.** For example, a race among ambassadors only.
* **Who earns referral rewards.** For example, only approved creators earn for inviting friends.

Your team adds and removes members in Console, or a quest adds the members who complete it, such as an application your team approves.

```mermaid
flowchart LR
    O["Operators: Console, CLI, management API"] --> G["Group"]
    Q["Quest reward: groupMembership"] --> G
    G --> P["inGroup prerequisite"]
    G --> B["Leaderboard group"]
    G --> R["Referral inviterGroup"]
```

## Keys and handles [#keys-and-handles]

Each group has a key, such as `vip`, and a title your team sees. The key never changes; the title can. In Console, **New group** fills in the key from the title until you type your own.

> [!DEV]
> Code names a group with a typed handle, as below. A leaderboard's `group` and a referral program's `inviterGroup` take the same key. Publishing code never adds members; it creates a group only when the environment has none with that key (see [Groups your code names](#groups-your-code-names)).
>
> Keys starting with `domino--exclusions--` are reserved for the groups Relay creates as a leaderboard's [exclusion group](/docs/concepts/leaderboards#exclusions). Creating one fails with 409 and code `already-exists`.

```ts
import { defineGroup, inGroup } from "@domino-sdk/relay/authoring";

export const vips = defineGroup({ key: "vip", title: "VIP" });

// In a quest definition:
const requires = [inGroup(vips)];
```

## Groups your code names [#groups-your-code-names]

When your code names a group the environment doesn't have yet, publishing creates it for you, with no members:

* **Deploying to test** creates it in test.
* **Approving a promotion** creates it in live. The promotion lists the groups approval will create, and members are never copied from test.
* **The title comes from your code**, from `defineGroup({ key, title })`. A group only a leaderboard or referral program names takes its key as its title. Rename it in Console **Groups** like any other.
* **An existing group stays as it is.** Publishing never renames a group or changes its members.
* **An archived group is never unarchived.** A quest reward that adds members to an archived group fails to publish: unarchive the group in Console **Groups** first.

**Audit history** records each created group, naming the deployment or promotion that created it.

> [!DEV]
> A quest's `groupMembership(group)` reward or `inGroup(group)` prerequisite, an offer's `inGroup(group)` requirement, a leaderboard's `group`, and a referral program's `inviterGroup` all count; a promotion also creates a leaderboard's exclusion group live lacks. The deployment preview and result list each group with the warning code `group-created`; a prepared promotion lists it as a `group-created` warning with `kind: "group"` and the group's `key`. A key starting with `domino--exclusions--` is never created.

## Create a group and add members [#create-a-group-and-add-members]

Create the group and manage its members in Console **Groups**. Select a group to open it. Your developer can do the same from the CLI or their server.

| Action             | Console                                                               | CLI                                                                                       | Management API                                                                 |
| ------------------ | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| Create             | **New group**                                                         | `domino groups create vip --title VIP`                                                    | `management.groups.create({ key, title })`                                     |
| Rename             | Change **Title**, then **Save title**                                 | `domino groups update vip --title …`                                                      | `management.groups.update(group, { title })`                                   |
| Add a member       | Find them in **Add a member**, give a reason, then **Add to group**   | `domino groups add vip <member> --reason …`                                               | `management.groups.addMember(group, { member, reason })`                       |
| Remove a member    | **Remove** beside them, give a reason, then **Remove from group**     | `domino groups remove vip <member> --reason …`                                            | `management.groups.removeMember(group, member, { reason })`                    |
| Archive, unarchive | **Archive group** or **Unarchive group**, give a reason, then confirm | `domino groups archive vip --reason …`, `domino groups unarchive vip --reason …`          | `management.groups.archive(group, { reason })`, `unarchive(group, { reason })` |
| Read               | Select the group                                                      | `domino groups get vip`, `domino groups members vip`, `domino groups member vip <member>` | `management.groups.get(group)`, `members(group)`, `member(group, member)`      |

On a leaderboard with a group, a member's **Eligibility** section in Console **Leaderboards** also has **Add to group** and **Remove from group**.

* **Groups are day-to-day operations.** You can change groups directly in live. A promotion never copies groups or members; it only creates, empty, the groups your code names and live lacks.
* **Only members of the project can join.** You can't add someone who hasn't joined the project.
* **Every change has a reason.** Adding or removing a member records the reason and who made the change in **Audit history**. Adding someone who already belongs, or removing someone who does not, records nothing. Console also asks for a reason to archive or unarchive a group; from the CLI or your server that reason is optional.
* **Keys are unique.** Creating a group with a key that already exists fails.

> [!DEV]
> Reads need `read`; changes need `publish`. A personal API key with `publish` can change groups in live; a CLI sign-in cannot. Adding someone who has not joined the project fails with 404 and code `not-joined`; creating a key that exists fails with 409 and code `already-exists`. Every refusal carries a stable code; see [API error codes](/docs/reference/errors). `management.groups.list()` and `members(group)` return `{ items, nextCursor }`: pass `nextCursor` back as `cursor` until it is `null`.

## What reads a group [#what-reads-a-group]

A group always reflects who is in it now: take someone out, and they lose what the group gave them from then on.

| Feature                                                                                                         | Rule                                                                                                                                                                                    |
| --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Quest prerequisite](/docs/concepts/quests#who-can-take-part) `inGroup(group)`                                  | The member must belong now. Removing them makes the prerequisite missing again.                                                                                                         |
| [Leaderboard](/docs/concepts/leaderboards) `group`                                                              | Only current members are ranked. A [prize settlement](/docs/concepts/leaderboards#prize-settlements) ranks the members the group had at its cutoff.                                     |
| [Leaderboard exclusion group](/docs/concepts/leaderboards#exclusions)                                           | Current members are left out of the boards bound to it. A prize settlement leaves out the members it had at the cutoff.                                                                 |
| [Referral program](/docs/concepts/referrals) `inviterGroup`                                                     | The program's rewards for a qualifying friend, its milestones, and its inviter bonuses apply only while the inviter belongs. Rewards missed while outside the group are not paid later. |
| [Quest evaluator](/docs/reference/verification#evaluator-readers-null-errors-and-pinning) `ctx.member.groups()` | Returns the keys of the unarchived groups the member belongs to, fixed at the evaluation run's first call.                                                                              |

Adding a member also checks [quests that unlock on their own](/docs/concepts/quests#outcomes-for-prerequisite-only-automatic-quests) the member can now enter.

## Add members with a quest reward [#add-members-with-a-quest-reward]

A quest can add the member who completes it to a group. For a curated program, such as approved creators, make an application quest your team reviews: approving an application adds the applicant, and quests for that group open to them.

> [!DEV]
> Put `groupMembership(group)` in the quest's rewards, next to `points(...)` and `badge(...)`, and turn on human review. Quests gated on `inGroup(group)` then open to the member.

```ts
import { defineGroup, groupMembership } from "@domino-sdk/relay/authoring";

export const creators = defineGroup({ key: "approved-creators", title: "Approved creators" });

// In the application quest's definition:
const rewards = [groupMembership(creators)];
```

* **Publishing creates the group if it's missing.** Deploying the quest creates it in test, and approving its promotion creates it in live, both empty. An archived group fails publication: unarchive it first.
* **It works like adding them yourself.** **Audit history** records the change with the quest and completion as its reason, and quests that unlock on their own are checked. Someone already in the group stays as they were.
* **Revoking the completion removes what it added.** The member leaves the group unless your team added them too, or another completion's reward did. An archived group keeps them.
* **Your team keeps control.** Removing a member works whatever added them.

> [!DEV]
> An archived group fails a deployment with 422 and code `validation-failed`, and promotion approval with 409 and code `conflict`. The revocation result's `groups` says whether the member is still in each group.

See [group membership rewards](/docs/concepts/rewards#group-membership) for how the reward appears to members.

## Archive instead of delete [#archive-instead-of-delete]

Groups are never deleted. Archive a group you no longer manage:

* **Members keep counting.** Prerequisites, leaderboards, and referral rules still read an archived group's members.
* **Membership is frozen.** You can't add or remove members until you unarchive the group.
* **History stays.** Leaderboard settlements read past membership, so it is kept.
* **Exclusion groups stay open.** A group a leaderboard uses as its exclusion group can't be archived. Bind those boards to another exclusion group first.
* **Rewarded groups stay open.** A group a quest adds members to can't be archived while that quest isn't archived. Remove the reward or archive the quest first.

> [!DEV]
> A change to an archived group's members fails with 409 and code `group-archived`. Archiving a group in use fails with 409 and code `group-in-use`: the response's `leaderboards` names the boards that use it as their exclusion group, and its `quests` lists the IDs of unarchived quests that reward it with `groupMembership`.

To stop a group from granting eligibility, remove its members, or remove the group from the quest, leaderboard, or referral program that names it.

> [!NOTE] Earlier leaderboard groups
> `management.leaderboards.group(group, { member, included, reason })` still works but is deprecated. It creates the group, titled with its key, on its first member. Use `management.groups` instead; create the group first.

## Where next [#where-next]

- [Quests](/docs/concepts/quests) — Prerequisites, including group membership.
- [Publish a leaderboard](/docs/build/leaderboards) — Restrict a board to a group.
- [Build referrals](/docs/build/referrals) — Restrict inviter rewards with `inviterGroup`.
