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.

Read the docs