Skip to content

API Reference

Every public name in cordless you build a bot with, with full signatures, defaults, and docstrings, generated straight from the installed package. For the Discord REST surface (bot.fetch_guild(), guild.ban(), and so on) see the REST API Reference; for the exception types see Errors. Task-oriented guides: Commands, Components, Modals & Select Menus, Embeds, and the CLI Reference.

Cordless(public_key: str | None = None)
bot.command(name: str, description: str | None = "No description provided.", options: Any = None,
defer: bool = False, dm_permission: bool = True,
default_member_permissions: Any = None, nsfw: bool = False, ephemeral: bool = False,
guild_ids: Any = None, user_installable: Union[bool, Literal["only"]] = False,
name_localizations: Any = None, description_localizations: Any = None,
group_description: str | None = None, group_name_localizations: Any = None,
group_description_localizations: Any = None, parent_name_localizations: Any = None)

Register a slash command.

Parameter
name 1-32 lowercase letters, digits, - or _; validated at decoration time. Use parent/sub or parent/group/sub paths for subcommands
description Shown in Discord’s command picker
options List of option dicts (build with option()). When omitted, options are inferred from the handler’s typed parameters: str, int, float, bool, Literal[...] for fixed choices, and Optional[T] / T | None (unwrapped to T; a default value is what makes the option optional)
defer Respond via the worker Lambda
dm_permission Set False to hide the command in DMs
default_member_permissions Permission bitfield members need to see the command
nsfw Restrict to age-verified channels
ephemeral Only meaningful with defer=True: makes the loading state and final reply private. For non-deferred commands, use ctx.send(ephemeral=True) instead
guild_ids Scope this command to specific guilds instead of registering it globally
user_installable True lets users install this command to their own account and run it in any server or DM, alongside the normal guild install. "only" drops the guild install, so it’s never a server-wide command, only usable by users who’ve installed it themselves
name_localizations {locale: name} dict for Discord’s per-locale command picker, e.g. {"es-ES": "comprar"}
description_localizations {locale: description} dict, same shape as name_localizations
group_description For a parent/group/sub path, the description shown on the auto-created group. Only the first subcommand registered under a given group needs to set it
group_name_localizations {locale: name} dict for the auto-created group’s name. Only the first subcommand registered under a given group needs to set it
group_description_localizations {locale: description} dict for the auto-created group’s description, same rule as group_name_localizations
parent_name_localizations For a parent/sub or parent/group/sub path, {locale: name} for the auto-created parent’s name. Doesn’t leak in from a subcommand’s own name_localizations, so set it explicitly; only the first subcommand registered needs to set it
bot.handler()

Returns the main Lambda entrypoint. Assign at module level in lambda_function.py: handler = bot.handler(). Wraps handle() plus keep-warm pings and @bot.cron dispatch.

bot.cron(schedule: str, name: str | None = None)

Register a scheduled handler; cordless deploy wires it to EventBridge.

schedule is an EventBridge expression, e.g. “rate(1 day)” or “cron(0 12 * * ? *)”. The handler takes no arguments.

bot.run_cron(name: str)

Run a registered @bot.cron handler by name, synchronously. Used by cordless cron NAME and the deployed EventBridge target; you don’t normally call this yourself.

bot.button(custom_id: str, defer: bool = False)

Register a handler for a button click. Prefix matching applies: custom_id="shop" also matches "shop:item1:2", with the suffix segments landing on ctx.custom_id_args.

bot.select(custom_id: str, defer: bool = False)

Register a handler for a select menu. Prefix matching applies: custom_id="shop" also matches "shop:item1:2", with the suffix segments landing on ctx.custom_id_args. Selected values are on ctx.values.

bot.modal(custom_id: str, defer: bool = False)

Register a handler for a modal submission. Prefix matching applies: custom_id="shop" also matches "shop:item1:2", with the suffix segments landing on ctx.custom_id_args. Submitted field values are on ctx.modal_values.

bot.user_command(name: str, dm_permission: bool = True, default_member_permissions: Any = None,
nsfw: bool = False, guild_ids: Any = None,
user_installable: Union[bool, Literal["only"]] = False,
name_localizations: Any = None)

