UX Encyclopedia

Permission Flow

A permission flow maps who can do what, and — the half that gets skipped — what everyone else sees when they can't. Three distinct systems get called "permissions"; a product often has all three:

  • Role-based access: admin / owner / editor / viewer / guest.
  • Account-tier access: free / trial / paid / enterprise — the same UI gated by billing instead of trust.
  • Device permissions: camera, mic, location, notifications, contacts — granted by the OS, revocable outside your product.

When to use one

Any multi-user product, plans/tiers, or device capabilities. Draw it before wireframes: every cell of the permission matrix is a screen variant someone must design, and undesigned cells ship as silent 403s and mystery-disabled buttons.

What it answers

  • For each role × action pair: allowed or not?
  • For each "not": what does that user SEE, and what's their route to getting the capability (request, upgrade, ask the owner) or a dignified alternative?
  • For device permissions: when do we ask, what happens on "no," and how does the user recover after a denial?

The permission matrix — roles × actions

List real actions (not features) down the side, roles across the top. Then make the design decision per cell — the matrix below shows the decision vocabulary in use:

Action Admin Owner Editor Viewer Guest
View project Allow Allow Allow Allow Request access
Edit content Allow Allow Allow Disable + "Viewers can't edit — request edit access" Hide
Invite members Allow Allow Disable + "Ask an admin" Hide Hide
Export data Allow Allow Upgrade prompt (paid tier) Hide Hide
Delete project Allow (any) Allow (own) Hide Hide Hide
Billing settings Allow Allow Hide Hide Hide

The four verbs for a blocked cell, and when each fits:

  • Hide — when the capability is irrelevant or its existence is sensitive (billing from viewers). Cost: undiscoverable; nobody ever requests what they can't see.
  • Disable with explanation — when the user should know it exists and why it's off, with the route named ("request access," "ask an admin"). Never a bare grayed-out button — an unexplained disable is a support ticket.
  • Upgrade prompt — for tier gates: show the capability, state the tier, make the value case. This is marketing surface; overuse reads as a nag wall (check Persuasion, Ethics & Dark Patterns).
  • Request access — for role gates: a one-tap request that notifies whoever can grant it, tells the requester it's pending, and closes the loop on grant/deny. The request itself is a multi-user flow (Multi-User Flow).

Convention: hide what's irrelevant, disable-and-explain what's aspirational, and never let the user compose work they can't submit — check permissions before the effort, not at the save button.

Device-permission flows: prime → ask in context → handle denial

OS permission dialogs are one-shot and system-owned, so the flow around them is your only design surface (platform details: Mobile App Design; prompt patterns: Notifications & Communication and Privacy & Security UX):

Feature needs camera
  ├─ Not yet asked ──▶ Prime in your UI ("Scanning needs the camera —
  │     enable it?") at the moment of use, never at launch
  │       ├─ user says yes ──▶ OS dialog ──▶ granted ──▶ feature
  │       │                              └─▶ denied ──▶ denial state
  │       └─ user says no ──▶ dismiss quietly; re-offer only at next
  │             natural use — priming "no" costs nothing, unlike an
  │             OS-level "no" (Apple HIG / Android guidance: ask in
  │             context, with the reason)
  └─ Previously denied ──▶ denial state: explain what's unavailable,
        offer the SETTINGS RECOVERY PATH ("Enable camera in
        Settings ▸ Privacy" + direct settings link where the OS
        allows) + a fallback (type the code instead of scanning)

The denial state is permanent UI, not an edge case: users revoke permissions in OS settings months later; degrade to something useful, never a blank screen demanding the camera.

Example — role gate as a decision flow

User opens Team Settings
  ├─ admin? ──▶ full Edit Team Settings
  ├─ member (editor/viewer)? ──▶ View Only + "Only admins can edit —
  │      request admin access" ──▶ request sent ──▶ pending badge
  │        ├─ granted ──▶ notify + unlock (return them to this page)
  │        └─ denied/expired ──▶ notify with reason or "ask <owner>"
  │              — never silent limbo
  └─ guest? ──▶ Request Access gate (page existence acknowledged,
         contents hidden) ──▶ same request loop as above

Failure branches to note: the request loop closes in all outcomes (granted, denied, expired) — an unanswered access request is the permission flow's most common dead end. Also design the demotion branch: what happens to an editor's draft when they're downgraded mid-edit (preserve the work, block the submit, explain).

Common mistakes

  • Designing only the admin's view; every other role inherits broken layouts full of hidden gaps.
  • Bare disabled buttons with no reason and no route.
  • Asking for all device permissions at first launch (documented denial driver — see Mobile App Design).
  • No settings-recovery path after an OS-level denial.
  • Access requests that vanish into nowhere — no notification to the granter, no status for the requester.
  • Checking permissions only in the UI; the API allows what the button hid (a security bug wearing a UX costume).

Checklist

  • Matrix exists: every action × every role (+ every tier), every cell assigned hide / disable+explain / upgrade / request.
  • Each role's version of each screen is designed, not inferred.
  • Every disable states its reason and its route.
  • Request-access loop closes on grant AND deny, with notifications both ways (Multi-User Flow).
  • Device permissions: primed in context, denial state designed, settings recovery path + fallback provided.
  • Mid-session changes handled: revocation, demotion, expiry, and logout preserve in-progress work.
  • Permission-denied appears as a state in the component's State Flow; enforcement exists server-side, not just in the UI.

Sources

↑↓ to navigate · Enter to open · Esc to close