Assigning members to units β
A member is in a unit when a row exists in member_unit_assignments linking the two. That row is what makes attendance roll up, what scopes shepherd visibility, what lets the map know where to put the pin. Without it a member is a name in a list with no organizational context.
This page covers every path GCM offers for creating those rows.
The one-per-level rule β
A member can be assigned to one unit at each level. If your levels are Branch β Center β Cell, a member can be in one branch and one center and one cell β three rows in member_unit_assignments, one per level. They cannot be in two branches at once.
The database enforces this with a unique index on (member_id, level_index) scoped to organization_id. Trying to add a second row at the same level either replaces the existing one (in the unit-detail flow) or surfaces a duplicate error (raw insert).
If a member legitimately participates in two branches β say a worship leader who plays at both campuses β pick one as their home branch and use a custom field to note the secondary.

From a unit's detail page β
This is the most common workflow. Open the unit (drill in from the tree or a level page), scroll to the Members panel, and click Add member.
The dialog shows a searchable dropdown. Type two characters and GCM hits the search_members RPC for live results across the whole org. Pick a member, click Add, and you'll see them appear in the panel immediately.
What happens under the hood:
- GCM checks the panel's current member list to bail early if the same member-id is already assigned to this unit (toast: "Member already assigned").
- It inserts a row into
member_unit_assignmentswith the member, the unit, and the current org id (RLS enforces this anyway). - The unit's member count badge ticks up across the rest of the app.
The Members panel itself has:
- A search box that filters by first or last name (case-insensitive, both fields ORed).
- Pagination at 25 / 50 / 100 rows. Big units load lazily.
- Member rows with avatar, type badge (visitor / member / regular), phone, and a completeness chip.
- Remove button on hover for unassigning without leaving the page.
From a member's profile β
Sometimes you're working from the other side β you've just added a member and want to file them. Open their profile, scroll to Org unit assignment, and pick units level by level.
The picker is cascading: choose a level 1 branch first, then the level 2 options narrow to that branch's centers, then level 3 to that center's cells. You're never asked to pick a cell whose branch you didn't select.
If you have only one branch the picker locks to it automatically β there's nothing to choose. Same for any level where the member-or-staff scope leaves exactly one option.
In bulk β
For migrations, big intakes, or a parish-wide restructure, single-row inserts get tedious. Two bulk paths:
Bulk import CSV β
Members imported via bulk import can include the unit name in their column. The import matches by name (case-insensitive) against org_units at the relevant level and creates assignments in the same transaction. Unmatched names show up in the dry-run preview so you can fix typos before committing.
Bulk actions inside the app β
From Members, filter the list to the subset you want, hit Bulk actions β Assign to unit, and pick the target. GCM upserts the assignments β existing rows at the same level are replaced, not duplicated.
Removing an assignment β
Three paths:
- From the Members panel on a unit page β hover, click the trash icon, confirm.
- From the member's profile β clear the picker for that level.
- From bulk actions β Unassign from current unit at level N.
Removing an assignment doesn't delete the member. It just removes the row from member_unit_assignments. The member becomes "unassigned at that level" and stops appearing in that unit's rolled-up reports.
What gets counted where β
Once a member has assignments, every count in the app respects them:
| Count | Cascade rule |
|---|---|
| Unit-card "N members" | Direct assignments only (no descendants) |
| Subtree total on a branch detail page | Member count + sum of every descendant's direct count |
| Attendance dashboard filter | All members visible in the selected unit's subtree |
| Giving dashboard filter | Same β member's giving rolls up the tree |
| Map pins per unit | Each unit's direct assignments are clustered around the unit's coordinates |
The "card" count and the "subtree total" being different is intentional. A branch with three centers and 200 members each shows "0 members" on its own card if nobody is directly assigned to the branch (which is normal β people are usually assigned to cells, not branches). The branch detail page shows "600 members" in the subtree stat.
Auto-assignment workflows β
Workflows can create assignments programmatically. The most common pattern: a visitor follow-up workflow assigns the visitor to the cell whose leader took the first call. See Workflow actions for the Assign to unit node spec.
Workflow assignments use the same one-per-level rule
If the workflow assigns a member to a cell and they're already in a different cell, the existing row is replaced. The audit log records both the unassign and the re-assign.
Common questions β
What if a member has no unit at all? Then they're "unassigned" and don't roll up to any branch. They appear in the all-members list and the global dashboard but not in any filtered report. This is the default for new members until you (or a workflow) place them.
Can I bulk-move members from one cell to another? Yes β filter the list to the source cell, select all, run Bulk actions β Assign to unit with the target cell. The existing assignments get replaced.
Why does a member's profile show two assignments at level 3? It shouldn't β the unique index prevents that. If you see it, something legacy is in the data. Reach out to support and reference the member's id.
Next steps β
- Assign leaders and shepherds so users β not members β get scoped views.
- Archive units when membership reshuffles.
- Read the bulk-import guide if you're seeding a fresh org.
