AL DiscordAPI setup guide
AL DiscordAPI is a free, zero-dependency Discord integration library for FiveM and web servers: role syncing, permission checks, webhook posting and a player data store, with a Node core and a matching FiveM Lua bridge. This guide covers install, the Node quickstart, and the FiveM bridge setup. No license key needed, it is free.
Install
Node install
Requires Node 18 or newer (it uses native fetch). No package registry needed: copy the src folder into your project, or install from a local checkout.
# Node 18 or newer is required (uses native fetch) node --version # install from a local checkout npm install /path/to/discord-api
Then import it:
import { DiscordAPI } from "@appleluis/discord-api";
Quickstart
Connect, sync a role, post a webhook
See examples/basic.js for a complete runnable tour that also works offline, without any Discord credentials set.
import { DiscordAPI } from "@appleluis/discord-api"; const api = new DiscordAPI({ token: process.env.DISCORD_BOT_TOKEN, guildId: process.env.DISCORD_GUILD_ID, webhookUrl: process.env.DISCORD_WEBHOOK_URL, }); // Connect (validates the free license as a no-op, fetches the bot user). await api.connect(); // Sync roles for a member. await api.syncRoles("987654321098765432", ["111111111111111111"]); // Post an embed to your webhook. await api.postEmbed(api.embed({ title: "Server online", description: "All systems go." }));
FiveM bridge
Install the Lua bridge
Copy the fivem/ folder into your resources directory as al_discordapi, fill in config.lua, then add it to your server.cfg.
-- server.cfg
ensure al_discordapiconfig.lua reads its secrets from convars so they stay out of the file. Set these in server.cfg and leave config.lua on the convar fallback:
- discord_bot_token bot token, no "Bot " prefix (Config.BotToken)
- discord_guild_id the guild this resource manages (Config.GuildId)
- discord_webhook_url default webhook target for Log() (Config.WebhookUrl)
config.lua also defines Config.Roles, a table mapping your own rank or job keys to Discord role IDs, plus Config.HttpTimeout (default 10000ms) and Config.Debug for verbose server console logging.
-- from any server-side script in another resource local src = source local discordId = exports.al_discordapi:GetDiscordId(src) exports.al_discordapi:IsInGuild(discordId, function(inGuild) if inGuild then exports.al_discordapi:SyncRole(discordId, 'ROLE_ID', true, function(ok, err) print('role synced:', ok, err) end) end end)
Exports: GetDiscordId, IsInGuild, SyncRole, SyncRoleByKey, Log.
What it does
Role sync, permissions, webhooks, player data
- Role sync: add, remove and reconcile a guild member's roles via the Discord REST API v10, with bot token auth.
- Permission checks: compute a member's effective permission bitfield and test it against a required permission, honoring ADMINISTRATOR and guild owner.
- Webhook posting: send messages and rich embeds to a Discord webhook, with rate-limit-aware retry.
- Player data: a store mapping FiveM identifiers (license:, steam:, discord:) to player records, in memory with optional JSON file persistence.
- Reliability: every network helper reads the HTTP 429 retry_after header and backs off exponentially on 5xx errors.
API reference
Core methods (Node)
- connect() validate the license and, if a token is set, fetch the bot user.
- syncRoles(userId, desiredRoleIds, opts?) reconcile a member to a desired role set.
- memberHasPermission(userId, required, guildId?) compute effective permissions and test against a flag.
- embed(opts) build a Discord embed object (title, description, color, fields, footer, thumbnail, image, timestamp).
- postEmbed(embeds, opts?) post one embed or an array of embeds to a webhook.
- isInGuild(userId, guildId?) boolean membership check.
Sub-modules (PlayerStore, LicenseClient, permission helpers, webhook helpers, Logger, retry, snowflakeToDate) import standalone from the same package for use outside the DiscordAPI facade.
License
Free, no key needed
AL DiscordAPI is free and MIT-style licensed: use it, modify it, ship it, including commercially. It also ships the same LicenseClient our paid products use, as a working reference for our license API. With no key set, LicenseClient.validate() returns a free pass and never blocks.
POST /api/license/validate { "key": "...", "product": "al-store", "fingerprint": "my-server-ip-or-cfx" } # with no key, LicenseClient resolves { valid: true, free: true }