Table of Contents
- Granular Access Control
- Overview
- How Field Rules Work
- View Rules (fieldRules)
- Edit Rules (fieldEditRules)
- Creating Collection with Edit Rules
- Creating Records with Edit Restrictions
- Updating Records with Edit Restrictions
- Edit Rules and Required Fields
- Edit Rules Bypass Global Rules
- Using Wildcards
- Group Priority and Union
- Practical Examples
- Web UI Configuration
- API Reference
- Behavior Summary
- Security Considerations
- 1. Defense in Depth
- 2. Default to Deny
- 3. Required Field Handling
- 4. System Field Protection
- 5. Test Access Patterns
- 6. Monitor Warnings
- Common Patterns
- Troubleshooting
- "Access Denied" on List/Get
- Fields Not Saving on Update
- Required Fields Null After Create
- Wildcard Not Working
- Global Rules Still Applying
- See Also
Granular Access Control
VSKI provides field-level access control that allows you to control which fields are visible or editable by different user groups. This works in addition to the standard collection-level rules.
Overview
Granular access control gives you fine-grained control over:
- View Rules (
fieldRules) - Control which fields users can READ (view) - Edit Rules (
fieldEditRules) - Control which fields users can WRITE (create/update)
Key Benefits
- Data Privacy - Hide sensitive fields (like passwords, internal notes) from certain user groups
- Data Integrity - Prevent users from modifying critical fields (like status, approval flags)
- Flexible Security - Override global rules with field-level restrictions
- Group-Based - Different access levels for different user roles
How Field Rules Work
Rule Priority
Field-level rules have higher priority than global collection rules:
Collection-level rules:
├─ listRule
├─ viewRule
├─ createRule
├─ updateRule
├─ deleteRule
└─ executeRule
Field-level rules (HIGHER PRIORITY):
├─ fieldRules (VIEW) → Overrides viewRule, listRule
└─ fieldEditRules (EDIT) → Overrides createRule, updateRule
When field rules are defined for a user's groups, global rules are bypassed.
Rule Format
Field rules use the following JSON format:
{
"groupName1": ["field1", "field2", "field3"],
"groupName2": ["*"], // Wildcard = all fields
"*": ["publicField"] // Public group applies to all users
}
System Fields
The following system fields are always included in view rules and always read-only in edit rules:
id- Record identifiercreated- Creation timestampupdated- Last update timestamp
These fields cannot be removed from field rules.
View Rules (fieldRules)
View rules control READ access - which fields users can see when listing or viewing records.
Creating Collection with View Rules
await client.settings.collections.create({
name: "user_profiles",
type: "base",
fields: [
{ id: crypto.randomUUID(), name: "username", type: "text", required: true },
{ id: crypto.randomUUID(), name: "email", type: "email", required: true },
{ id: crypto.randomUUID(), name: "phone", type: "text" },
{ id: crypto.randomUUID(), name: "ssn", type: "text" }, // Sensitive!
{ id: crypto.randomUUID(), name: "notes", type: "text" }, // Internal!
],
listRule: "1=1",
viewRule: "1=1",
createRule: "1=0", // Admin only
updateRule: "1=0",
deleteRule: "1=0",
// Field-level view rules
fieldRules: {
"public": ["username"], // Public can only see username
"viewer": ["username", "email", "phone"], // Viewers can't see SSN or notes
"admin": ["*"], // Admins see everything
},
});
How View Rules Affect Queries
When a user queries records with field-level restrictions:
// User in "viewer" group
const records = await client.collection("user_profiles").getList(1, 10);
// Result - only username, email, phone, id, created, updated
// SSN and notes are filtered out automatically
[
{
id: "abc123",
username: "john_doe",
email: "john@example.com",
phone: "+1234567890",
created: "2026-02-22T10:00:00Z",
updated: "2026-02-22T10:00:00Z",
// ssn and notes are NOT included
},
];
View Rules Bypass Global Rules
If a user has field-level view access, global viewRule and listRule are
bypassed:
// Global rule says "no access"
viewRule: "1=0", // No one can view (except admin)
// But field rules give access
fieldRules: {
"viewer": ["title"] // Viewers can see title
}
// Result: User in "viewer" group CAN view records
// despite global viewRule being "1=0"
Edit Rules (fieldEditRules)
Edit rules control WRITE access - which fields users can set when creating or updating records.
Creating Collection with Edit Rules
await client.settings.collections.create({
name: "support_tickets",
type: "base",
fields: [
{ id: crypto.randomUUID(), name: "title", type: "text", required: true },
{
id: crypto.randomUUID(),
name: "description",
type: "text",
required: true,
},
{ id: crypto.randomUUID(), name: "status", type: "text", required: true }, // Workflow field
{ id: crypto.randomUUID(), name: "priority", type: "text", required: true }, // Workflow field
{ id: crypto.randomUUID(), name: "resolution", type: "text" },
],
listRule: "1=1",
viewRule: "1=1",
createRule: "1=0",
updateRule: "1=0",
deleteRule: "1=0",
// Field-level edit rules
fieldEditRules: {
"customer": ["title", "description"], // Customers can only set title, description
"agent": ["title", "description", "resolution"], // Agents can't change status/priority
"admin": ["*"], // Admins can edit everything
},
});
Creating Records with Edit Restrictions
// User in "customer" group
const record = await client.collection("support_tickets").create({
title: "Login issue",
description: "I can't log in",
status: "open", // IGNORED - not in customer's edit list
priority: "high", // IGNORED - not in customer's edit list
resolution: "" // IGNORED - not in customer's edit list
});
// Result: Record created with only allowed fields
{
id: "ticket-123",
title: "Login issue",
description: "I can't log in",
status: "open", // Set by DB default or trigger
priority: "normal", // Set by DB default or trigger
resolution: null,
created: "2026-02-22T10:00:00Z",
updated: "2026-02-22T10:00:00Z"
}
Updating Records with Edit Restrictions
// User in "agent" group
const updated = await client.collection("support_tickets").update(
"ticket-123",
{
title: "Updated title", // ALLOWED
description: "Updated description", // ALLOWED
resolution: "Fixed by password reset", // ALLOWED
status: "closed", // IGNORED - not in agent's edit list
priority: "low", // IGNORED - not in agent's edit list
},
);
// Result: Only title, description, resolution are updated
// status and priority remain unchanged
Edit Rules and Required Fields
If a required field is not in a user's edit field list, the server logs a warning but allows the operation. The field may still get a value through:
- Database default values
- Trigger hooks
- Migration defaults
Example:
// Field "status" is required
fields: [
{ name: "status", type: "text", required: true }
]
// But customer group can't edit it
fieldEditRules: {
"customer": ["title", "description"] // status NOT included
}
// Customer creates record - warning logged but allowed
await client.collection("tickets").create({
title: "Issue",
description: "Details"
// status not provided, but DB has default "open"
});
// Server logs: "Creating record with required fields not in allowed edit fields: status"
// But record is created successfully with status = "open" (default)
Edit Rules Bypass Global Rules
If a user has field-level edit access, global createRule and updateRule are
bypassed:
// Global rule says "no access"
createRule: "1=0", // No one can create (except admin)
updateRule: "1=0", // No one can update (except admin)
// But field rules give access
fieldEditRules: {
"creator": ["title"] // Creators can create/edit title
}
// Result: User in "creator" group CAN create/update records
// despite global createRule/updateRule being "1=0"
Using Wildcards
The * character has special meanings:
Wildcard in Group Name ("*")
The "*" group represents all users, including unauthenticated (guest)
users:
fieldRules: {
"*": ["title", "description"] // Everyone can see these
"admin": ["*"] // Admins see everything
}
Wildcard in Field List (["*"])
A field list containing only ["*"] means all fields are allowed:
fieldEditRules: {
"editor": ["*"] // Editors can edit every field
"viewer": ["title", "description"] // Viewers restricted
}
Group Priority and Union
Users can belong to multiple groups. Field rules are combined using union logic:
fieldRules: {
"*": ["title"], // Public - everyone gets this
"team_member": ["description"], // Team members get this
"admin": ["notes", "internal"] // Admins get this
}
// User in "team_member" group:
// Can see: title (from *) + description (from team_member)
// Cannot see: notes, internal (only admin)
Practical Examples
HR System
Public can see name/title, HR sees salary, managers see all:
await client.settings.collections.create({
name: "employees",
type: "base",
fields: [
{ id: crypto.randomUUID(), name: "name", type: "text", required: true },
{ id: crypto.randomUUID(), name: "title", type: "text", required: true },
{
id: crypto.randomUUID(),
name: "department",
type: "text",
required: true,
},
{ id: crypto.randomUUID(), name: "salary", type: "number" }, // Sensitive!
{ id: crypto.randomUUID(), name: "performance_notes", type: "text" }, // Internal!
],
listRule: "1=0",
viewRule: "1=0",
createRule: "1=0",
updateRule: "1=0",
deleteRule: "1=0",
fieldRules: {
"public": ["name", "title"], // Anyone can see name/title
"hr": ["name", "title", "department", "salary"], // HR can see salary
"manager": ["*"], // Managers see everything
},
fieldEditRules: {
"hr": ["name", "title", "department", "salary"], // HR can edit these
"manager": ["*"], // Managers can edit everything
},
});
Customer Portal
Customers can read limited data, support can read all:
await client.settings.collections.create({
name: "orders",
type: "base",
fields: [
{
id: crypto.randomUUID(),
name: "customerId",
type: "text",
required: true,
},
{ id: crypto.randomUUID(), name: "items", type: "json", required: true },
{ id: crypto.randomUUID(), name: "total", type: "number", required: true },
{ id: crypto.randomUUID(), name: "status", type: "text", required: true },
{ id: crypto.randomUUID(), name: "internal_notes", type: "text" }, // Internal!
],
listRule: "1=0",
viewRule: "1=0",
createRule: "1=0",
updateRule: "1=0",
deleteRule: "1=0",
fieldRules: {
"*": ["customerId", "items", "total", "status"], // Public can't see notes
"support": ["*"], // Support sees everything
},
fieldEditRules: {
"customer": [], // Customers can't create/edit (only view)
"support": ["status", "internal_notes"], // Support can update these
},
});
Content Management System
Editors can create content, editors can edit content, admins manage everything:
await client.settings.collections.create({
name: "articles",
type: "base",
fields: [
{ id: crypto.randomUUID(), name: "title", type: "text", required: true },
{ id: crypto.randomUUID(), name: "content", type: "text", required: true },
{
id: crypto.randomUUID(),
name: "published",
type: "bool",
required: true,
},
{ id: crypto.randomUUID(), name: "author", type: "text", required: true },
{ id: crypto.randomUUID(), name: "featured", type: "bool" }, // Admin only!
],
listRule: "published = true",
viewRule: "published = true || author = @request.auth.id",
createRule: null,
updateRule: null,
deleteRule: "1=0",
fieldEditRules: {
"author": ["title", "content", "published"], // Authors can't change author or featured
"editor": ["title", "content", "published"], // Same for editors
"admin": ["*"], // Admins control everything
},
});
Web UI Configuration
Field-level permissions are configured in the Collection Settings page:
- Navigate to Settings → Collections
- Click on a collection to edit
- Go to Access tab
- Scroll to Field-Level Permissions section
UI Sections
The field permissions UI has two separate sections:
View Rules (blue)
Controls which fields users can READ:
- Shows which fields are visible for each group
- System fields (id, created, updated) are always included
- Badge: "Controls READ visibility"
Edit Rules (orange)
Controls which fields users can CREATE/UPDATE:
- Shows which fields are editable for each group
- System fields are read-only
- Warnings when required fields are missing from a group's allowed list
- Badge: "Controls CREATE/UPDATE access"
Managing Group Rules
For each group (or * for public):
- Add Group Rule - Create a new permission group
- Toggle Fields - Click field buttons to add/remove from allowed list
- All Fields (*) - Click to allow all fields for this group
- Remove Group - Delete a permission group
Required Field Warnings
When a required field is not in a group's edit rules:
⚠ Required fields not editable:
field1, field2Users in this group won't be able to set these fields during create/update.
The record can still be created if the field has a default value or is set via triggers.
API Reference
Field rules are included in collection configuration:
interface CollectionConfig {
// ... other properties
fieldRules?: Record<string, string[]>; // View rules
fieldEditRules?: Record<string, string[]>; // Edit rules
}
Creating Collection with Field Rules
await client.settings.collections.create({
name: "my_collection",
type: "base",
fields: [...],
fieldRules: {
"viewer": ["title", "description"]
},
fieldEditRules: {
"viewer": ["title"]
}
});
Updating Field Rules
await client.settings.collections.update("my_collection", {
fieldRules: {
"viewer": ["title"], // More restrictive
"editor": ["title", "description", "author"],
},
fieldEditRules: {
"viewer": ["title"],
"editor": ["title", "description"],
},
});
Behavior Summary
View Rules (fieldRules)
| Scenario | Behavior |
|---|---|
| No rules defined | All fields visible (default open) |
| User has field access | Global viewRule/listRule bypassed, filtered fields returned |
| User has no field access | Access denied (empty result) |
| Wildcard in field list | All fields visible |
| User in multiple groups | Union of all group fields visible |
| Admin user | All fields visible (bypasses all rules) |
Edit Rules (fieldEditRules)
| Scenario | Behavior |
|---|---|
| No rules defined | All fields editable (default open) |
| User has field access | Global createRule/updateRule bypassed, only allowed fields updated |
| User has no field access | Access denied (create/update blocked) |
| Wildcard in field list | All fields editable |
| User in multiple groups | Union of all group fields editable |
| Required field not in list | Warning logged, operation proceeds (field may have default) |
| System fields | Always read-only, cannot be edited |
| Admin user | All fields editable (bypasses all rules) |
Security Considerations
1. Defense in Depth
Field rules are an additional layer of security:
- Use field rules in addition to global rules
- Don't rely solely on field rules for security
2. Default to Deny
When configuring field rules:
- Start with empty field list (deny all)
- Add only necessary fields (allow by exception)
- More secure than starting with
["*"]and removing
3. Required Field Handling
If a required field is not in edit rules:
- The field may get a default value from DB schema
- Consider using triggers to set appropriate defaults
- Or add the field to the user's allowed list
4. System Field Protection
System fields (id, created, updated):
- Are always read-only for edit rules
- Cannot be removed from view rules
- This prevents record tampering
5. Test Access Patterns
Before deploying:
- Test with users in each group
- Verify field visibility (read operations)
- Verify field editing (create/update operations)
- Test global rule bypass scenarios
- Verify admin user behavior (should bypass all)
6. Monitor Warnings
Server logs warnings when:
- Required fields are not in edit rules
- Users attempt to edit restricted fields (silently ignored)
- Monitor these logs to catch configuration issues
Common Patterns
Public View, Private Edit
{
fieldRules: {
"*": ["title", "description"] // Public can read
},
fieldEditRules: {
"editor": ["title", "description"] // Only editors can write
}
}
Progressive Disclosure
{
fieldRules: {
"viewer": ["title"], // Basic info
"member": ["title", "description"], // More info
"admin": ["*"] // Full access
}
}
Field Lifecycle Control
{
fieldRules: {
"*": ["*"] // Everyone can read everything
},
fieldEditRules: {
"creator": ["title", "content"], // Can create initial content
"editor": ["content"], // Can edit content only
"publisher": ["published"] // Can only change status
}
}
Troubleshooting
"Access Denied" on List/Get
Problem: User gets access denied error when listing or viewing records.
Solution: Check if user's group is in fieldRules:
// User groups
const userGroups = ["viewer"];
// Check field rules
const collection = await client.settings.collections.get("my_collection");
console.log("Field rules:", collection.fieldRules);
// Does user's group exist in fieldRules?
console.log("Has access:", userGroups.some((g) => collection.fieldRules?.[g]));
Fields Not Saving on Update
Problem: User updates a record but some fields don't change.
Solution: Check fieldEditRules - those fields may not be in allowed list:
// User in "viewer" group
await client.collection("my_collection").update("record-id", {
title: "Updated", // Saved
status: "active", // NOT saved - not in viewer's edit rules
});
Required Fields Null After Create
Problem: After creating a record, required fields are null.
Solution: Field may have default value. Check:
- Field default in schema
- Trigger that sets the value
- Or add field to user's
fieldEditRules
Wildcard Not Working
Problem: User with "*" in field list can't see all fields.
Solution: Ensure wildcard is in array, not string:
// Wrong
fieldRules: {
"viewer": "*"
}
// Correct
fieldRules: {
"viewer": ["*"]
}
Global Rules Still Applying
Problem: Global rules (createRule, updateRule) still apply even with field rules defined.
Solution: Ensure user has ANY field-level access. Field rules override global rules only when user has field-level permission:
// User in "any_group" has field access → global createRule bypassed
fieldRules: {
"any_group": ["title"]
}
// User in "no_access" has NO field access → global createRule applies
fieldRules: {
"admin_only": ["*"]
}
See Also
- Rules and Access Control - Collection-level rules
- Authentication - User management and groups
- Collections - Collection configuration