Two applications, one member base
Avivando is a member management platform in production, serving over 500 users. It is two integrated applications built on Payload CMS — a public site and an operational core covering member records, forms, check-in, announcements and activities, with role-based access control. Built alongside a senior engineer.
Context
The platform serves an organization that tracks members, runs activities and needs different people to see different things. It is two applications: a public site, and an operational core with member records, forms, check-in, announcements, activities and role-based access.
I worked in a pair with a senior engineer. The two of us shared the codebase throughout, and my design decisions were questioned in review — that loop is where most of the reasoning on this page came from. I am not going to draw a line through the work and claim a side of it.
The problem
A member management platform is mostly a permissions problem wearing a CRUD costume. Leaders see their own group; coordinators see several groups; administrators see everything; a member sees themselves. Every list, every dashboard, every export and every check-in screen has to answer the same question — who is allowed to see this row — and answer it identically.
The second problem is that two applications read overlapping data. A public site and an operational core with separate front ends can easily drift into two definitions of what a member is, two shapes for an activity, two places to change when a field is added.
Constraints
The architecture
Payload CMS, running on Node.js and TypeScript, is the core. Content and domain objects are declared as collections in code; PostgreSQL is the store, reached through @payloadcms/db-postgres, and schema changes are migrations checked into the repository alongside the collection definitions.
The public site is a separate application that integrates with the core rather than talking to the database on its own. Both applications end up reading one definition of a member, one definition of an activity, and one set of API routes over them. Adding a field is one change in one place.
Access control is role-based and configured per collection in Payload. Roles decide what each collection exposes, and the same configuration governs both applications.
Trade-offs
Building the core on a CMS rather than a bespoke service is a real choice with real costs.
Quality and tests
There are 181 automated tests, and CI runs lint, type checking, tests and build against an isolated PostgreSQL instance, plus end-to-end tests with Playwright.
The pipeline is the part I would defend hardest. Two people on a live system with real member data need a gate that does not depend on either of them being careful that day.
Results
In production, serving over 500 users. Two integrated applications, 23 content modules and 22 API/BFF routes run against a single core and a single schema.
The outcome I care about is narrower than a usage number: a field added to a member is added once, and both applications see it.
What this case does not claim
A specific database-level authorization mechanism. Access control is role-based and configured per collection in Payload CMS; the underlying policies were not independently re-verified for this write-up, so this page does not describe them in more detail than that.
A division of labour. The work was done in a pair and the commit history does not accurately reflect how effort was split, so there is no per-directory or per-commit authorship claim here — only that I was one of the two people who built it.
Retrospective
The part worth carrying forward is the single definition. Two applications, one schema, one place where a member is described. Most of the bugs this project did not have are bugs of two systems disagreeing about the same row.
What I would do differently is write the access-control test matrix first — one seeded user per role, one assertion per collection — instead of growing it behind the features. The tests that exist were largely written to catch behaviour I had already shipped to staging.