Register a User context menu command (right-click → Apps → name).

name_localizations is a {locale: name} dict, e.g. {"es-ES": "inspeccionar"}

  • context menu commands have no description, so there is no description_localizations.
bot.message_command(name: str, dm_permission: bool = True,
default_member_permissions: Any = None, nsfw: bool = False,
guild_ids: Any = None,
user_installable: Union[bool, Literal["only"]] = False,
name_localizations: Any = None)

Register a Message context menu command (right-click message → Apps → name).

name_localizations is a {locale: name} dict, e.g. {"es-ES": "inspeccionar"}

  • context menu commands have no description, so there is no description_localizations.
bot.autocomplete(cmd_name: str, option_name: str | None)

Handler for an option marked autocomplete=True. Return a list of strings (filtered against the typed value for you) or choice dicts (sent as-is).

bot.route(method: str, path: str)

Register a raw HTTP handler on the same Lambda, outside the Discord interaction flow.

Use it for requests that must reach this function without Discord signature verification: incoming webhooks from other services, OAuth redirect callbacks, health checks. The handler is called as handler(event, bot) with the raw Lambda event and this instance, so it can reuse send_message, execute_webhook, and the rest.

path may contain {name} segments; matched values arrive on event["pathParameters"]. A trailing {name+} captures the rest of the path. The handler may return a string, a dict or list (sent as JSON), a status int, a (status, body) or (status, body, headers) tuple, or a full Lambda proxy dict.

Works on either endpoint. On the default Function URL every path reaches the function and cordless does the matching. Setting endpoint = "api_gateway" adds edge 404s for unknown paths and makes cordless deploy sync these routes onto the API alongside the Discord commands.

bot.error(func: Any)

Register the error handler, called as (ctx, exc). If it sends a response (or returns one), that becomes the interaction’s response; otherwise the exception propagates.

bot.guard(fn: Any)

Attach a guard that runs before the handler. Guards reject by raising: a falsy return value is ignored, not treated as a rejection. Can be sync or async; runs for commands, buttons, selects, and modals alike.

bot.handle(event: Any, context: Any = None)

Process one raw Lambda event dict: verifies the signature and dispatches it to the right registered handler. Most bots use handler() instead, which wraps this plus keep-warm pings and @bot.cron dispatch, call this directly only if you’re building a custom Lambda entrypoint.

bot.load_extension(name: str)

Load a cog module by dotted path (e.g. ‘cogs.game’). Discovers all Cog instances defined in the module automatically. Alternatively, define a plain (non-async) setup(bot) for manual control.

bot.load_extensions(package: str)

Load all cog modules in a package (e.g. ‘cogs’). Files starting with ‘_’ are skipped.

bot.add_cog(cog: Any)

Register all decorated handlers from a Cog instance.

bot.sync_commands(bot_token: Any = None, client_id: Any = None, client_secret: Any = None,
guild_id: Any = None)

Push this bot’s registered commands to Discord.

Authenticate with a bot token, or with client_id + client_secret via OAuth2 client credentials (no bot user required). Run this from a deploy step, not from inside the Lambda handler, since it makes blocking network calls to Discord’s API.

Omit guild_id (the default) to sync each command to its own scope: global by default, or whichever guild(s) @bot.command(guild_ids=...) named, all in this one call. Pass guild_id to override every command’s own scope and push the full set to just that guild instead, for instant updates during development.

choice(name: str, value: Any, name_localizations: Any = None)

Build a single choice dict for option(choices=[...]). name_localizations is a {locale: name} dict, e.g. {"es-ES": "Rojo"}

  • choices have no description, so there’s no description_localizations.
option(name: str, description: str = "No description provided.", *, type: Any = "string",
required: bool = False, autocomplete: bool = False, choices: Any = None,
min_value: Any = None, max_value: Any = None, min_length: Any = None,
max_length: Any = None, channel_types: Any = None, name_localizations: Any = None,
description_localizations: Any = None)

Build a Discord application command option dict, for @bot.command(options=[...]).

