Skip to content

Instantly share code, notes, and snippets.

@lcsmuller
Last active June 12, 2026 00:45
Show Gist options
  • Select an option

  • Save lcsmuller/8a60b7ccfd9abd4a7f5d8b723f6a2857 to your computer and use it in GitHub Desktop.

Select an option

Save lcsmuller/8a60b7ccfd9abd4a7f5d8b723f6a2857 to your computer and use it in GitHub Desktop.

Migrating to Concord 3.0.0

This guide explains the key changes in Concord 3.0.0 and provides instructions for migrating from earlier versions.

Breaking Changes

Voice Connections Removed

Voice connection functionality has been completely removed from Concord 3.0.0.

Old behavior:

// Previously, you could enable voice connection like this
discord_voice_join(client, guild_id, channel_id);

New behavior:

// Voice connections are no longer supported natively
// Use CogLink instead: https://github.com/PerformanC/CogLink

For applications that require voice connections, we recommend using CogLink, which is a separate project providing a LavaLink client designed to work with Concord.

Configuration Format Changes

The configuration format has been significantly restructured in 3.0.0.

Old format:

{
  "logging": { // logging directives
    "level": "trace",        // trace, debug, info, warn, error, fatal
    "filename": "bot.log",   // the log output file
    "quiet": false,          // change to true to disable logs in console
    "overwrite": true,       // overwrite file if already exists, append otherwise
    "use_color": true,       // display color for log entries
    "http": {
      "enable": true,        // generate http specific logging
      "filename": "http.log" // the HTTP log output file
    },
    "disable_modules": ["WEBSOCKETS", "USER_AGENT"] // disable logging for these modules
  },
  "discord": { // discord directives
    "token": "YOUR-BOT-TOKEN",         // replace with your bot token
    "default_prefix": {                 
      "enable": false,                 // enable default command prefix
      "prefix": "YOUR-COMMANDS-PREFIX" // replace with your prefix
    }
  }
}

New format:

{
  "token": "YOUR-BOT-TOKEN", // replace with your bot token
  "log": {                   // logging directives
    "level": "TRACE",           // TRACE, DEBUG, INFO, WARN, ERROR, FATAL
    "trace": "bot.log",         // the log output file (null to disable)
    "quiet": false,             // true to disable logs in console
    "overwrite": true,          // true overwrites the file on each run
    "color": true,              // display color on console
    "http": "http.log",         // the HTTP log output file (null to disable)
    "ws": "ws.log",             // the WebSockets log output file (null to disable)
    "disable": ["WEBSOCKETS", "HTTP"] // disable logging for specific modules
  }
}

Key differences:

  • Bot token moved from discord.token to top-level token
  • Default prefix configuration (discord.default_prefix) removed
  • logging renamed to log
  • Log levels are now in UPPERCASE
  • filename renamed to trace
  • use_color renamed to color
  • http.enable and http.filename simplified to just http
  • New ws option for WebSockets logging
  • disable_modules renamed to disable

For more detailed information about the new configuration format, see config.json directives guide.

Logging System Change

The logging system has been transitioned from the old log library to the new logmod library.

Old behavior:

#include <concord/log.h>

// Old logging approach
log_info("Starting bot...");

New behavior:

#include <concord/logmod.h>

// New logging approach
logmod_log(INFO, NULL, "Starting bot...");

JSON Codec API Replaced (Reflect-C)

