Revolt Wiki

Welcome to Revolt's documentation. Everything you need to contribute to Revolt, build apps or bots, or to learn more about the project can be found here.

Learn more about:

You may also be interested in the:

Contribution Guide

This is the contribution guide for developers wanting to help out with Revolt.

Repository Lifecycle

Making Commits

  • Sign-off your commits (Git flag), read here about DCO obligations.
  • Sign commits where possible, learn more about that here.
  • Commit using Conventional Commit style.
  • Use prettier in relevant repositories using Typescript, use cargo fmt in those using Rust. Note: PRs should only format files that have been changed to avoid conflicts.
  • Try to keep each PR bound to a single feature or change, multiple bug fixes may be fine in some cases. This is to avoid your PR getting stuck due to parts of it having conflicts or other issues.

Merging Pull Requests

To keep commit history nice and tidy, always use Conventional Commit style for any merge commit messages. And where possible:

  1. Squash and Merge for bug fixes / small features, especially if the fix has multiple iterations, example, or if the commits don't follow conventional commit style.

Beyond this point you should usually check with a maintainer on how to merge, and should only proceed with the following if the commits follow Conventional Commit style:

  1. Prefer to Rebase and Merge where possible.
  2. Create a merge request with commit message similar to merge: remote-tracking branch abc into xyz (#1).

What can I help with?

The main project board can serve as a helpful starting point:

  1. If you are new to the code base or are looking for issues we really need help with, look at "What can I help with?"
  2. Issue Board "Free Issues": issues that anyone can pick up and are generally free to work on
  3. Issue Board "Todo": these are issues that are probably fine to pick up, but please ask first since a lot of these tend to be complicated and potentially already planned
  4. Working on new issues and fixes: ideally you should run new features by us, most fixes are probably going to be alright though, we wouldn't want to reject any PRs that we don't deem suitable after work has already been done. If it's a fix, make sure to make an issue for it first, if it's a new feature, it may be better suited in Feature Suggestions

Any issues marked with "Future Work" or with a milestone greater than the current milestone are out of bounds and should not be worked on since it's likely that the team already has a plan in place, any work you may do may conflict with prior ideas, and your work may potentially be rejected if it does fit the criteria exactly. In general, these issues are just postponed to reduce long term technical debt, i.e. allow current issues to be handled.

Project Guidance

Please read the additional relevant guidance on:

Other ways to help

You can contribute to Revolt in a variety of ways:

1. Feedback

The easiest, but most important, way to contribute to Revolt is to voice your opinion and give us feedback. We want to hear what you think and appreciate and await your feature suggestions, bug reports and general opinions on everything Revolt has to offer.

Within the Revolt app, you can navigate to the feedback tab, or you can open an issue on the relevant GitHub repo.

2. Translate

Revolt is used by users all around the world; as such, it's more accessible if the user interface is available in a variety of languages. You can contribute translations through Weblate.

3. Donate

Revolt is not backed by a big company, is not currently monetised (for example, via a subscription service) and does not serve you advertisements; as such, Revolt currently relies entirely on donations. You can learn more about donating here - if you want to make a larger donation, please consult me first.

Frequently Asked Questions

This page includes several frequently asked questions and explanations.

All of these answers are written from the perspective of the project owner.

Last Update: 10th May 2024

Why another project?

I think this is best explained with a bit of history:

  • Revolt started as a passion project
  • It grew way beyond any of our expectations
  • We might as well keep going since there is an interest in this space

Beyond that:

  • Revolt has been a great learning experience, including development, management, and running the infrastructure for a large project. Revolt has also taught me a lot about different concepts and programming languages, and really, that's how developers learn. We make cool projects to try to better how we work, it doesn't matter if someone has done it before as long as you can attempt to do the same. Reinventing the wheel is part of the process.
  • At the time, there was also a relative void of competition in this specific genre of chat platforms. There were Guilded, Discord, and Matrix but these are all either closed-source or cater to a different audience.

PS. I've had a few people say, 'why not just contribute to X?', the answer is quite simple, I just didn't know about any of these projects (i.e. Matrix).

How are we funded?

Revolt is entirely funded through donations, we have amassed a significant amount of money from donations alone. (financial transparency reports coming soon :tm:)

The month-to-month income of Revolt covers our operational costs and leaves enough spare to cover yearly expenses and the occassional one-time expense, such as for additional hardware.

We have monetisation plans lined up for the future, however it is not our intention to paywall existing features, instead where possible we intend to pass down costs such as for file storage or voice bandwidth.

'X' feature when?

Please take a look at our roadmap on GitHub.

If you find something is not listed, it could be being worked on somewhere or have been requested as a feature.

Does Revolt have federation?

As of right now, Revolt does not feature any federation and it is not in our feature roadmap.

However, this does not necessarily mean federation is off the table, possible avenues are:

  • Implement our own federation protocol
  • Implement a promising up and coming federation protocol, polyproto
  • Implement the Matrix protocol (unlikely, obtuse and unstable)
  • Implement the XMPP protocol (battle-tested and stable)

Any federation that is implemented MUST exercise caution in:

  • Preventing spam and abuse: moderators should be able to block malicious actors
  • Protecting user data: users should be able to redact all of their information and messages

What can I do with Revolt and how do I self-host?

In general:

  • The Revolt branding is used to represent the platform,
  • You may use the branding as-is to promote the platform and your community on the platform.
  • You should not use the branding in order to appear as if you are associated with the Revolt team.
  • Please make explicit distinctions between Revolt (the platform, "") and the Revolt software.
  • The Revolt software is unbranded and only associated by name.

If you have any concerns or questions, please liase with us at

As a third-party platform:

  • You must provide correct attribution in line with our software licenses:

    If you are using official images (GitHub Packages / Docker Hub), attribution is included. If you are modifying the software and using it in production, you must publish the changes to the source publicly in line with AGPLv3. (In addition to providing attribution back to the original project.)

  • You are solely responsible for whatever happens on your third party instance, we provide no warranty or liability for what happens on 3rd party instances.

  • You must not appear to associate with Revolt / unless if granted explicit written permission. In regards to custom clients, provide a warning of any potential risks or clear it with us.

  • You may not use any of the Revolt branding or brand assets to advertise or promote your third party instance.

You can self-host Revolt by:

Can you verify my server/bot?

Currently, you can only apply to verify servers given that you have a valid reason to believe verification is necessary for your community. Verification is intended to provide protection for server owners from copy cats and to provide authenticity to users as such we are not just giving it out to anyone because that would defeat the purpose.

However if you would like to get a server verified, you should satisfy one of the following criteria:

  • Official community for a well-established open source project

  • Official community for any other well-established product, service, or person

  • Large and active distinct pre-existing community

    Distinct means the community is unique and well-known (& has an active presence) off platform. This means we are not currently verifying generic servers that centre around a topic unless if it meets one of the first two criteria. Though in special circumstances, well known on platform communities may also be considered.

Server verification also comes with a vanity invite, so please have one ready if you want to apply. To apply, drop an email at

We also periodically prune verification from servers that have fallen into disrepair and / or otherwise are no longer active.

For questions about the Revolt platform, you may want to go to our knowledge base:


You can connect to the API on the following URLs:

https://api.revolt.chatProductionPrimary API endpoint endpoint for old client endpoint for new client

You can connect to the events server on the following URLs:

wss://ws.revolt.chatProductionPrimary events endpoint
wss:// endpoint for old client
wss:// endpoint for new client


To authenticate with the API, you must first acquire a bot token or user token:

  • Bot: create one from user settings in the client
  • User: copy one from client or authenticate manually through API

Then you may provide these through either:


When dealing with an authenticated route.

Rate Limits

Revolt uses a fixed-window ratelimiting algorithm:

  • You are given a set amount of calls per each named bucket.
  • Any calls past this limit will result in 429 errors.
  • Buckets are replenished after 10 seconds from initial request.


There are distinct buckets that you may be calling against, none of these affect each other and can be used up independently of one another.



There are multiple headers you can use to figure out when you can and cannot send requests, and to determine when you can next send a request.

X-RateLimit-LimitnumberMaximum number of calls allowed for this bucket.
X-RateLimit-BucketstringUnique identifier for this bucket.
X-RateLimit-RemainingnumberRemaining number of calls left for this bucket.
X-RateLimit-Reset-AfternumberMilliseconds left until calls are replenished.

Rate Limited Response

When you receive 429 Too Many Requests, you will also receive a JSON body with the schema:

interface Response {
  // Milliseconds until calls are replenished
  retry_after: number;


Revolt's permission system works by sequentially applying allows then denies.

Flow Chart

Below are the high-level steps taken to determine both server and channel permissions (click to view).

If you are looking to implement permissions in a library, I highly recommend reading either revolt.js or delta source code since all the routines are well commented and should be relatively easy to understand.


The following permissions are currently allocated:

ManageChannel11 << 0Manage the channel or channels on the server
ManageServer21 << 1Manage the server
ManagePermissions41 << 2Manage permissions on servers or channels
ManageRole81 << 3Manage roles on server
ManageCustomisation161 << 4Manage emoji on servers
KickMembers641 << 6Kick other members below their ranking
BanMembers1281 << 7Ban other members below their ranking
TimeoutMembers2561 << 8Timeout other members below their ranking
AssignRoles5121 << 9Assign roles to members below their ranking
ChangeNickname10241 << 10Change own nickname
ManageNicknames20481 << 11Change or remove other's nicknames below their ranking
ChangeAvatar40961 << 12Change own avatar
RemoveAvatars81921 << 13Remove other's avatars below their ranking
ViewChannel10485761 << 20View a channel
ReadMessageHistory20971521 << 21Read a channel's past message history
SendMessage41943041 << 22Send a message in a channel
ManageMessages83886081 << 23Delete messages in a channel
ManageWebhooks167772161 << 24Manage webhook entries on a channel
InviteOthers335544321 << 25Create invites to this channel
SendEmbeds671088641 << 26Send embedded content in this channel
UploadFiles1342177281 << 27Send attachments and media in this channel
Masquerade2684354561 << 28Masquerade messages using custom nickname and avatar
React5368709121 << 29React to messages with emojis
Connect10737418241 << 30Connect to a voice channel
Speak21474836481 << 31Speak in a voice call
Video42949672961 << 32Share video in a voice call
MuteMembers85899345921 << 33Mute other members with lower ranking in a voice call
DeafenMembers171798691841 << 34Deafen other members with lower ranking in a voice call
MoveMembers343597383681 << 35Move members between voice channels

Uploading Files

File uploads work by first sending a file to the server and then using the ID provided.

You can find out what kinds of files you can upload by visiting This may depend on your instance, so you should determine the endpoint from the root API response.

To upload a file, pick the desired tag then send a POST to {endpoint}/{tag} along with a multipart/form-data body with one field file that contains the file you wish to upload.

You will receive the following JSON response:

  "id": "0"

You can use the ID wherever a file is required in the API.

Code sample in JavaScript using Fetch API:

const body = new FormData();
body.append("file", file);

const data = await fetch(`${endpoint}/${tag}`, {
  method: "POST",
}).then((res) => res.json());

// use

Serving images

For caching purposes, use the following URL templates for file previews:


Parameters may be forced in the future. Missing URLs to be added.


You can find the API changelog on the releases page on GitHub!

Legacy Changelog

Gap in data

This is missing stuff between the one above and the one below.

20230120-1: New Account Events

This update adds two new events:

  • UserPlatformWipe { user_id: String; flags: Int; }
  • Auth { event_type: 'DeleteSession' | 'DeleteAllSessions'; [..] } (see Auth)

It also adds a new user flag of value 8 which represents a user who has been flagged and removed as spam.

It also adds one REST routes:

  • GET /users/<user_id>/flags: Fetch user flags

If your account is disabled, login will no longer throw an error, instead it will return Disabled { user_id: String } with status code 200.

You must now also specify a list of reactions when enabling restrict reactions.

20220903-1: Changes to role colours, masquerades, members and user timeouts

Role colours now support most valid CSS gradients and colours up to 128 characters and which satisfy the following Regex:

(?i)^(?:[a-z ]+|var\(--[a-z\d-]+\)|rgba?\([\d, ]+\)|#[a-f0-9]+|(repeating-)?(linear|conic|radial)-gradient\(([a-z ]+|var\(--[a-z\d-]+\)|rgba?\([\d, ]+\)|#[a-f0-9]+|\d+deg)([ ]+(\d{1,3}%|0))?(,[ ]*([a-z ]+|var\(--[a-z\d-]+\)|rgba?\([\d, ]+\)|#[a-f0-9]+)([ ]+(\d{1,3}%|0))?)+\))$

You can now also masquerade role colours per-message, simply include the colour property matching the properties above.

All members now include a joined_at property which indicate when the timestamp at which they joined a certain server.

All members now also have a timeout property which can be changed by PATCH /servers/<server_id>/members/<user_id>, users will not be able to interact with the server until the time expires. A visual indicator will also display on the user's end as well as for others in chat:

Timeout UI

20220901-1: Reactions Update

This update adds support for message reactions, including the following REST routes:

  • PUT /channels/<id>/messages/<id>/reactions/<emoji>: Add reaction to message
  • DELETE /channels/<id>/messages/<id>/reactions/<emoji>: Remove reaction from message
  • DELETE /channels/<id>/messages/<id>/reactions: Remove all reaction from a message

It adds a new permission React with value 536870912.

It adds three new events:

  • MessageReact { id: String; channel_id: String; user_id: String; emoji_id: String; }
  • MessageUnreact { id: String; channel_id: String; user_id: String; emoji_id: String; }
  • MessageRemoveReaction { id: String; channel_id: String; emoji_id: String; }

20220707-1: Emoji Update

This update adds support for server emojis, including the following REST routes:

  • GET /custom/emoji/<id>: Get an existing emoji
  • PUT /custom/emoji/<id>: Create a new emoji (uses Autumn id)
  • DELETE /custom/emoji/<id>: Delete an emoji
  • GET /server/<id>/emojis: Fetch all emoji in a server

It adds a new permission ManageCustomisation with value 8.

It includes a new field in the Ready payload: emojis?: Emoji[].

It adds two new events:

  • EmojiCreate(Emoji)
  • EmojiDelete { id: String }

20220608-1: Friends API v2

To accomodate the new Unicode usernames, the existing add friend route is being split into two different routes:

  • PUT /users/{target}/friend: Accept friend request where {target} is an ID
  • POST /users/friend: Send friend request (with body { username: string })

The old route will stop accepting usernames in a week, see 20220608-2.

20220608-2: 'Send Friend' Deprecation

The PUT /users/{target}/friend route will stop accepting usernames in place of {target}.

Establishing a connection

To get started, you should have:

  • A WebSocket URL, which is found from the API root.
  • A valid session or bot token.

You may authenticate in one of two ways:

You should listen out for Error events to find out if your credentials are incorrect or if something goes wrong here.

After authenticating, the server will respond with Authenticated then it will send a Ready event containing useful data.

The server will now start sending relevant events as they come in.

You should Ping the server every 10 to 30 seconds to prevent your connection being dropped.

Bots receive all events, normal users do not receive UserUpdate events fanned out through servers by default, read more here.

Query Parameters

The Bonfire service supports additional query parameters:

versionDescribes the protocol version in use.1No †
formatIn what format to send packets, default is JSON.json, msgpackNo
tokenSession token for authenticating the connecting user.No

version may become compulsary in the future, please set it to 1 if you can.

You may specify these in the connection URL: wss://


This page documents various incoming and outgoing events.

Help Wanted: we should adopt AsyncAPI to properly document the protocol!

Client to Server


Authenticate with Revolt.

  "type": "Authenticate",
  "token": "{token}"


Tell other users that you have begun typing in a channel.

Must be in the specified channel or nothing will happen.

  "type": "BeginTyping",
  "channel": "{channel_id}"


Tell other users that you have stopped typing in a channel.

Must be in the specified channel or nothing will happen.

  "type": "EndTyping",
  "channel": "{channel_id}"


Ping the server, you can specify a timestamp that you'll receive back.

  "type": "Ping",
  "data": 0


Subscribe to a server's UserUpdate events.

  "type": "Subscribe",
  "server_id": "{server_id}"

Implementation notes:

  • Subscriptions automatically expire within 15 minutes.
  • A client may have up to 5 active subscriptions.
  • This has no effect on bot sessions.
  • This event should only be sent iff app/client is in focus.
  • You should aim to send this event at most every 10 minutes per server.

Server to Client


An error occurred which meant you couldn't authenticate.

  "type": "Error",
  "error": "{error_id}"

The {error_id} can be one of the following:

  • LabelMe: uncategorised error
  • InternalError: the server ran into an issue
  • InvalidSession: authentication details are incorrect
  • OnboardingNotFinished: user has not chosen a username
  • AlreadyAuthenticated: this connection is already authenticated


The server has authenticated your connection and you will shortly start receiving data.

  "type": "Authenticated"

Logged Out

The current user session has been invalidated or the bot token has been reset.

  "type": "Logout"

Your connection will be closed shortly after.


Several events have been sent, process each item of v as its own event.

    "type": "Bulk",
    "v": [...]


Ping response from the server.

  "type": "Pong",
  "data": 0


Data for use by client, data structures match the API specification.

    "type": "Ready",
    "users": [{..}],
    "servers": [{..}],
    "channels": [{..}],
    "emojis": [{..}]


Message received, the event object has the same schema as the Message object in the API with the addition of an event type.

    "type": "Message",


Message edited or otherwise updated.

    "type": "MessageUpdate",
    "id": "{message_id}",
    "channel": "{channel_id}",
    "data": {..}
  • data field contains a partial Message object.


Message has data being appended to it.

    "type": "MessageAppend",
    "id": "{message_id}",
    "channel": "{channel_id}",
    "append": {
        "embeds"?: [...]


Message has been deleted.

  "type": "MessageDelete",
  "id": "{message_id}",
  "channel": "{channel_id}"


A reaction has been added to a message.

  "type": "MessageReact",
  "id": "{message_id}",
  "channel_id": "{channel_id}",
  "user_id": "{user_id}",
  "emoji_id": "{emoji_id}"


A reaction has been removed from a message.

  "type": "MessageUnreact",
  "id": "{message_id}",
  "channel_id": "{channel_id}",
  "user_id": "{user_id}",
  "emoji_id": "{emoji_id}"


A certain reaction has been removed from the message.

  "type": "MessageRemoveReaction",
  "id": "{message_id}",
  "channel_id": "{channel_id}",
  "emoji_id": "{emoji_id}"


Channel created, the event object has the same schema as the Channel object in the API with the addition of an event type.

    "type": "ChannelCreate",


Channel details updated.

    "type": "ChannelUpdate",
    "id": "{channel_id}",
    "data": {..},
    "clear": ["{field}", ...]
  • data field contains a partial Channel object.
  • {field} is a field to remove, one of:
    • Icon
    • Description


Channel has been deleted.

  "type": "ChannelDelete",
  "id": "{channel_id}"


A user has joined the group.

  "type": "ChannelGroupJoin",
  "id": "{channel_id}",
  "user": "{user_id}"


A user has left the group.

  "type": "ChannelGroupLeave",
  "id": "{channel_id}",
  "user": "{user_id}"


A user has started typing in this channel.

  "type": "ChannelStartTyping",
  "id": "{channel_id}",
  "user": "{user_id}"


A user has stopped typing in this channel.

  "type": "ChannelStopTyping",
  "id": "{channel_id}",
  "user": "{user_id}"


You have acknowledged new messages in this channel up to this message ID.

  "type": "ChannelAck",
  "id": "{channel_id}",
  "user": "{user_id}",
  "message_id": "{message_id}"


Server created, the event object has the same schema as the SERVER object in the API with the addition of an event type.

    "type": "ServerCreate",


Server details updated.

    "type": "ServerUpdate",
    "id": "{server_id}",
    "data": {..},
    "clear": ["{field}", ...]
  • data field contains a partial Server object.
  • {field} is a field to remove, one of:
    • Icon
    • Banner
    • Description


Server has been deleted.

  "type": "ServerDelete",
  "id": "{server_id}"


Server member details updated.

    "type": "ServerMemberUpdate",
    "id": {
        "server": "{server_id}",
        "user": "{user_id}"
    "data": {..},
    "clear": ["{field}", ...]
  • data field contains a partial Server Member object.
  • {field} is a field to remove, one of:
    • Nickname
    • Avatar


A user has joined the server.

  "type": "ServerMemberJoin",
  "id": "{server_id}",
  "user": "{user_id}"


A user has left the server.

  "type": "ServerMemberLeave",
  "id": "{server_id}",
  "user": "{user_id}"


Server role has been updated or created.

    "type": "ServerRoleUpdate",
    "id": "{server_id}",
    "role_id": "{role_id}",
    "data": {..},
    "clear": ["{field}", ...]
  • data field contains a partial Server Role object.
  • clear is a field to remove, one of:
    • Colour


Server role has been deleted.

  "type": "ServerRoleDelete",
  "id": "{server_id}",
  "role_id": "{role_id}"


User has been updated.

    "type": "UserUpdate",
    "id": "{user_id}",
    "data": {..},
    "clear": ["{field}", ...]
  • data field contains a partial User object.
  • clear is a field to remove, one of:
    • ProfileContent
    • ProfileBackground
    • StatusText
    • Avatar
    • DisplayName


Your relationship with another user has changed.

  "type": "UserRelationship",
  "id": "{your_user_id}",
  "user": "{..}",
  "status": "{status}"
  • user field contains a User object.
  • status field matches Relationship Status in API.


User has been platform banned or deleted their account.

Clients should remove the following associated data:

  • Messages
  • DM Channels
  • Relationships
  • Server Memberships

User flags are specified to explain why a wipe is occurring though not all reasons will necessarily ever appear.

  "type": "UserPlatformWipe",
  "user_id": "{user_id}",
  "flags": "{user_flags}"


Emoji created, the event object has the same schema as the Emoji object in the API with the addition of an event type.

  "type": "EmojiCreate",


Emoji has been deleted.

  "type": "EmojiDelete",
  "id": "{emoji_id}"


Forwarded events from Authifier, currently only session deletion events are forwarded.

  "type": "Auth",
  "event_type": "{event_type}",

Event type may be defined as one of the following with the additional properties:


A session has been deleted.

  "event_type": "DeleteSession",
  "user_id": "{user_id}",
  "session_id": "{session_id}"


All sessions for this account have been deleted, optionally excluding a given ID.

  "event_type": "DeleteAllSessions",
  "user_id": "{user_id}",
  "exclude_session_id": "{session_id}"


The following libraries are provided by the Revolt team:

You can find a host of community created libraries here.

Plugin API


This page documents the old Revite Plugin API (manifest v1), it will be replaced in the new client.


The Plugin API is very powerful. Tread carefully.

Zero guarantees or sandboxes are provided. Your code is run as-is.

This document details the very experimental plugin API available in Revite.

This is more or less a proof of concept but can be used to achieve some simple client modifications.

Plugin Manifest

Below is the specification for revision 1 of the plugin API. The format parameter is not currently enforced but you should set it to 1 to avoid future breakage.

type Plugin = {
   * Plugin Format Revision
  format: 1;

   * Semver Version String
   * This is the version of the plugin.
  version: string;

   * Plugin Namespace
   * This will usually be the author's name.
  namespace: string;

   * Plugin Id
   * This should be a valid URL slug, e.g. cool-plugin.
  id: string;

   * Entrypoint
   * Valid Javascript code. It must be a function which returns a object.
   * ```typescript
   * function (state: State) {
   *   return {
   *     onUnload: () => {}
   *   }
   * }
   * ```
  entrypoint: string;

   * Whether this plugin is enabled.
   * @default true
  enabled?: boolean;

An example plugin:

    format: 1,
    version: "0.0.1",
    namespace: "insert",
    id: "my-plugin",
    entrypoint: `(state) => {
        console.log('[my-plugin] Plugin init!');
        return {
            onUnload: () => console.log('[my-plugin] bye!')

Using the Plugin API

To begin, you can load plugins using the global plugin manager at state.plugins.

Open the developer console and run:

state.plugins.load({ ... });
// ...where [...] is your plugin manifest as described above.

Plugin API

A plugin's entrypoint is required to return an object which is referred to as the instance:

interface Instance {
  onUnload?: () => void;

The Plugin API (state.plugins) exposes the following methods:

interface PluginAPI {
   * Add a plugin
   * @param plugin Plugin Manifest
  add(plugin: Plugin);

   * Remove a plugin
   * @param namespace Plugin Namespace
   * @param id Plugin Id
  remove(namespace: string, id: string);

   * Load a plugin
   * @param namespace Plugin Namespace
   * @param id Plugin Id
  load(namespace: string, id: string);

   * Unload a plugin
   * @param namespace Plugin Namespace
   * @param id Plugin Id
  unload(namespace: string, id: string);

   * Reset everything