Access requests
Members ask for a role in their organization, for a set time if they like. The request goes through approvals, the final approval grants the role, and the grant ends early or when it expires.
access-requests lets a member ask for a role (a viewer asking to be an admin for month end, a contractor asking to join). It files the request with the approvals module, so approvers, steps, delegation, notifications and expiry work as they do there. The final approval grants the role, and when the grant has a duration, expire_access_grants() puts the previous role back. Asking needs access_requests.create, reading other people's requests needs access_requests.read, and ending someone else's grant needs access_requests.manage.
better-supabase sql add access-requestsimport { createAccessRequests } from "better-supabase/blocks/access-requests";
import { createApprovals } from "better-supabase/blocks/approvals";
const access = createAccessRequests({ transport });
const request = await access
.request({
organizationId,
role: "admin",
reason: "Month end",
duration: Temporal.Duration.from({ days: 3 }),
})
.orThrow();
// An approver decides through approvals.
const approvals = createApprovals({ transport });
await approvals.approve(request.approvalId!).orThrow();Configure
sql: {
modules: {
"access-requests": {
options: {
roles: ["admin", "billing"],
maxDuration: "P7D",
policy: { steps: [{ approvers: { role: "owner" } }], expiresIn: "P2D" },
},
},
},
},| Option | Default | What it does |
|---|---|---|
roles | every role of the access model except owner | The roles a member may ask for (ACCESS_ROLE_INVALID otherwise) |
policy | one step: holders of members.update_role | An approvals policy without run: steps, expiresIn, selfApproval |
maxDuration | none | An ISO 8601 duration that caps a grant and is the default when a request names none |
join | false | Lets someone outside the organization ask to join with a role |
A member can have one pending request per organization (ACCESS_REQUEST_PENDING), can't ask for the role they hold (ACCESS_ROLE_HELD), and an owner can't ask for another role.
Statuses
pending, rejected, cancelled and expired follow the approval request; cancel a request with approvals.cancel(request.approvalId). granted means the role is held, and ended means it was given up, revoked or ran out.
end(id) ends a grant early, for the holder or a manager. Schedule expire_access_grants() to end grants past their expiresAt:
select cron.schedule('expire-access', '*/5 * * * *', 'select better_supabase.expire_access_grants()');When a grant ends, the previous role comes back (or the membership goes, for someone who joined through the request), unless the role was changed in the meantime: a later change wins.
The provider access model
With the provider access model the module doesn't touch memberships. The authorization provider's adapter grants the role when it receives access.request_approved and takes it back on access.ended.
Events and hooks
The module records access.requested, access.request_approved and access.ended (with reason set to expired or revoked), each with organizationId, userId, role, the request id, approvalId and expiresAt; they also go to the audit log. The approvals module records its own approval.* events for the request. The module calls before_access_request, after_access_request, before_access_grant and after_access_grant.
With ReUI
An admin page renders list(organizationId, { status: "pending" }) in a ReUI data grid with approve and reject actions from the approvals client. A member's request form pairs a role select with a duration select and shows their requests from list(organizationId, { mine: true }).
Last updated on
Approvals
Approval requests with steps per subject type and action, approvers by permission, role, team or user, delegation, expiry and reminders. The final approval runs a state-machine transition or a job.
Documents
Collaborative Yjs documents attached to any subject. The database keeps an update log and a compacted snapshot, a private Realtime topic carries live edits and presence, and better-supabase/yjs connects a Y.Doc to both.