Parameter
type "string", "integer", "number", "boolean", "user", "channel", "role", "attachment"
required Default False, note this is the opposite default from inferred options, where a parameter without a default value is required
autocomplete Pair with @bot.autocomplete
choices List of {"name": label, "value": value} dicts (build with choice() for per-choice localization); the user must pick one
min_value / max_value Bounds for integer/number options
min_length / max_length Length bounds for string options
channel_types Restrict a channel option to specific Discord channel type ints, e.g. [0, 2] for text + voice
name_localizations {locale: name} dict for this option’s per-locale name, e.g. {"es-ES": "cantidad"}
description_localizations {locale: description} dict, same shape as name_localizations

Every handler receives one of these as ctx. Fields not applicable to the current interaction are None (or empty). Constructed by cordless itself, not something you instantiate directly.

Attribute
ctx.user The invoking User (resolved from the member in guilds, direct in DMs)
ctx.member Guild Member (roles, nick, permissions); None in DMs
ctx.guild_id / ctx.channel_id Where the interaction happened
ctx.channel Partial Channel
ctx.guild Partial Guild (None in DMs); only .id, .locale, .features are populated
ctx.message The Message the component sits on (component interactions)
ctx.locale The invoking user’s locale, e.g. "en-US"
ctx.options Dict of option name to value for the invoked (sub)command
ctx.attachments Dict of attachment id to Attachment for attachment options
ctx.resolved_users / ctx.resolved_members Dict of id to resolved User/Member, for UserSelect/MentionableSelect picks
ctx.resolved_roles Dict of id to resolved Role, for RoleSelect/MentionableSelect picks
ctx.resolved_channels Dict of id to resolved Channel, for ChannelSelect picks
ctx.custom_id The component/modal’s full custom_id
ctx.custom_id_args Suffix segments when a handler matched by prefix ("shop:item1" becomes ["item1"])
ctx.values Selected values/ids for select menus (always a list)
ctx.modal_values Dict of field custom_id to submitted value (modal submissions)
ctx.focused_value What the user has typed so far (autocomplete)
ctx.target_user / ctx.target_member Target User / Member of a user context menu command
ctx.target_message Target Message of a message context menu command
ctx.interaction_id / ctx.token The interaction’s id and token
ctx.entitlements List of entitlement dicts the invoking user/guild holds, for gating premium features
ctx.interaction The full raw interaction payload, for anything not surfaced above

User, Member, Message, Channel, and Attachment are thin wrappers around Discord’s raw object, not dicts. Every field Discord sends is available as an attribute, e.g. ctx.user.username. Fields not on the underlying payload raise AttributeError rather than silently returning None.

await ctx.send(msg: Any = None, *, content: Any = None, ephemeral: bool = False,
embeds: Any = None, components: Any = None, files: Any = None,
allowed_mentions: Any = None)

Send the response. msg and content are interchangeable (positional vs keyword). files is a list of (filename, bytes) tuples. In a deferred handler, send edits the loading message instead of creating a new one.

await ctx.followup(msg: Any = None, *, content: Any = None, ephemeral: bool = False,
embeds: Any = None, components: Any = None, files: Any = None,
allowed_mentions: Any = None)

Manual replica of what decorator defer=True sends automatically: same shape as send. You normally don’t call this yourself, it’s what send/edit fall through to in worker mode.

await ctx.send_followup(msg: Any = None, *, content: Any = None, ephemeral: bool = False,
embeds: Any = None, components: Any = None, allowed_mentions: Any = None)

Deferred handlers only: post an additional, separate message (doesn’t touch the original loading message).

await ctx.delete_original()

Deferred handlers only: delete the original loading message.

await ctx.edit(msg: Any = None, *, content: Any = None, embeds: Any = None,
components: Any = None, files: Any = None, allowed_mentions: Any = None)

Update the message the component sits on (buttons/selects). No ephemeral: a message’s visibility can’t change after creation.

await ctx.defer(ephemeral: bool = False)

Loading state, for commands/modals. You don’t normally call this yourself; decorator defer=True handles the ack and runs your handler on the worker.

await ctx.defer_edit()