The gencodecs preprocessor pipeline has been replaced by Reflect-C reflection. The per-type JSON codec functions are gone; a single set of generic macros replaces them, and they now require your struct discord * client (the codecs run through the client's reflection registry).

Old behavior:

struct discord_user user = { 0 };

// returned a jsmn-style count, > 0 on success
if (discord_user_from_json(json, len, &user) > 0) {
    printf("%s\n", user.username);
}
discord_user_cleanup(&user);

char *buf = NULL; size_t size = 0;
discord_create_message_to_json(&buf, &size, &params);

New behavior:

struct discord_user user = { 0 };

// returns a CCORDcode, CCORD_OK (0) on success
if (CCORD_OK == discord_data_from_json(struct discord_user, client,
                                       json, len, &user)) {
    printf("%s\n", user.username);
}
discord_data_cleanup(client, &user);

char *buf = NULL; size_t size = 0;
discord_data_to_json(struct discord_create_message, client, &params,
                     &buf, &size);
discord_data_unwrap(client, &params); /* see ownership notes below */

Key differences:

  • discord_T_from_json(...)discord_data_from_json(struct T, client, ...)
  • discord_T_to_json(buf, size, data)discord_data_to_json(struct T, client, data, buf, size) (argument order: data first, buffer last)
  • discord_T_cleanup(&data)discord_data_cleanup(client, &data)
  • Return value semantics inverted: from_json used to return a positive count on success; it now returns a CCORDcode where success is CCORD_OK (zero). Code testing the old return with > 0 or truthiness must be updated.
  • A struct discord * client is now required for every codec call.

Ownership notes:

  • discord_data_cleanup() keeps the old _cleanup contract: every non-NULL pointer member is owned and freed. Never call it on hand-built structs pointing at string literals or stack objects.
  • New requirement: after discord_data_to_json() of a struct you own (e.g. stack-built params), call discord_data_unwrap(client, &params) once done. Encoding caches a reflection wrap keyed by the struct's address; unwrap releases it without touching your data. Decoded structs don't need this — discord_data_cleanup() releases both the data and the wrap.

Behavioral fixes that come with the new codecs (no code changes needed, but previously-broken fields now work):

  • discord_channel.voice_quality_mode now decodes (the wire key is video_quality_mode; it never populated before)
  • discord_role_tag.premium_subscribe now serializes under its real wire key premium_subscriber

Client Initialization and Shutdown

The client constructors have been renamed; compatibility macros keep old source code compiling, but new code should use the new names.

Old behavior:

struct discord *client = discord_init(TOKEN);
struct discord *client = discord_config_init("config.json");

New behavior:

struct discord *client = discord_from_token(TOKEN);
struct discord *client = discord_from_json("config.json");

// new: programmatic configuration, no config file needed
struct discord *client = discord_from_config(&(struct discord_config){
    .token = TOKEN,
});

discord_init and discord_config_init remain as #define aliases, so existing code recompiles — but they are gone as symbols, which breaks shared-library (libdiscord.so) users until recompiled.

Multi-client shutdown replaces the old global shutdown helpers:

Old New
ccord_shutdown_async() discord_shutdown_all() (compat macro kept)
ccord_shutting_down() discord_shutdown_all_ongoing() (no compat macro)
discord_dup_shutdown_fd() ccord_notifier_* API in <concord/concord-notifier.h>

Dynamic Embed Helpers Removed

The discord_embed_* builder helpers (discord_embed_set_title(), discord_embed_set_description(), discord_embed_set_footer(), discord_embed_set_image(), discord_embed_add_field(), etc.) have been removed. Populate the embed structures directly instead:

Old behavior:

struct discord_embed embed = { .color = 3447003 };
discord_embed_set_title(&embed, "Title");
discord_embed_add_field(&embed, "Field", "Value", true);

New behavior:

struct discord_embed embed = {
    .color = 3447003,
    .title = "Title",
    .fields = &(struct discord_embed_fields){
        .size = 1,
        .array = &(struct discord_embed_field){
            .name = "Field", .value = "Value", .Inline = true,
        },
    },
};

Designated initializers with compound literals (as the examples in examples/embed.c show) replace the dynamic builders; nothing is heap-allocated, so there is nothing to free afterwards.

Removed Deprecated Wrappers

Function wrappers that were already deprecated in v2 (or wrapped endpoints Discord has removed) are gone:

Removed Use instead
discord_set_presence() discord_update_presence()
discord_modify_current_user_nick() discord_modify_current_member()
discord_set_on_wakeup() / discord_set_next_wakeup() the discord_timer() / discord_timer_interval() API
discord_list_active_threads() removed (Discord dropped the endpoint)
discord_return_error() return CCORDcode values; see discord_strerror() / discord_code_as_string()
discord_get_logconf() discord_get_logmod() (returns the new logmod handle)

No More Runtime Aborts

In v2, several misuse paths (invalid parameters, allocation failures) called abort(). These now return CCORDcode errors instead — check return values where you previously relied on the library crashing loudly.

Build System Changes

Reflect-C Submodule Required

Codecs are generated at build time through the reflect-c git submodule into the untracked generated/ directory.

Old behavior:

git clone https://github.com/Cogmasters/concord.git
cd concord && make

New behavior:

git clone --recurse-submodules https://github.com/Cogmasters/concord.git
cd concord && make
# or, for an existing clone:
git submodule update --init reflect-c

The top-level make initializes the submodule and regenerates codecs automatically; make reflectc-gen regenerates them explicitly. When cross-compiling, the generator tools must run on the host:

make reflectc-gen HOSTCC=gcc
make CC=aarch64-linux-gnu-gcc

(replacing the old cd gencodecs && make HOSTCC=... CC=... flow)

libcurl with WebSockets Support

Concord 3.0.0 now requires libcurl 8.7.1 or higher compiled with WebSockets support.

Old requirement:

Any libcurl version was generally acceptable

New requirement:

libcurl 8.7.1 or higher WITH --enable-websockets flag

Makefile Changes

The voice target has been removed from the Makefile.

Old behavior:

# This would compile with voice support
make voice

New behavior:

# Voice targets removed
# Regular build is now sufficient
make

Package Configuration

A new PKGCONFIGDIR variable has been added to the Makefile for installing the concord.pc file.

Old behavior:

# No specific package config directory
make install

New behavior:

# Specify a custom package config directory
PKGCONFIGDIR=/usr/local/lib/pkgconfig make install

Migration Steps

Follow these steps to migrate your application to Concord 3.0.0:

  1. Update dependencies:

    # Compile libcurl with WebSockets support
    git clone https://github.com/curl/curl.git
    cd curl
    ./buildconf
    ./configure --enable-websockets --with-openssl
    make -j$(nproc)
    sudo make install
  2. Update your code:

    • Remove any voice-related functionality
    • Replace all log library calls with logmod
    • Update header includes from <concord/log.h> to <concord/logmod.h>
    • Update your configuration file to the new format
    • Replace per-type codec calls (discord_T_from_json/_to_json/ _cleanup) with the discord_data_* macros, passing your client; check from_json results against CCORD_OK instead of > 0, and add discord_data_unwrap() after encoding structs you own
  3. Rebuild your application:

    # Fetch the reflect-c submodule (existing clones)
    git submodule update --init reflect-c
    # Rebuild with Concord 3.0.0
    make clean
    make

VS Code Integration

If you're using VS Code, the include paths have been updated in c_cpp_properties.json:

Old paths:

"includePath": [
    "${workspaceFolder}/**"
]

New paths:

"includePath": [
    "${workspaceFolder}/core",
    "${workspaceFolder}/generated",
    "${workspaceFolder}/reflect-c",
    "${workspaceFolder}/include"
]

Additional Resources

If you encounter any issues during migration, please open an issue on the GitHub repository.

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