REST API endpoints for Discord guild roles.
All functions return {:ok, result} or {:error, reason}.
Summary
Functions
Creates a role in a guild.
Deletes a guild role.
Gets roles for a guild.
Gets the number of members carrying each role in a guild.
Modifies a guild role.
Modifies guild role positions.
Sets a role's colours.
Functions
@spec create(String.t() | integer(), keyword() | map(), keyword()) :: {:ok, map()} | {:error, term()}
Creates a role in a guild.
Deletes a guild role.
Gets roles for a guild.
@spec member_counts(String.t() | integer()) :: {:ok, %{required(String.t()) => non_neg_integer()}} | {:error, term()}
Gets the number of members carrying each role in a guild.
Returns a map of role ID to member count.
Two things to know, both confirmed against a live guild (2026-09-19):
- the
@everyonerole is absent from the result — its ID equals the guild ID, and every member carries it, so Discord omits it. Expect one fewer entry thanlist/1returns; - the counts overlap. A member holding three roles is counted in all three, so
the values sum to more than the guild's
member_count(2739 against 528 on the guild probed).
Why not count from the cache?
The traditional approach in other libraries is to filter cached members —
discord.js's Role.members, JDA's getMembersWithRoles/1. EDA can do the same with
EDA.Cache.members/1, but that needs the privileged :guild_members intent and a
fully chunked member cache (config :eda, chunk_members: true). This endpoint needs
neither.
Measured against a live guild with a complete member cache (2026-09-19), the two agree exactly: 0 divergences over 56 roles, 2739 assignments either way. But counting from the cache saw only 52 roles against the endpoint's 56 — roles with zero members are absent from a cache-derived tally and present here. If you are listing every role with its count, that difference is the whole point.
discord.js exposes the same endpoint as guild.roles.fetchMemberCounts() and documents
the same @everyone exclusion.
Examples
{:ok, counts} = EDA.API.Role.member_counts(guild_id)
#=> {:ok, %{"938496731396599808" => 179, "964090281647542342" => 272}}
Map.get(counts, role_id, 0)
@spec modify(String.t() | integer(), String.t() | integer(), map(), keyword()) :: {:ok, map()} | {:error, term()}
Modifies a guild role.
Modifies guild role positions.
@spec set_colors( String.t() | integer(), String.t() | integer(), EDA.Role.Colors.t() | map(), keyword() ) :: {:ok, map()} | {:error, term()}
Sets a role's colours.
Accepts an EDA.Role.Colors struct or a plain map. Sends only the colors object,
never the deprecated color field — matching discord.js, whose setColors builds
the same payload.
Discord enforces the holographic triple whenever tertiary_color is present, so
build that case with EDA.Role.Colors.holographic/0 rather than by hand.
Requires an eligible guild
Setting anything other than a solid colour returns HTTP 403 with code 670006,
"Missing guild feature" (EDA.Error.missing_guild_feature/0) on a guild that is not
eligible — observed on a boost-tier-0 guild, 2026-09-19. There is nothing to check
first: ENHANCED_ROLE_COLORS was absent from the features array of every guild
probed, including one that already had seven gradient roles. Attempt the call and
handle 670006.
Options
:reason— audit log reason
Examples
EDA.API.Role.set_colors(guild_id, role_id, EDA.Role.Colors.gradient(0xFF0000, 0x00FF00))
EDA.API.Role.set_colors(guild_id, role_id, EDA.Role.Colors.holographic(), reason: "event")
EDA.API.Role.set_colors(guild_id, role_id, %{primary_color: 0x5865F2})