Defer a component interaction: tells Discord we’ll update this message async (type 6).

await ctx.send_modal(modal: Any)

Show a Modal. Must be the first response; you can’t defer, then open a modal.

await ctx.respond_autocomplete(choices: Any)

The manual piece underneath an @bot.autocomplete handler’s returned list. You don’t normally call this yourself.

Cog()

Group related handlers. Decorate functions with @cog.command, @cog.button, etc.

Cog.command(name: str, description: str = "No description provided.", options: Any = None,
defer: bool = False, dm_permission: bool = True,
default_member_permissions: Any = None, nsfw: bool = False, ephemeral: bool = False,
guild_ids: Any = None, user_installable: Union[bool, Literal["only"]] = False,
name_localizations: Any = None, description_localizations: Any = None,
group_description: str | None = None, group_name_localizations: Any = None,
group_description_localizations: Any = None, parent_name_localizations: Any = None)

Same parameters as Cordless.command. Handlers registered here take effect once the cog is passed to bot.add_cog(cog).

Cog.button(custom_id: str, defer: bool = False)

Same as Cordless.button.

Cog.select(custom_id: str, defer: bool = False)

Same as Cordless.select.

Cog.modal(custom_id: str, defer: bool = False)

Same as Cordless.modal.

Cog.route(method: str, path: str)

Same as Cordless.route.

Cog.autocomplete(cmd_name: str, option_name: str | None)

Same as Cordless.autocomplete.

Cog.user_command(name: str, dm_permission: bool = True, default_member_permissions: Any = None,
nsfw: bool = False, guild_ids: Any = None,
user_installable: Union[bool, Literal["only"]] = False,
name_localizations: Any = None)

Same as Cordless.user_command.

Cog.message_command(name: str, dm_permission: bool = True,
default_member_permissions: Any = None, nsfw: bool = False,
guild_ids: Any = None,
user_installable: Union[bool, Literal["only"]] = False,
name_localizations: Any = None)

Same as Cordless.message_command.

ActionRow(components: collections.abc.Iterable[typing.Any])

Wraps up to 5 buttons, or exactly 1 select (Discord doesn’t allow a select menu to share a row with anything else).

Button(label: str | None = None, custom_id: str | None = None, style: int = 1,
url: str | None = None, emoji: dict[str, typing.Any] | None = None,
disabled: bool = False, sku_id: str | None = None)

style is a ButtonStyle. emoji is a partial emoji dict, e.g. {"name": "👋"} or {"id": "1234", "name": "custom"}.

Button.style values: PRIMARY (1), SECONDARY (2), SUCCESS (3), DANGER (4), LINK (5, takes url instead of custom_id), PREMIUM (6, takes only sku_id).

Constant Value
PRIMARY 1
SECONDARY 2
SUCCESS 3
DANGER 4
LINK 5
PREMIUM 6
ChannelSelect(custom_id: str, channel_types: list[int] | None = None,
placeholder: str | None = None, min_values: int = 1, max_values: int = 1,
disabled: bool = False, default_values: list[typing.Any] | None = None)

A select menu populated with the guild’s channels, resolved by Discord. channel_types is a list of Discord channel type ints, e.g. [0, 2] for text + voice. default_values pre-selects entries: a list of {"id": ..., "type": "channel"} dicts.

Label(label: str, component: Any, description: str | None = None)

Wraps a single select menu (or TextInput) in a Modal with a label, Discord’s required container for anything other than a plain TextInput in an ActionRow.

MentionableSelect(custom_id: str, placeholder: str | None = None, min_values: int = 1,
max_values: int = 1, disabled: bool = False,
default_values: list[typing.Any] | None = None)

A select menu populated with both members and roles, resolved by Discord. default_values pre-selects entries: a list of {"id": ..., "type": "user"|"role"} dicts.

Modal(custom_id: str, title: str, *components: Any)

Takes up to 5 TextInputs (each wrapped in its own row automatically). Select menus aren’t valid inside an ActionRow in a modal, wrap them in a Label first: Modal("m", "Title", Label("Pick one", StringSelect(...))).

