Skip to content

Instantly share code, notes, and snippets.

@dolfies
Last active March 5, 2026 03:52
Show Gist options
  • Select an option

  • Save dolfies/7a2d589a60c4c3eba4cc2197be0f0b89 to your computer and use it in GitHub Desktop.

Select an option

Save dolfies/7a2d589a60c4c3eba4cc2197be0f0b89 to your computer and use it in GitHub Desktop.

Discord Custom Settings RFC

This document outlines a way for third-party Discord clients, plugins, or other modifications to persist custom settings, data, and user preferences to a given Discord account. Settings are to be persisted in a way where they do not interfere with the normal operation of the Discord client or with other third-party modifications. Settings will also support real-time updates through Discord's existing Real Time Gateway.

Design

This goal is accomplished through Discord's existing user settings API. The current implementation uses protobufs, with three distinct settings types:

export enum UserSettingsTypes {
  PRELOADED_USER_SETTINGS = 1,
  FRECENCY_AND_FAVORITES_SETTINGS = 2,
  TEST_SETTINGS = 3,
}

PRELOADED_USER_SETTINGS is used for settings that are preloaded when the user logs in, encompassing most traditional user settings. FRECENCY_AND_FAVORITES_SETTINGS is used for settings related to favorites and frecently used items, such as emojis and GIFs.

What we're interested in is the TEST_SETTINGS type, which was once used by Discord developers to test this new user settings system before release. This settings type is still present five years later, currently unused, and serves as a perfect candidate for third-party modifications to utilize for their custom settings.

Implementation

The following protobuf message defines the base shared message for the custom type:

syntax = "proto3";

message CustomUserSettings {
  message Versions {
    uint32 client_version = 1;
    uint32 server_version = 2;
    uint32 data_version = 3;
  }

  message SettingsEntry {
    bytes data = 1;
  }

  message ClientSettings {
    SettingsEntry shared = 1;
  }

  optional Versions versions = 1;
  optional ClientSettings settings = 2;
}

Considerations

Per Discord's implementation details, all protobuf settings must have a Versions message as the first field, defining the client_version, used to track the version of the client-side data structure, server_version, used to track the version of the server-side data structure, and data_version, used to track the version of the actual data being stored. client_version is currently not well-defined by this document, but may be used in the future. server_version is defined to be used by Discord to track the version of the server-side data structure, but has practically never been used, and should never be used in the TEST_SETTINGS type. data_version is the only required version field, and will be automatically incremented by the server every time the settings are updated.

Other than the Versions message shared across all protobuf settings, the remaining top-level fields are defined by the backend. The backend enforces that all top-level fields are messages, and only top-level fields that are defined in the backend are actually persisted. The TEST_SETTINGS type only has one other defined field, which we will repurpose to hold all our custom settings.

Usage

Generating Field Number

A project must first choose a unique identifier for their settings, which will be used to generate a unique field number for the SettingsEntry message to avoid conflicts with other projects. This will be done with a simple CRC32 hash, clamped to the valid protobuf field number range of 1 to 536,870,911.

Note that field 2 is reserved for shared settings, which are currently not defined by this document, but may be used in the future.

import zlib

MIN_FIELD_NUMBER = 2
MAX_FIELD_NUMBER = 536_870_911
RESERVED_START = 19_000
RESERVED_END = 19_999

def get_field_number(input: str) -> int:
    hash_val = zlib.crc32(input.encode("utf-8"))

    reserved_count = (RESERVED_END - RESERVED_START) + 1
    total_restricted = reserved_count + 1 
    valid_pool_size = MAX_FIELD_NUMBER - total_restricted

    field_num = (hash_val % valid_pool_size) + MIN_FIELD_NUMBER
    if field_num >= RESERVED_START:
        field_num += reserved_count

    return field_num

Extending Protobuf Message

Once a field number is generated, the shared base message can be extended to include a new field within the ClientSettings message with the generated field number, and the project can begin using that field to persist their custom settings data.

For example, for the fictional project dolfcord, we can first get our field number:

>>> get_field_number("dolfcord")
418868759

Then we can extend the base message like so:

syntax = "proto3";

message CustomUserSettings {
  message Versions {
    uint32 client_version = 1;
    uint32 server_version = 2;
    uint32 data_version = 3;
  }

  message SettingsEntry {
    bytes data = 1;
  }

  message ClientSettings {
    SettingsEntry shared = 1;
    SettingsEntry dolfcord = 418868759;
  }

  optional Versions versions = 1;
  optional ClientSettings settings = 2;
}

Note that your custom settings can utilize the existing SettingsEntry message, which simply wraps a bytes field, or a custom-defined message of your own design, as long as the top-level ClientSettings field is still a message.

Using the API

To retrieve settings, a client can call the GET /users/@me/settings-proto/3 endpoint, which will return an object with a single top-level field settings, which will be a base64-encoded string of the protobuf message defined above. The client can decode the base64 string, parse the protobuf message, and retrieve the custom settings from the appropriate field.

To modify settings, a similar process can be followed, but the client must then re-encode the protobuf message and send it back to the server with a PATCH /users/@me/settings-proto/3 request, with a JSON body containing a settings field, which is the base64-encoded string of the modified protobuf message. The server will then decode the protobuf message, persist the changes, and return the updated settings in the same format. When modifying, the server supports an additional field required_data_version, which can be included in the JSON body to ensure that the settings are only updated if the data_version of the settings on the server matches the provided required_data_version, used to prevent overwriting changes if the settings have been modified since they were last retrieved by the client.

When settings are modified, the Gateway will omit a USER_SETTINGS_PROTO_UPDATE event with the following structure:

{
    "partial": false,
    "settings": {
        "proto": "base64-encoded protobuf message",
        "type": 3
    }
}

This can be used by clients to listen for real-time updates to settings, and update their local state accordingly.

For more information on the API, see here.

Important

Discord's protobuf implementation requires that all fields are included within a top-level message when sending data back to the server, even if they are not being modified. This means that when modifying settings, the client must include all settings fields within the ClientSettings message, including ones from other projects, to avoid discarding settings. Take care to ensure your protobuf implementation supports handling unknown fields when parsing to avoid losing data from other projects.

Limitations

Try to keep data representations stored in these settings as compact as possible. Remember that this is a shared space, and larger settings will take up more bandwidth for both clients and servers.

For our purposes, these settings will be limited to 5 MiB of serialized base64-encoded protobuf data, which should be more than enough for typical use cases. If you find yourself using a significant portion of that limit, consider whether all the data you're storing is necessary, or find alternative ways to persist your data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment