Skip to main content
rule

discord-api — Cursor rule: purpose and installation

This Cursor rule guides Discord app development with REST requests, typed API data, and interaction-based commands. Use it while building or reviewing a bot.

Install into Cursor · Manually: put the files in place All methods and platforms ↓
.cursor/rules/discord-api.mdc
Open on GitHub

What discord-api does

The rule separates API work by purpose: use REST to create or modify resources, and the Gateway to receive events. It recommends discord-api-types/v10 for typed payloads, exponential delays when handling 429 responses, and keeping the bot token in the DISCORD_BOT_TOKEN environment variable.

For new interactions, it favors slash commands and message components over prefix-based commands. It also advises requesting only the Gateway Intents the bot needs and splitting command and event handlers into testable modules. MessageContent is privileged, so a bot that reads message text needs justification for that intent.

Place the rule at .cursor/rules/discord-api.mdc in your project. Its examples use TypeScript, discord-api-types/v10, and discord.js; the rule does not install those libraries.

What it helps with

  • send Discord API resource changes through REST
  • add API types to request and response data
  • handle 429 responses with exponential delays
  • keep the bot token in an environment variable
  • split command and event handlers into modules

Example prompts for the agent

  • Review my bot code using the discord-api rule, especially its Discord API requests.
  • Replace prefix commands with slash commands following the discord-api rule.
  • Add exponential-delay handling for 429 responses based on the discord-api rule.

How to install discord-api

Manually: put the files in place

.cursor/rules/discord-api.mdc

Frontmatter: description, globs, alwaysApply

Step by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules

Questions about discord-api

How do I install discord-api in Cursor?

Save the rule in your project at .cursor/rules/discord-api.mdc.

What does discord-api do?

It sets out practices for Discord apps, including REST for resource changes, typed API payloads, and interaction-based commands. It also covers rate limits and bot token handling.

Which libraries appear in the discord-api examples?

The examples use discord-api-types/v10 and discord.js. If the bot processes message text, the rule says to request the privileged MessageContent intent only when needed.

Source text

