Granular review permissions
Control who can edit, comment, resolve, and approve — per user, per action.
Control exactly who can resolve, reject, edit, and delete in each document — down to the action level.
The problem
Document review is collaborative, but it's not a free-for-all. Legal needs reviewers who can comment but not resolve threads. Compliance needs approvers who can accept changes but not reject them. A junior team member should be able to add comments but not delete someone else's.
Most editors treat permissions as binary: you're either an editor or a viewer. Real review workflows need something more granular.
Permission Resolver
The permissionResolver callback lets you decide, per user and per action, what's allowed. You define the rules. SuperDoc enforces them.
const superdoc = new SuperDoc({
permissionResolver: ({ permission, role, comment, currentUser }) => {
if (permission === 'RESOLVE_OTHER') return role === 'editor';
if (permission === 'COMMENTS_DELETE_OTHER') return false;
if (permission === 'COMMENTS_DELETE_OWN') return true;
// Return undefined to fall back to built-in defaults
},
});
Permissions you can control
The resolver receives a permission string for every sensitive action:
| Permission | What it controls |
|---|---|
RESOLVE_OWN |
Resolve your own comments or tracked changes |
RESOLVE_OTHER |
Resolve other users' comments or tracked changes |
REJECT_OWN |
Reject your own tracked changes |
REJECT_OTHER |
Reject other users' tracked changes |
COMMENTS_OVERFLOW_OWN |
Edit your own comments (overflow menu) |
COMMENTS_OVERFLOW_OTHER |
Edit other users' comments |
COMMENTS_DELETE_OWN |
Delete your own comments |
COMMENTS_DELETE_OTHER |
Delete other users' comments |
UPLOAD_VERSION |
Upload document versions |
VERSION_HISTORY |
Access version history |
The resolver context
The callback receives everything you need to make a decision:
permissionResolver: ({
permission, // The action being checked (e.g. 'RESOLVE_OTHER')
role, // Current user's role: 'editor' | 'viewer' | 'suggester'
comment, // The comment being acted on (if applicable)
trackedChange, // The tracked change (if applicable)
currentUser, // The user performing the action
isInternal, // Whether the comment is internal
defaultDecision, // What SuperDoc would decide without your resolver
}) => {
// Return true to allow, false to block
// Return undefined to use the built-in default
};
Real-world patterns
Legal review: only editors can resolve
permissionResolver: ({ permission, role }) => {
if (permission === 'RESOLVE_OWN' || permission === 'RESOLVE_OTHER') {
return role === 'editor';
}
};
External collaboration: no one can delete others' comments
permissionResolver: ({ permission }) => {
if (permission === 'COMMENTS_DELETE_OTHER') return false;
if (permission === 'COMMENTS_OVERFLOW_OTHER') return false;
};
Strict review: viewers can't reject tracked changes
permissionResolver: ({ permission, role }) => {
if (permission === 'REJECT_OWN' || permission === 'REJECT_OTHER') {
return role === 'editor';
}
};
Configuration
The resolver can be set at the top level or scoped to the comments module (module-level takes precedence):
// Top-level
new SuperDoc({
permissionResolver: ({ permission, role }) => { /* ... */ },
});
// Or module-level
new SuperDoc({
modules: {
comments: {
permissionResolver: ({ permission, role }) => { /* ... */ },
},
},
});
Get started
Pass a permissionResolver to your SuperDoc configuration. The callback runs on every permission-sensitive action - no middleware, no separate permission service.