Designing Your Data Model for Access Control
Knack enforces record access through the connections between your tables, so how you structure your data decides what each user can reach. Design for that before you build.
What you'll learn
Data Access Control in Knack enforces record access through the connections between your tables, which makes your data model the thing that decides what each user can reach. This article covers the structural decisions that determine whether your access rules can work at all, and why some of them are expensive to change later.
Access control follows your connections
Most people think of permissions as a settings screen. In Knack they are closer to a consequence of your table design.
Data Access Control works in terms of records a user owns and records they do not. Which records a user owns is decided by how your tables connect. So the question "can a provider see this appointment?" is answered by your data model long before anyone opens the Data Access screen.
This is why access control belongs in your design conversation and not at the end of it.
Every record has one owner
A record has a single owner. Knack sets it when the record is created, based on who created it.
That is enough when only one role needs scoped access. A patient portal where patients see their own appointments works cleanly: the appointment is owned by the patient, and Data Access Control gives patients access to records they own.
It stops being enough the moment a second role needs their own scoped view of the same records. If appointments are owned by the patient, there is no second ownership slot for the provider. You can give providers access to all appointments, or to none, but not to "their own".
What to do when two roles both need a scoped viewGive the second role broad read access and narrow what their screen displays, while being clear that this controls display and not permission. Or connect the record directly to both roles and design your access rules around those connections rather than around ownership. Which one is right depends on whether the boundary needs to be enforced or merely tidy.
Ownership does not travel across connections
This is the one that catches experienced people.
Access does not flow through a chain of connections. If a supervisor connects to a clinician, and that clinician connects to a record, the supervisor does not get access to the record. Knack does not walk the chain.
Sketch it out and it looks reasonable:
Supervisor → Clinician → Appointment
But access control only sees the direct relationship. The supervisor has no connection to the appointment, so as far as the rules are concerned there is no relationship at all.
Connect directly to every role that needs access. If supervisors need to reach appointments, appointments need a connection to supervisors, populated when the record is created. A connection added later does not backfill itself.
When the same thing needs different rules, split the table
Sometimes a record needs one set of access rules early in its life and a different set later. An intake form a patient fills in and owns, which then becomes a clinical record the practice owns, is the usual shape.
Data Access Control applies per table, not per record state. You cannot say "patients own this record until it is submitted, then the clinic owns it".
Split it into two tables instead. One holds the patient's submission with patient ownership. The other holds the reviewed clinical record with clinic ownership. A connection links them.
This feels like duplication and it is not. They are genuinely two things with two different sets of people entitled to them, and the split is what makes each set of rules expressible.
Filtering in your app is not enforcement
Your app can show a user fewer records than it received. That is a display choice, and it protects nothing.
If a role is permitted to read every appointment, then every appointment is available to anyone who can call the API as that role, no matter what your screen shows. A filtered screen and an enforced boundary look identical to the person using the app and are completely different in what they guarantee.
Ask which one you are buildingFor every filter in your app, answer this: is it for tidiness, or is it protecting something? If it is protecting something, it belongs in Data Access Control, which means it belongs in your data model.
Questions to ask before you build
Work through these while your tables are still ideas. Every one of them is cheap now and expensive later.
- Which roles need to see only their own records, rather than all of them?
- For each of those, what connects a record to that person? Is it direct?
- Does any record need scoped access for more than one role at once?
- Does any record change hands during its life, so that who is entitled to it changes?
- Which tables hold people who sign in, as opposed to information?
That last one matters most, because a table is either a user role table or a standard table and the choice cannot be reversed. Getting it wrong means rebuilding the table and losing what is in it.
Common mistakes
Designing the tables first and the permissions afterwards. The permissions you can express are decided by the tables. Design them together.
Assuming access flows through a chain of connections. It does not. Connect directly.
Adding a connection after the records already exist. Existing records will have it empty, and access rules that depend on it will not apply to them.
Treating a filtered screen as a secure one. Check what the role is permitted to read, not what the page displays.
Deciding late which tables hold people who sign in. That choice cannot be undone.
Next steps
- Checking That Your Access Rules Actually Work covers how to test what each role can really reach, which is the companion to this article.
- Choosing a Default User Role for a Custom Frontend covers where new users land before anyone reviews them.
- Knack MCP Server vs. Runtime Data API covers where access control is applied when your front end asks for records.
Updated 2 days ago

