Skip to main content

Core Concepts

Before integrating collaboration into your application, it's important to understand how everything works and fits together.


By default, WebViewer Collaboration behaves similar to Slack (except document based rather than chat based).

Slack - In Slack, users create channels to write messages in.

WebViewer Collaboration - In WebViewer Collaboration, users create documents to annotate on.

Slack - In Slack, users can invite other users to join a channel. Once a user has joined the channel, you can type messages back and forth to each other in real time.

WebViewer Collaboration - In WebViewer Collaboration, users can invite other users to documents. Once a user has joined your document, you can create an comment on annotations in real time.


There are 6 main entities / data types the collaboration modules work with:

  • User
  • Document
  • Annotation
  • Document Member
  • Annotation Member
  • Mention

Additionally, to use the snapshot feature, the following 2 entities are required.

  • Snapshot
  • SnapshotAsset


A user entity represents a single user in the application. A user must be signed in in order to perform actions like creating documents or annotations.


A document entity contains information about a certain document. It does not store the actual PDF document itself, or even the location of it.


You can think of a document as a "channel" in a typical chat application.


An annotation represents a single annotation in a PDF document. Each annotation belongs to a single document, and stores info like the annotation's XFDF, who created it, etc. When you load a document, all annotations that belong to that document are also loaded.


You can think of an annotation as a "message" in a typical chat application. Each message belongs to a channel (or in our case, a document).

Document Member#

A document member represents a user's membership to a document. Without being a member of a document, the user is unable to see that document (unless it is public).

There are three ways to become a member of a document.

  1. If you created the document
  2. If you were invited to the document
  3. The document is public and you join it

In a chat application like Slack, in order to read a private channel, you must "join" it via an invite from someone else. This is exactly how document membership works.

Annotation Member#

An annotation member is very similar to document members, except for annotations.

If you are a member of the document, then by default, you are then a member of all annotations that belong to that document.

Annotation member entities are used to track if a user has read a message or not.


Annotation members are only created when an annotation has been viewed


A mention represents a user mentioned in a Note annotation.

A mention overrides the notification and email settings of the annotation that it belongs to.


A snapshot represents the state of a document at a specific point in time.

Snapshots can be used to restore a document to a previous version.

Snapshot assets#

Snapshot assets are a way of splitting up large XFDF strings for more efficient read queries. They are used only behind the scenes. See this guide for more information.


WebViewer Collaboration reads and writes data through functions called "resolvers". If you have worked with GraphQL before, you may be familiar with this concept.

A resolver is simply a function that reads or writes data from a database. It resolves data!

There are two kinds of resolvers - mutation and query resolvers.

Mutation resolvers write data to the database (they mutate data), and query resolvers read data from the database (they query data).

All resolver functions accept some data as inputs, and based on that data, they must perform a certain action.

When instantiating the Collab server, you must provide a set of resolvers that allow our server to read and write data from your database. These resolvers are documented here.


If you are using an SQL-like database, we have a module to generate these resolvers for you! Check out the SQL Resolver Generator package.

Query resolvers#

As mentioned above, query resolvers are used to read data from your database. They accept some kind of input, and based on that input, return some specific data.

Let's take the user resolver for example. This resolver is used to get a user from your database. An example implementation might look like this:

const server = new CollabServer({
resolvers: {
Query: {
user: async (id) => {
const db = getDatabaseConnection();
const user = await db.query(`SELECT * FROM Users WHERE id = ${id}`);
return user;

You can see here that the resolver function accepts a user id, and returns that user from your database. Whenever the Collaboration server needs to fetch a user, it can now do so by calling this resolver!

Mutation resolvers#

Mutation resolvers are very similar, except they write data instead of reading it. They accept the data that needs to be written, and in return must write that data.

Let's look at the addUser resolver for example:

const server = new CollabServer({
resolvers: {
Mutation: {
addUser: async (user) => {
const db = getDatabaseConnection();
const newUser = await db
type: user.type,
user_name: user.userName,
created_at: user.createdAt,
updated_at: user.updatedAt
return newUser;

This function accepts a user entity, and you must write that user to your database. Now, whenever the Collab Server needs to add a user to your database, it just needs to call this resolver!


All mutation resolvers must return the data that was written.

Default resolvers#

Writing these resolvers can be a little bit boring and error prone. As such, we provide a couple ways to automatically generate these resolvers for you.

If you are using our default database, then the resolvers are already created for you! You can get them with the getResolvers() function.

If you are using an SQL like database, you can use our SQL Resolver Generator package to generate resolvers for you!