discord-api.mdc · CC0-1.0
---
description: Definitive guidelines for building robust, scalable, and compliant Discord applications using the discord-api, emphasizing REST over Gateway, strong typing, and modern interaction patterns.
globs: **/*
---
# discord-api Best Practices

Building Discord applications requires adherence to specific patterns for reliability and compliance. This guide outlines the essential practices for our team.

## 1. API Interaction: REST First, Gateway for Events

Always prefer the HTTP REST API for creating, updating, or deleting resources. The Gateway is exclusively for receiving real-time events. This minimizes rate-limit issues and simplifies scaling.

❌ **BAD: Using Gateway for resource modification**
```typescript
// Don't send messages or modify roles via Gateway opcodes directly
// This is an oversimplification, but illustrates the anti-pattern
gateway.send({ op: GatewayOpcodes.MessageCreate, d: { channel_id: '...', content: '...' } });
```

✅ **GOOD: Using REST for resource modification**
```typescript
import { Routes, REST } from 'discord-api-types/v10';

const rest = new REST().setToken(process.env.DISCORD_BOT_TOKEN!);

// Always specify the API version (v10 as of 2025)
await rest.post(Routes.channelMessages('12345'), {
  body: { content: 'Hello via REST!' },
});
```

## 2. Strong Typing with `discord-api-types`

Ensure all API payloads conform to Discord's official types. This prevents runtime errors and future-proofs against API changes.

❌ **BAD: Untyped payloads**
```typescript
// Prone to typos and API changes breaking at runtime
const messagePayload = {
  channel_id: '123',
  content: 'Untyped message',
};
```

✅ **GOOD: Typed payloads**
```typescript
import { APIChannel, APIMessage } from 'discord-api-types/v10';

const channel: APIChannel = await rest.get(Routes.channel('123'));
const messagePayload: APIMessage = {
  channel_id: channel.id,
  content: 'Typed message!',
  // ... other required fields
};
```

## 3. Rate Limit Handling: Exponential Back-off

Discord's API is heavily rate-limited. Implement robust exponential back-off with jitter to handle `429 Too Many Requests` responses gracefully.

❌ **BAD: Ignoring rate limits or simple retries**
```typescript
try {
  await rest.post(Routes.channelMessages('...'), { body: { content: '...' } });
} catch (error: any) {
  if (error.status === 429) {
    console.warn('Rate limited! Retrying immediately...');
    await rest.post(Routes.channelMessages('...'), { body: { content: '...' } }); // Will likely fail again
  }
}
```

✅ **GOOD: Exponential back-off with `Retry-After`**
```typescript
import { setTimeout } from 'node:timers/promises';

async function makeRequestWithRetry(requestFn: () => Promise<any>, retries = 5, delay = 1000): Promise<any> {
  try {
    return await requestFn();
  } catch (error: any) {
    if (error.status === 429 && retries > 0) {
      const retryAfter = (error.headers?.get('Retry-After') ? parseInt(error.headers.get('Retry-After')!) * 1000 : delay) + Math.random() * 500;
      console.warn(`Rate limited. Retrying in ${retryAfter}ms. Retries left: ${retries}`);
      await setTimeout(retryAfter);
      return makeRequestWithRetry(requestFn, retries - 1, delay * 2);
    }
    throw error;
  }
}

await makeRequestWithRetry(() => rest.post(Routes.channelMessages('...'), { body: { content: '...' } }));
```

## 4. Secure Token Management

Never hardcode or commit bot tokens. Use environment variables or a secure secret management system.

❌ **BAD: Hardcoded token**
```typescript
const rest = new REST().setToken('YOUR_SUPER_SECRET_TOKEN_HERE');
```

✅ **GOOD: Environment variable**
```typescript
// Ensure DISCORD_BOT_TOKEN is set in your environment
const rest = new REST().setToken(process.env.DISCORD_BOT_TOKEN!);
```

## 5. Gateway Intents: Request Only What's Needed

Specify only the Gateway Intents your bot genuinely requires. Over-requesting intents can lead to verification denial and unnecessary resource consumption.

❌ **BAD: Requesting all intents or privileged intents without justification**
```typescript
// Don't request all intents if you only need MESSAGE_CREATE
const client = new Client({ intents: [
  GatewayIntentBits.Guilds,
  GatewayIntentBits.GuildMembers,
  GatewayIntentBits.GuildMessages,
  GatewayIntentBits.MessageContent, // Privileged, requires justification
  // ... many more
]});
```

✅ **GOOD: Minimal, justified intents**
```typescript
import { Client, GatewayIntentBits } from 'discord.js'; // Or your chosen library

const client = new Client({ intents: [
  GatewayIntentBits.Guilds, // For guild-related events
  GatewayIntentBits.GuildMessages, // For message events in guilds
  GatewayIntentBits.MessageContent, // ONLY if you process message content (privileged)
]});
```

## 6. Modern UI: Slash Commands & Message Components

Prioritize Application Commands (slash commands) and Message Components (buttons, select menus) for user interaction. Text commands are largely deprecated for new features.

❌ **BAD: Relying solely on prefix commands**
```typescript
// This approach is outdated for new features
client.on('messageCreate', message => {
  if (message.content.startsWith('!mycommand')) {
    // ...
  }
});
```

✅ **GOOD: Registering and handling slash commands**
```typescript
import { ApplicationCommandType, Routes, REST } from 'discord-api-types/v10';

const commands = [{
  name: 'ping',
  description: 'Replies with Pong!',
  type: ApplicationCommandType.ChatInput,
}];

const rest = new REST().setToken(process.env.DISCORD_BOT_TOKEN!);

// Register commands globally or per guild
await rest.put(Routes.applicationCommands(process.env.DISCORD_CLIENT_ID!), { body: commands });

// Handle interactionCreate event for slash commands
client.on('interactionCreate', async interaction => {
  if (!interaction.isChatInputCommand()) return;
  if (interaction.commandName === 'ping') {
    await interaction.reply('Pong!');
  }
});
```

## 7. Code Organization: Modular and Testable

Structure your bot into modular, testable components (e.g., separate files for commands, events, services). This improves maintainability and allows for easier testing.

❌ **BAD: Monolithic bot file**
```typescript
// bot.ts
client.on('ready', () => { /* ... */ });
client.on('messageCreate', () => { /* ... */ });
// ... all logic in one file
```

✅ **GOOD: Modular structure**
```typescript
// src/bot.ts
import { Client } from 'discord.js';
import { registerCommands } from './commands';
import { registerEvents } from './events';

const client = new Client({ intents: [...] });
registerCommands(client);
registerEvents(client);
client.login(process.env.DISCORD_BOT_TOKEN);

// src/commands/index.ts
export function registerCommands(client: Client) {
  // Load and register command handlers
}

// src/events/index.ts
export function registerEvents(client: Client) {
  // Load and register event handlers
}
```

## 8. Data Storage: Use Databases

For persistent data, always use a proper database (SQL or NoSQL). Avoid JSON files for anything beyond simple configuration. JSON files are inefficient for frequent writes and concurrent access.

❌ **BAD: Storing user data in JSON files**
```typescript
// user_data.json
// { "userId": { "points": 100, "level": 5 } }
// This rewrites the entire file on every change, inefficient and error-prone
```

✅ **GOOD: Using a database**
```typescript
// Example with a simple ORM/ODM (e.g., Prisma, Mongoose, SQLAlchemy)
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

async function updateUserPoints(userId: string, points: number) {
  await prisma.user.upsert({
    where: { id: userId },
    update: { points: { increment: points } },
    create: { id: userId, points: points, level: 1 },
  });
}
```

## 9. Game SDK & Rich Presence: Modern Approach

The original Game SDK is archived. For new game integrations, prioritize the Discord Social SDK. If using the legacy Game SDK, rely on its "stub" libraries to ensure compatibility. For Rich Presence, keep strings concise, actionable, and utilize all available fields for maximum impact.

❌ **BAD: Long, uninformative Rich Presence strings**
```typescript
// "Playing a very long game of my favorite game with my friends and having a great time"
```

✅ **GOOD: Concise and actionable Rich Presence**
```typescript
// "Playing [Game Name]"
// "In a party with 3 others"
// "Elapsed: 00:15:30"
// "Join Game" button
```

© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc

Similar IDE rules

All IDE rules for Backend and APIs →

From the same set240

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor