A class-level permission is a rule on a class schema that controls which users or roles may run each operation on that class at all. It’s the outermost gate of data-layer security: before any per-row check happens, the class itself decides whether this caller may find, create, update, or delete here — one rule, whole table, checked first.
Key takeaways
| Question | Answer |
|---|---|
| What it is | Per-operation access rules on the class (table) itself |
| vs. ACLs | CLP gates the class; ACLs gate each row — requests must pass both |
| The operations | Find, get, count, create, update, delete, add field — each set separately |
| The golden defaults | Require authentication, lock add-field, never ship the master key |
| SQL cousin | GRANT/REVOKE on tables and schemas — same idea, different syntax |
The class gate, declared and felt
A CLP is schema configuration — here as the REST payload that locks a Config class to public-read, server-only-write:
PUT /schemas/Config
{
"classLevelPermissions": {
"find": { "*": true }, // anyone may query
"get": { "*": true },
"create": {}, // nobody from the client side
"update": { "role:admin": true }, // admins only
"delete": {},
"addField": {} // schema frozen
}
}
What clients experience is the gate doing its job — reads flow, writes die at the class boundary before any data is touched:
// JavaScript / Node.js — Back4app JS SDK
// The class gate in action: Config is read-only for clients (CLP),
// so reads succeed and writes never reach the data
const config = await new Parse.Query('Config').first(); // ✓ public read
const c = new Parse.Object('Config');
c.set('flag', true);
try {
await c.save(); // ✗ CLP blocks client writes to this class entirely
} catch (e) {
console.log(e.code); // 119: operation forbidden by class-level permissions
} // Flutter / Dart — Back4app Flutter SDK
// The class gate in action: reads allowed, client writes blocked by CLP
final config = ParseObject('Config')..set('flag', true);
final response = await config.save();
if (!response.success) {
print(response.error?.code); // 119: forbidden by class-level permissions
} // iOS / Swift — Back4app Swift SDK
// The class gate in action: reads allowed, client writes blocked by CLP
var config = Config()
config.flag = true
config.save { result in
if case .failure(let error) = result {
print(error) // operation forbidden by class-level permissions
}
} // Android / Kotlin — Back4app Android SDK
// The class gate in action: reads allowed, client writes blocked by CLP
val config = ParseObject("Config").apply { put("flag", true) }
config.saveInBackground { e ->
if (e != null) {
Log.d("CLP", "blocked: ${e.code}") // forbidden by class-level permissions
}
} How Class-Level Permissions and ACLs Work Together
The granularity ladder: CLP vs. ACL vs. pointer permissions vs. protected fields
| Mechanism | Scope | Declared where | Checked | Typical job |
|---|---|---|---|---|
| Class-level permission | Whole class, per operation | Schema | First | ”Clients never write Config” |
| Pointer permission | Per object, via a schema rule | Schema (keyed to a field) | Second | ”Only the owner touches their rows” |
| ACL | Per object | On each row, as data | Second | ”This note: its author only” |
| Protected fields | Per column | Schema | On read | ”Hide email from other users” |
The design idiom that falls out: broad strokes on the schema, fine grain on the rows. Classes whose access rule is uniform (config, catalogs, logs) need only the class gate; classes with per-user data add ACLs or pointer permissions beneath it.
The same gate in other engines
-- PostgreSQL: table-level privileges are CLPs by another name
GRANT SELECT ON films TO PUBLIC;
GRANT INSERT, UPDATE ON films TO role_editor;
REVOKE ALL ON launch_codes FROM PUBLIC;
Document databases mirror it with roles scoped to collection-level actions — a role granted find-and-insert on exactly one collection. The concept is universal; what differs is ergonomics: SQL grants and role documents are administered in code, while BaaS platforms surface the same matrix as dashboard toggles.
Recommended settings by content type
| Content type | Find/Get | Create | Update/Delete | Add field |
|---|---|---|---|---|
| Public content (catalog, posts) | Public | Roles/server | Roles/server | Off |
| User-owned data (notes, orders) | Authenticated + ACLs | Authenticated | ACL-scoped | Off |
| Config and flags | Public or authenticated | Nobody (server only) | Admin role | Off |
| Logs and analytics | Nobody (server only) | Authenticated (write-only) | Nobody | Off |
| Admin-only data | Admin role | Admin role | Admin role | Off |
That table is the checklist most launches actually need — note the constant in the last column, and its sibling rule: client-side class creation off in production, so the schema only changes on purpose.
Common use cases
- Freezing production schemas. Add-field off everywhere; classes stop appearing and mutating from client traffic.
- Read-only reference data. Catalogs and settings publicly readable, writable only by server code — the
Configpattern above. - Write-only inboxes. Feedback and telemetry classes clients may create into but never read — the inverse gate app-layer code often forgets.
- Role-tiered admin surfaces. Update and delete reserved to an admin role while the app reads freely.
- Defense against the classic dump. The infamous one-liner — a curl with an app ID querying an open
Userclass — dies at the find gate when the class requires authentication.
Should the class gate or server code enforce it? A decision matrix
| Enforce with CLPs (+ ACLs) when… | Route through server code when… |
|---|---|
| The rule is “who may do what, where” | The rule needs business logic (“only before the order ships”) |
| It’s uniform per class or per owner | It spans multiple objects or classes |
| You want it enforced on every path, including new clients | You want one audited chokepoint for a sensitive workflow |
| Declarative beats imperative for review | Validation, enrichment, or side effects ride along |
| The dashboard toggle is the whole spec | The spec is a paragraph of conditions |
They compose: the hardened pattern for genuinely sensitive classes locks the CLPs entirely and exposes server-side functions as the only doorway — the class gate guarantees nobody walks around the chokepoint.
Limitations and trade-offs
- Class granularity is coarse by definition. Anything per-row belongs to ACLs and pointer permissions; anything conditional belongs in server-side validation — CLPs gate, they don’t reason.
- Defaults are permissive for development. New classes are born open so prototypes fly; the pre-launch pass that flips the table above is a discipline, not an automatism.
- The master key ignores everything. Every gate in this article is void wherever that key travels — which is why it lives server-side only, used per-operation, never shipped.
- Enforcement surface must be complete. Real-time subscriptions and special endpoints have historically had enforcement gaps — keep the platform updated, and test the gates from a real client, not just the dashboard.
- Toggles need review too. Declarative security is auditable security only if someone audits it — the settings matrix belongs in your launch checklist, not in tribal memory.
CLPs on Back4app
Back4app is an open-source Backend-as-a-Service (BaaS) platform that combines a managed database, auto-generated REST and GraphQL APIs, authentication, file storage, and Cloud Code serverless functions. Class-level permissions are its schema-security surface made visual: every class in the dashboard carries the operations matrix as checkboxes — public, requires-authentication, per-role — plus pointer permissions and protected fields, enforced server-side on every request across SDKs, REST, and GraphQL alike. Underneath sit per-object ACLs; above sit Cloud Code triggers for the rules that need logic. The security guidelines walk the full pre-launch pass — the settings matrix in this article, applied click by click.
Frequently asked questions
What are class-level permissions?
Rules attached to a class (table/collection) schema that control which users or roles may perform each operation — find, get, count, create, update, delete, add field — on any object in that class. They are the coarsest access gate and the first one checked: if the class-level rule denies an operation, no per-object permission is ever consulted.
What is the difference between CLPs and ACLs?
Scope and order. A CLP answers "who may touch this class at all" — one rule for the whole table. An ACL answers "who may touch this specific row" — data carried by each object. A request must pass both gates: the class gate first, then the object gate, and either can deny. Broad strokes at the class level, fine grain at the object level.
What is requiresAuthentication?
The middle setting between public and enumerated roles: it restricts an operation to any logged-in user with a valid session, without naming specific users or roles. It is the right default for most app data — anonymous requests are rejected, while every authenticated user passes the class gate and proceeds to per-object ACL checks.
What are pointer permissions?
Class-level rules keyed to a user-pointer field on the object — for example "only the user in the owner field may read or write." They act as a virtual ACL: enforcement is per-object, but the rule is declared once on the schema instead of stored on every row. They intersect with real ACLs; both must allow the action.
What are protected fields?
Field-level rules layered on the class gate: specific columns — an email address, an internal score — hidden from some requesters while the rest of the object stays readable. They turn the permission ladder into three rungs on one schema: class-wide operations, per-object access, and per-field visibility.
What is the SQL equivalent of class-level permissions?
Table and schema privileges: GRANT SELECT, INSERT, UPDATE, DELETE ON a table to a role, REVOKE to remove, plus schema-level USAGE and CREATE rights. The concept maps one-to-one — a table-level GRANT is a class-level permission with different syntax — and document databases mirror it with roles scoped to collection-level actions.
Should clients be allowed to add fields or create classes in production?
No — this is the consensus hardening step. Schema flexibility is a development convenience; in production, disable the add-field permission on every class and turn off client class creation entirely, freezing the schema against clients. Schema changes then flow through the dashboard or server-side code, where they belong.
Are class-level permissions enough to secure an app?
They are the first layer, not the whole defense. The standard stack: CLPs gate operations per class, ACLs or pointer permissions scope rows, protected fields hide sensitive columns, and server-side triggers validate writes. And one rule above all: the master key — which bypasses every gate — never ships in client code.