
How permissions, memberships, and activity trails were made reliable in a campaign operations platform.
Context
The creator marketing platform runs as a multi-organisation system. One user can belong to multiple organisations, each organisation can have different role assignments, and site admins need elevated visibility across all tenants. Most day-to-day workflows, like campaign edits, event management, and billing, are organisation-scoped. That means permission design is not just security work. It directly affects how confidently teams can operate.
The stack used GraphQL resolvers with Prisma, plus a Vue frontend that consumed permission state for UI gating. There was also an invitation system for adding members and a shared activity log used across major domains.
Challenge
The biggest risk in multi-org setups is inconsistent boundaries. One resolver checks membership correctly while another only checks authentication. One mutation enforces role permissions while another assumes frontend controls are enough. Over time, that drift creates hidden access bugs and painful audits.
There was also a workflow challenge. Teams needed to invite members, apply role sets per organisation, and keep ownership clear when people moved between organisations. If invitation handling was brittle or role mapping was too loose, manual admin work increased fast.
So the goal was less about adding one new permission and more about creating a predictable model: who can see what, who can change what, and where that decision is enforced.
Approach
We treated access checks as shared primitives instead of ad hoc logic. Resolver entry points use explicit guards like requireAuth, requireOrganisation, and permission checks for privileged actions. On top of that, organisation membership is validated directly against the organisation-user join model before member lists or organisation activity feeds are exposed.
Role handling was scoped to organisation context, which is important because the same user can carry different responsibilities in different tenants. Member queries hydrate role-permission details per user, so teams can inspect access intent without digging through multiple tables.
Invitation flow was built to support safe onboarding. Invitation queries enforce token validity, accepted state, expiry windows, and organisation soft-delete checks before returning anything useful. This keeps old or stale invites from silently becoming an access path.
We also made auditability a first-class behaviour. Core organisation and campaign operations write activity logs with actor and action details. That gives operations teams a timeline that mirrors real workflow events, not just raw database writes.
On the frontend side, permission checks were centralized through a composable so components can ask simple questions like has permission, has any, or has all. This does not replace backend enforcement, but it keeps the UI consistent with backend policy and reduces accidental feature exposure.
Outcome
The result was a calmer operating model. Teams could add members, assign roles, and manage org-scoped resources with fewer surprises. Engineers had clearer expectations when adding new resolvers because there was an established guard pattern to follow.
This also improved cross-functional trust. Product and support teams could reason about why a user did or did not see an action. Admins had cleaner visibility into membership and activity history. Security-wise, tenancy boundaries became explicit enough to review instead of implicit assumptions scattered through the codebase.
Again, the outcome was operational rather than vanity-driven: less manual access troubleshooting, cleaner onboarding flow for organisation members, and a stronger baseline for future feature growth.
Key takeaway
Multi-tenant reliability usually comes from consistency, not complexity. Shared access guards, org-scoped role resolution, and clear activity trails made the platform easier to secure and easier to run.