Group & Chapter Admin Handbook#

This handbook is the operational reference for the people who run a group day-to-day: the group’s admin and moderators. It covers the create flow, membership management, post moderation, chapter event creation, and broadcast announcements.

Who this is for: anyone with MembershipRole = admin or moderator on a group (interest or chapter). The platform-admin lens is in documentation/role-guides/admin/phase2-admin-overview.md.

Source of truth: backend/internal/groups/service.go, backend/internal/groups/store.go, backend/internal/groups/types.go. RPC contract: protos/bits/groups/v1/groups.proto.


1. Roles inside a group#

Three roles, persisted as strings in groups_membership.role (backend/internal/groups/types.go:47):

  • admin — full control. Can transfer admin to another member, change visibility, archive the group, promote/demote moderators, ban members.
  • moderator — can approve/decline join requests, delete posts, ban members. Cannot change visibility or transfer admin.
  • member — post, comment, react.

The platform admin role overrides everything: a platform admin can take any moderator action on any group regardless of group membership.

Last-admin protection#

The store rejects any role change or removal that would leave a group with zero admins (ErrLastAdminProtect → 422). Before stepping down, promote a successor.

2. Creating a group#

Any verified user can create an interest group. Chapter creation requires the groups.create capability — typically reserved for platform admins.

/app/groups/new walks through:

  1. Name (≤ 200 chars).
  2. Slug — must match ^[a-z0-9][a-z0-9_-]*[a-z0-9]$|^[a-z0-9]$, ≤ 120 chars, globally unique. Slug collisions return 409.
  3. Kindinterest (default) or chapter (admin-only).
  4. Visibilitypublic | institute | alumni | private.
  5. Description (≤ 4 000 chars), avatar URL, banner URL.

The first creator is set to admin automatically.

3. Managing membership#

Public / institute / alumni groups#

Members join directly. The flow does not require approval — the API call mints a GroupMembership row in state active.

Private groups (and any group that wants gated entry)#

Members must RequestMembership. The request lands in GroupRequest with state pending. Admins/moderators see the queue on /app/groups/<slug>Members tab → Requests sub-tab.

For each request you can:

  • Approve — flips request state to approved, mints a GroupMembership row, publishes groups.member.joined event. Audit row written.
  • Decline — flips to declined. Audit row written. The user can request again in the future (declines are not bans).

Cross-group request IDs are rejected as NotFound to prevent enumeration.

Banning a member#

Use Remove member with the Ban option on /app/groups/<slug>Members tab. This sets GroupMembership.state = banned. Banned members cannot rejoin; their existing posts remain (consider deleting separately if needed). Banning is reversible — re-adding a banned user via the same API explicitly resets state to active.

Promoting / demoting#

UpdateMemberRole flips a member’s role between admin, moderator, and member. Last-admin protection applies — promoting yourself out of admin while you are the only admin is rejected.

4. Moderating posts#

Group posts are subject to the same moderation pipeline as the main feed (backend/internal/groups/moderation.go → ContentModerator port → Stage 12 classifier).

What you (as group admin/moderator) can do:

  • Delete a post — soft-delete via DeleteGroupPost. The post is hidden from listings but preserved in audit. Audit row written.
  • Pin an announcement-kind post — composing with kind = announcement (admin/moderator only) auto-pins to the top of the Posts tab.
  • Report a post — the existing ReportContent RPC accepts target_type=group_post. Reports flow to /admin/moderation for the platform team.

Banned members cannot post — the API returns 403 PermissionDenied (ErrBanned).

5. Chapter events#

Note: applicable to chapter-kind groups only (Group.kind = chapter). Interest groups do not have an Events tab.

Chapter events are stored on the calendar (calendar_event.kind = 'chapter') and bound to the chapter via calendar_event.scope_ref = <chapter group UUID> (backend/internal/calendar/store.go:39, backend/internal/calendar/service.go:180).

To create a chapter event:

  1. As chapter admin/moderator, navigate to the calendar admin (/admin/calendar) — the chapter scope option appears for chapters you administer.
  2. Author the event: title, body, start/end, location, optional cover image.
  3. Save. The event appears on:
    • The chapter group’s Events tab (/app/groups/<slug> → Events).
    • Each chapter member’s /app/calendar once they have toggled chapter event subscription on.
    • The Stage 19 search index under the events surface.

Visibility is otherwise unconditional inside the chapter — every chapter member sees every published chapter event.

6. Announcement broadcasts#

Two flavours:

a) Group-internal announcement post#

Compose a post with kind = announcement on the group’s Posts tab. This is a regular GroupPost row that gets pinned to the top of the Posts tab. Notifications fan out via the groups.post.created topic with an extra kind=announcement flag so member preferences can treat it as higher-priority.

b) Platform-wide announcement (admin only)#

For announcements that should appear on every member’s Campus tab, you need the platform admin role and use /admin/cms/announcements. Set category = chapter and audience = chapter:<your-chapter-uuid> to scope it to your chapter. See documentation/campus-tab-and-chapter-events.md.

If you don’t have admin, ask your Bits admin to publish on your behalf.

7. Notifications you’ll generate#

Every action you take in the admin role fans out a notification to the relevant party. Topics:

  • groups.member.joined — to existing members when someone joins (or is approved).
  • groups.post.created — to subscribed members when a new post is published. Default-on for chapter members; default-off for interest groups (members opt in per group).
  • groups.request.received — to admins/moderators when someone requests to join.

All admin-side topics honour the recipient’s /app/profile/notifications preferences.

8. Audit trail#

Every mutating action you take writes an audit row via s.writeAudit(...) (e.g. inside ApproveRequest in backend/internal/groups/service.go). The action string is bits.groups.v1.GroupsService/<MethodName>. Platform admins can replay any action via /admin/audit-logs.

9. Common operational issues#

  • “A request has been pending for weeks” — there is no automatic SLA; pending requests do not expire. Either approve, decline, or hand the moderation duty to another member by promoting them.
  • “I can’t archive a group” — Phase 2 does not expose an archive button to group admins; only platform admins can archive (via archived_at). Request via Bits support.
  • “A member’s posts are spammy but they are not banned” — delete the posts individually; bans persist forever in V2 (no time-limited bans). Use bans only for abuse, not for low-quality content.
  • “I want to make my interest group bigger by inviting alumni” — invites are not implemented in V2. Members must self-discover and join. Use cross-posting on the main feed plus a chapter announcement to drive traffic.