Members and memberships
AMember (id prefix mem) is a person. Their email is unique across the whole marketplace, so the same person never has two member records. A member can be deactivated with is_active without touching their memberships.
A SellerMember (id prefix selmem) is one person’s membership in one store. It’s the pivot between Seller and Member, and it’s unique on (seller_id, member_id). Each membership carries:
role_id: the member’s role in this store, one of theSellerRolevaluesis_owner:truefor the store’s owner membership,falsefor everyone elsemetadata: free-form data for your own extensions
Roles
SellerRole from @mercurjs/types lists the five roles a membership can hold. The membership stores the role as a plain string.
The vendor membership check confirms that a member belongs to the store they’re acting for. It doesn’t restrict routes by role, so enforce role rules in your own middleware when you need them.
The owner
Every store has at most one owner membership, flagged withis_owner. Mercur sets it in two places:
- Self-registration:
createSellerAccountWorkflowadds the registering member as owner withrole_seller_administration - First accepted invite:
acceptMemberInviteWorkflowmakes the new member owner when the store has none yet
updateMemberRoleWorkflow and removeSellerMemberWorkflow both reject the owner membership with a NOT_ALLOWED error. That keeps every store with someone who can administer it.
Invites
AMemberInvite (id prefix meminv) is a pending offer to join one store with one role. The service signs a JWT for each invite, stores it in token, and sets expires_at 7 days out unless you change the module’s invite_valid_duration option.
Invites follow these rules:
- One open invite per email and store: a unique index covers
(email, seller_id)whileacceptedisfalse - No invites for existing members: inviting an email that already belongs to the store fails
- Existing accounts are reused: accepting an invite for a known email links the existing member instead of creating one
- Single use: accepting deletes the invite, and an expired or accepted token fails validation
The active store
A member who belongs to several stores picks the active one per request. The vendor middleware reads the store id from thex-seller-id header, or from the session, and checks for a matching SellerMember. It then exposes the store on req.seller_context, typed by the SellerContext interface:
seller_context.seller_id, so switching stores in the Vendor panel never shows another store’s data. The store list a member sees leaves out terminated stores.