Unified cache interface for EDA.
This module provides a simple API for accessing cached Discord data. Data is automatically cached as events are received from the Gateway.
Examples
# Get a guild
guild = EDA.Cache.get_guild("123456789")
# Get a user
user = EDA.Cache.get_user("987654321")
# Get a channel
channel = EDA.Cache.get_channel("111222333")
# Get the bot user
me = EDA.Cache.me()Storage backend
Where entities live is pluggable; which ones are kept and how many is not. The admission policy, the size limit and eviction, and the telemetry all sit above the backend, so any adapter inherits them:
config :eda, cache_adapter: EDA.Cache.Adapter.ETS # default
config :eda, cache_adapter: EDA.Cache.Adapter.NoOp # store nothingA backend only answers "where does this live", so it never has to reimplement the
filtering or the memory bounds. See EDA.Cache.Adapter to write your own.
Obfuscated channels
From 2026-11-16 Discord redacts channels the bot cannot view rather than hiding
them: they are still dispatched over the gateway, with name set to "___hidden___",
sensitive fields nulled, and the CHANNEL_OBFUSCATED flag set. Before that date the
behaviour is opt-in via config :eda, capabilities: [:channel_obfuscation].
EDA caches them like any other channel. They are dropped neither on write nor on read, because "this channel exists and I cannot see it" is real information — Discord explicitly expects apps that manage channels or compute permissions across a guild to detect them and surface that state. Hiding them in the cache would make a channel that demonstrably exists look absent.
Redaction nulls the sensitive fields rather than omitting them, which matters here:
EDA.Cache.Channel.update/2 merges the incoming payload over the cached one, so a channel
that becomes obfuscated has its name, topic, status and last_message_id genuinely
replaced — the previously cached values do not survive. Verified against a live guild on
2026-09-19. Redaction is also selective: position, parent_id, nsfw, bitrate and
rate_limit_per_user keep their real values.
The consequences are therefore yours to handle:
channels/0andchannels_for_guild/1include them — reject withEDA.Channel.obfuscated?/1before showing a channel list to a user;their
permission_overwriteshold a single synthetic@everyoneVIEW_CHANNELdeny.EDA.Permission.in_channel/3already refuses to compute from it and returns{:error, :channel_obfuscated}, so that path is safe;a cache admission policy can drop them if you would rather not see them at all:
config :eda, cache: [ channels: [policy: fn _entity, _key, channel -> if EDA.Channel.obfuscated?(channel), do: :skip, else: :cache end] ]
Summary
Functions
Returns the number of cached channels.
Gets all cached channels.
Gets all channels for a guild.
Fetches a channel from cache, falling back to REST on miss.
Fetches a guild from cache, falling back to REST on miss.
Fetches a member from cache, falling back to REST on miss.
Fetches a role from cache, falling back to REST on miss.
Fetches a user from cache, falling back to REST on miss.
Gets a channel by ID.
Gets a guild by ID.
Gets a member in a guild.
Gets a user's presence in a guild.
Gets a role by ID.
Gets a role by guild and role ID, without going through the role-to-guild index.
Gets a user by ID.
Gets a user's voice state in a guild.
Returns the number of cached guilds.
Gets all cached guilds.
Gets the current bot user as an %EDA.User{} struct.
Returns the raw map of the current bot user (string keys).
Returns the total number of cached members.
Gets all members for a guild.
Gets all presences for a guild.
Stores the current bot user. Saves both the parsed %EDA.User{} struct
and the raw map for backward compatibility.
Returns the total number of cached roles.
Gets all roles for a guild.
Returns the number of cached users.
Gets all cached users.
Gets all voice states for a specific channel in a guild.
Gets all voice states for a guild.
Functions
@spec channel_count() :: non_neg_integer()
Returns the number of cached channels.
@spec channels() :: [map()]
Gets all cached channels.
Includes channels Discord has obfuscated; see channels_for_guild/1.
Gets all channels for a guild.
Channels the bot cannot view are included, with their metadata redacted by
Discord (name is "___hidden___"). Filter them out when listing channels for
a user:
guild_id
|> EDA.Cache.channels_for_guild()
|> Enum.reject(&EDA.Channel.obfuscated?/1)See the "Obfuscated channels" section of EDA.Cache for why they are kept.
Fetches a channel from cache, falling back to REST on miss.
Fetches a guild from cache, falling back to REST on miss.
@spec fetch_member(String.t() | integer(), String.t() | integer()) :: {:ok, map()} | {:error, term()}
Fetches a member from cache, falling back to REST on miss.
Fetches a role from cache, falling back to REST on miss.
Note: the REST API returns all roles for the guild, so guild_id is required.
Fetches a user from cache, falling back to REST on miss.
Gets a channel by ID.
Gets a guild by ID.
Gets a member in a guild.
Gets a user's presence in a guild.
Gets a role by ID.
Gets a role by guild and role ID, without going through the role-to-guild index.
Gets a user by ID.
Gets a user's voice state in a guild.
@spec guild_count() :: non_neg_integer()
Returns the number of cached guilds.
@spec guilds() :: [map()]
Gets all cached guilds.
@spec me() :: EDA.User.t() | map() | nil
Gets the current bot user as an %EDA.User{} struct.
Returns nil if the bot hasn't connected yet.
The raw map is also stored for internal callers that need string-key access.
@spec me_raw() :: map() | nil
Returns the raw map of the current bot user (string keys).
Used internally by code that expects me["id"] string-key access
(e.g., app_id/0, voice event routing).
@spec member_count() :: non_neg_integer()
Returns the total number of cached members.
Gets all members for a guild.
Gets all presences for a guild.
@spec put_me(map()) :: :ok
Stores the current bot user. Saves both the parsed %EDA.User{} struct
and the raw map for backward compatibility.
@spec role_count() :: non_neg_integer()
Returns the total number of cached roles.
Gets all roles for a guild.
@spec user_count() :: non_neg_integer()
Returns the number of cached users.
@spec users() :: [map()]
Gets all cached users.
Gets all voice states for a specific channel in a guild.
Gets all voice states for a guild.