RoleSelect(custom_id: str, placeholder: str | None = None, min_values: int = 1,
max_values: int = 1, disabled: bool = False,
default_values: list[typing.Any] | None = None)

A select menu populated with the guild’s roles, resolved by Discord. default_values pre-selects entries: a list of {"id": ..., "type": "role"} dicts.

SelectOption(label: str, value: str, description: str | None = None,
emoji: dict[str, typing.Any] | None = None, default: bool = False)

One option in a StringSelect. default=True pre-selects it.

StringSelect(custom_id: str, options: collections.abc.Iterable[typing.Any],
placeholder: str | None = None, min_values: int = 1, max_values: int = 1,
disabled: bool = False)

A select menu with a fixed list of SelectOptions.

TextInput(custom_id: str, label: str, style: int = 1, min_length: int | None = None,
max_length: int | None = None, required: bool = True, value: str | None = None,
placeholder: str | None = None)

A field inside a Modal. value pre-fills it.

TextInput.style values: SHORT (1) or PARAGRAPH (2).

Constant Value
SHORT 1
PARAGRAPH 2
UserSelect(custom_id: str, placeholder: str | None = None, min_values: int = 1,
max_values: int = 1, disabled: bool = False,
default_values: list[typing.Any] | None = None)

A select menu populated with the guild’s members, resolved by Discord. default_values pre-selects entries: a list of {"id": ..., "type": "user"} dicts.

Embed(title: str | None = None, description: str | None = None, color: int | None = None,
url: str | None = None, timestamp: datetime.datetime | str | None = None)

color is an integer (0x5865F2); timestamp accepts a datetime or ISO 8601 string. All setters return the embed, so calls chain: Embed(title="Hi").set_footer("a footer").add_field("name", "value").

Embed.set_footer(text: str, icon_url: str | None = None)

Sets the embed’s footer text and optional icon. Returns self.

Embed.set_image(url: str)

Sets the embed’s large image. Returns self.

Embed.set_thumbnail(url: str)

Sets the embed’s small corner thumbnail. Returns self.

Embed.set_author(name: str, url: str | None = None, icon_url: str | None = None)

Sets the embed’s author line, with an optional link and icon. Returns self.

Embed.add_field(name: str, value: str, inline: bool = False)

Appends an EmbedField. Returns self.

EmbedField(name: str, value: str, inline: bool = False)

What Embed.add_field creates; you rarely construct it directly.

Attachment(data: dict[str, typing.Any] | None)

A file attached to a command’s attachment option, e.g. ctx.attachments[att_id]. .id, .filename, .url, .size, .content_type, and any other field Discord sends are available as attributes.

Permissions(raw: str | int | None = 0, **flags: bool)

A Discord permission bitfield. Read one off an incoming member or role, e.g. ctx.member.permissions.manage_guild, or build one to send, e.g. default_member_permissions=Permissions(manage_guild=True).

raw is the starting value (Discord sends this as a string of a big int, e.g. off ctx.member.permissions or ctx.role.permissions). Keyword args set or clear individual named bits on top of that, e.g. Permissions(manage_guild=True, kick_members=True).

Container(components: collections.abc.Iterable[typing.Any], accent_color: int | None = None,
spoiler: bool = False)

Components v2 layout block. accent_color is an integer color for the left-edge bar.

File(url: str, spoiler: bool = False)

Components v2 layout block: a file attached to this message. url must be an "attachment://filename" reference, matching a file uploaded alongside this message.

MediaGallery(*items: Any)

Components v2 layout block: a gallery of images/videos. items are dicts, e.g. {"media": {"url": "..."}}.

Section(*components: Any, accessory: Any = None)

Components v2 layout block. Holds up to 3 TextDisplays with an optional Thumbnail or Button accessory.

Separator(divider: bool = True, spacing: int = 1)

Components v2 layout block: visual spacing between other blocks. spacing is 1 (small) or 2 (large).

TextDisplay(content: str)

Components v2 layout block: a block of markdown text.

Thumbnail(url: str, description: str | None = None, spoiler: bool = False)

Components v2 layout block: a small image, typically used as a Section’s accessory.