# Migrate from discord.js

> Move a discord.js v14 bot to lamma.js step by step, with every change your code needs.

This guide moves a bot written for current discord.js (14.27) to [lamma.js](https://lammapp.sa/developers/lamma-js) one step at a time. Most bots keep their structure, events and command handlers. What changes is the import, the address, the token, a few names, and any feature Lamma doesn't offer bots.

> [!NOTE]
> lamma.js isn't published to npm yet. You can read this guide and check your bot against it now, then switch once the package is released.

## The short version

For most bots the whole change is the import and the token:

```diff
- import { Client, GatewayIntentBits, Events, SlashCommandBuilder } from 'discord.js';
+ import { Client, GatewayIntentBits, Events, SlashCommandBuilder } from 'lamma.js';
```

```bash
LAMMA_TOKEN=lmb1_... node index.js
```

Your intents, events, command files and permission checks stay as they are. Lamma's address is built in, as Discord's is. `login()` with no token reads `LAMMA_TOKEN`, and `client.login(process.env.DISCORD_TOKEN)` works too if you put the Lamma token there. Once lamma.js is on npm, an install alias (`npm install discord.js@npm:lamma.js`) will leave even the import untouched.

The steps below are for bots that use something Lamma doesn't have yet, and for checking a port line by line.

## 1. Check what your bot uses

Search your code for the features below first. Each needs a change, or is switched off on Lamma.

| If your bot uses | On Lamma |
| --- | --- |
| `user.send()`, `createDM()`, DM commands | Bots can't DM people. The call fails with code `50007`, like a person with DMs closed |
| `GuildPresences`, `member.presence`, `presenceUpdate` | Never sent to bots. The intent reads as off, so code that checks it switches itself off |
| `sendTyping()`, `TypingStart` | Does nothing, with one warning |
| `setPresence()`, `setActivity()` | Does nothing for now, with one warning |
| Buttons, select menus, modals (`ButtonBuilder`, `ActionRowBuilder`, `ModalBuilder`) | Not yet. Rewrite those flows as slash command options |
| Subcommands, autocomplete, context menus, attachment options | Not yet. `addSubcommand` throws so you notice. Use separate commands |
| Components V2 (`ContainerBuilder`, `TextDisplayBuilder`, …) | Not yet. Use embeds |
| `files`, `AttachmentBuilder`, embed images and thumbnails | Not yet. Images are dropped and files aren't sent |
| `ban()`, `kick()`, `timeout()`, `roles.add()`, deleting other people's messages | Not in v1. Bots can still hear about bans, kicks and timeouts |
| Voice (`@discordjs/voice`) | Not offered to bots |
| Threads, forums, stages, stickers | Lamma doesn't have them yet |
| `permissionsFor()`, `permissionsIn()` for someone other than the bot | Returns `null` with one warning. See [step 7](#7-keep-your-permission-checks) |
| `fetchReply()`, `reply({ withResponse: true })` | Not yet. Use the message `editReply()` returns |
| `Options`, `ShardingManager` | Not exported. Remove them: Lamma needs no shards |
| `discord.com/channels/…` links | Use `message.url` or Lamma's own links |
| Libraries that look people up on Discord by id | Remove them. Lamma ids aren't Discord ids, so they would find the wrong person |

## 2. Make an app and get a token

Follow [Build your first bot](https://lammapp.sa/developers/quick-start) to create the app, copy its token and install it in a test server. Ask for the scopes and intents your bot really uses.

Discord ids saved in your database won't match anything on Lamma. Plan to set up each server's settings again.

## 3. Swap the package

```bash
npm uninstall discord.js
npm install lamma.js
```

## 4. Change the import and the client

```js
// Before
import { Client, GatewayIntentBits, Partials } from 'discord.js';
const client = new Client({
  intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent],
  partials: [Partials.Message, Partials.Reaction]
});
client.login(process.env.DISCORD_TOKEN);

// After
import { Client, GatewayIntentBits, Partials } from 'lamma.js';
const client = new Client({
  intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent],
  partials: [Partials.Message, Partials.Reaction]
});
client.login(process.env.LAMMA_TOKEN);
```

`login()` with no token reads `LAMMA_TOKEN`. To reach another copy of Lamma, such as a local one, set `LAMMA_URL` or `baseURL`. `partials` can stay: lamma.js always delivers events about uncached messages and users as partials.

## 5. Keep your intents

Keep `GatewayIntentBits` as it is. Bits, arrays, flag names such as `'GuildMessages'` and `IntentsBitField` all work.

| discord.js intent | On Lamma |
| --- | --- |
| `Guilds`, `GuildMessages`, `GuildMessageReactions` | Sent |
| `MessageContent`, `GuildMembers` | Sent. Privileged |
| `GuildModeration` (or `GuildBans`) | Sent. Also needs View audit log |
| `GuildExpressions` (or `GuildEmojisAndStickers`) | Sent, for emoji |
| `GuildPresences`, `GuildMessageTyping`, `DirectMessages`, `GuildVoiceStates`, `GuildInvites`, `GuildWebhooks`, `GuildScheduledEvents`, `AutoModeration*` | Ignored with one warning, and read as off |

Privileged intents work in up to five servers you own while you develop, and everywhere once the app is [reviewed](https://lammapp.sa/developers/apps#privileged-access). In the app's settings, intents are listed by what they send, such as **New, edited and deleted messages** for `GuildMessages`.

## 6. Keep your events and handlers

These work as they are: `Events.ClientReady` (`'clientReady'`, as in current discord.js), `MessageCreate`, `MessageUpdate`, `MessageDelete`, `MessageBulkDelete`, `MessageReactionAdd`, `MessageReactionRemove`, `InteractionCreate`, `GuildCreate`, `GuildDelete`, `GuildUpdate`, `GuildMemberAdd`, `GuildMemberUpdate`, `GuildMemberRemove`, `GuildBanAdd`, `GuildBanRemove`, `GuildAuditLogEntryCreate`, `ChannelCreate`, `ChannelUpdate`, `ChannelDelete`, `GuildRoleCreate`, `GuildRoleUpdate`, `GuildRoleDelete`, `GuildEmojiCreate`, `GuildEmojiUpdate`, `GuildEmojiDelete`, `Error`, `Warn` and `Raw`.

Reaction events give `(reaction, user, { type, burst })`, as in discord.js. A reaction on a message the bot hasn't cached comes as a partial: check `reaction.partial` and call `reaction.fetch()`.

The message cache keeps 200 messages per channel in memory, so `messageUpdate` and `messageDelete` carry the old message as they do in discord.js. Follow the [message cache rule](https://lammapp.sa/developers/developer-rules#message-text-stays-in-its-server).

When the connection drops, the client reconnects by itself. There is no resume, so `ClientReady` fires again; fetch anything you missed with `channel.messages.fetch({ after })`.

## 7. Keep your permission checks

`PermissionsBitField` and `PermissionFlagsBits` work as in discord.js:

```js
if (!interaction.memberPermissions.has(PermissionFlagsBits.ManageMessages)) return;
if (!interaction.appPermissions.has(PermissionFlagsBits.EmbedLinks)) return interaction.reply('I need Embed links here.');
const canKick = message.member.permissions.has(PermissionFlagsBits.KickMembers);
```

- `interaction.memberPermissions` and `interaction.appPermissions` are exact for the command's channel, including channel overrides.
- `member.permissions` and `role.permissions` are server-wide, as in discord.js.
- `guild.members.me`, `channel.permissionsFor(client.user)` and `me.permissionsIn(channel)` give the bot's own permissions.
- Lamma doesn't send other members' channel overrides, so `channel.permissionsFor()` for anyone but the bot returns `null` and warns once. In a command, use `interaction.memberPermissions` instead.

Lamma's permissions have Discord's flags. A few have different names in Lamma's settings: **Manage server** is `ManageGuild`, **Create invite** is `CreateInstantInvite`, **Create polls** is `SendPolls` and **Manage expressions** is `ManageGuildExpressions`. Lamma's **Mention roles** has no Discord flag, so it's left out. Discord flags for features Lamma doesn't have, such as `ManageThreads`, read as off unless the member is an administrator.

## 8. Update slash commands

`SlashCommandBuilder` works as it is for string, integer, number, boolean, user, channel and role options, and `toJSON()` gives the same JSON as @discordjs/builders. A bot laid out like the discord.js guide keeps its layout: one `{ data, execute }` file per command in `commands/`, one file per event in `events/`, a `client.commands` `Collection`, and a `deploy-commands.js` that registers them with `REST` and `Routes`.

```js
// commands/utility/ping.js
import { SlashCommandBuilder } from 'lamma.js';
export default {
  data: new SlashCommandBuilder().setName('ping').setDescription('Check the bot'),
  async execute(interaction) { await interaction.reply('Pong!'); }
};

// deploy-commands.js
import { REST, Routes } from 'lamma.js';
const rest = new REST().setToken(process.env.LAMMA_TOKEN);
await rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: commands.map(command => command.data.toJSON()) });
```

You can also register them from the client:

```js
client.once(Events.ClientReady, async ready => {
  await ready.application.commands.set(commands);          // every server, at once
  // await ready.application.commands.set(commands, guildId); // one server
});
```

- Global commands appear at once, so you don't need test-server tricks.
- `setDefaultMemberPermissions()` isn't enforced yet and warns once. Keep an `interaction.memberPermissions` check in each sensitive command ([step 7](#7-keep-your-permission-checks)).
- `setContexts()` and `setIntegrationTypes()` are accepted. Commands are used in servers only.
- Mentionable and attachment options, autocomplete and subcommands throw when you add them, so you notice.
- Split subcommands into separate commands, for example `/config-show` and `/config-set` instead of `/config show` and `/config set`.

`interaction.reply()`, `deferReply()`, `editReply()`, `followUp()`, `flags: MessageFlags.Ephemeral` and the `interaction.options` getters work as before, within 3 seconds and 15 minutes. `reply({ withResponse: true })` gives `undefined` with one warning and `fetchReply()` rejects, so use the message `editReply()` returns.

## 9. Small changes in messages and channels

- `channel.type` is a `ChannelType`: `GuildText`, `GuildVoice` or `GuildCategory`. Voice channels have a text chat, as on Discord, so `isTextBased()` is true for them.
- Ids are strings in the same format, but they are Lamma ids.
- Embeds keep their fields and limits. Images, thumbnails and video are dropped for now.
- Lamma doesn't notify on replies, so `allowedMentions.repliedUser` changes nothing. Everything else in `allowedMentions` works.
- An ephemeral reply is never stored, so `editReply()` on one returns a plain object, not a `Message`.
- On a rate limit, `retry_after` is in milliseconds. lamma.js waits and retries for you.

## 10. Test it

1. Run the bot against a test server you own.
2. Watch for `warn` events: lamma.js warns once for each intent, call or option Lamma doesn't support, so nothing fails silently.
3. Try every command and the events your bot relies on.
4. When it works, share the install link with the servers that use it.

Anything missing that your bot needs? Email [lamma.help@gmail.com](mailto:lamma.help@gmail.com) and say what it is.
