@robojs/giveaways
🎉 One-click Discord giveaways plugin for Robo.js - Makes running giveaways effortless with automatic winner selection, persistent storage, and rich moderation tools.
➞ 📚 Documentation: Getting started
➞ 🚀 Community: Join our Discord server
Features
✨ Zero-Friction Entry - Users click a button to enter giveaways
🎯 Fair Winner Selection - Random selection with no duplicates
💾 Persistent Storage - All giveaway data survives bot restarts via Flashcore with automatic recovery
⏰ Automatic Scheduling - Giveaways end automatically at configured time
🔒 Role Restrictions - Allow/deny specific roles from entering
📅 Account Age Limits - Require minimum account age
🔄 Reroll Winners - Select new winners from remaining entrants
⚙️ Per-Guild Settings - Customize defaults for each server
🌐 Optional Web API - REST endpoints for dashboards (requires @robojs/server)
🛡️ Permission Checks - Manage Server permission for admin commands
Getting Started
npx robo add @robojs/giveawaysNew to Robo.js? Start your project with this plugin pre-installed:
npx create-robo <project-name> -p @robojs/giveawaysQuick Start
1. Start a Giveaway
/giveaway start prize: Discord Nitro duration: 1h winners: 1
2. Users Click "Enter Giveaway"
Members click the button on the giveaway message to enter.
3. Automatic Winner Selection
When time expires, the bot automatically:
- Selects random winner(s)
- Updates the message
- Announces winners in channel
- Sends DMs to winners (optional)
Commands
/giveaway start
Start a new giveaway with customizable options.
Required Options:
prize- The prize descriptionduration- Time until end (e.g.,10m,1h,2d)
Optional Options:
winners- Number of winners (default: 1)channel- Channel to post in (default: current)allow_roles- Comma-separated role IDs that can enterdeny_roles- Comma-separated role IDs that cannot entermin_account_age_days- Minimum account age in days
Example: /giveaway start prize: 3x Nitro duration: 24h winners: 3 min_account_age_days: 30
/giveaway end
Manually end an active giveaway and select winners immediately.
Options:
message_id- The giveaway message ID
/giveaway cancel
Cancel a giveaway without selecting winners.
Options:
message_id- The giveaway message ID
/giveaway reroll
Reroll winners from remaining eligible entrants.
Options:
message_id- The giveaway message IDcount- Number of new winners (default: original winner count)
/giveaway list
View all active and recent giveaways in the server.
/giveaway info
Get detailed information about a specific giveaway.
Options:
message_id- The giveaway message ID
/giveaway settings get
View current giveaway settings for the server.
/giveaway settings set
Update server giveaway settings.
Options:
default_winners- Default number of winnersdefault_duration- Default durationbutton_label- Custom button textdm_winners- Send DMs to winners (true/false)max_winners- Maximum winners allowed
/giveaway settings reset
Reset all settings to defaults.
Required Permissions
For the Bot:
- Send Messages
- Embed Links
- Read Message History
- Use Slash Commands
- Manage Messages
For Admin Commands (end, cancel, reroll, settings):
- Manage Server permission
Configuration
Declarative Config (Optional)
Create config/plugins/robojs/giveaways.mjs:
export default {
defaults: {
winners: 1,
duration: '1h',
buttonLabel: 'Enter Giveaway',
dmWinners: true
},
limits: {
maxWinners: 20,
maxDurationDays: 30
},
restrictions: {
allowRoleIds: [],
denyRoleIds: [],
minAccountAgeDays: null
}
}
Imperative API (Optional)
For dashboard integrations:
import { getGuildSettings, setGuildSettings } from '@robojs/giveaways'
import type { GuildSettings } from '@robojs/giveaways/types'
// Get settings
const settings = await getGuildSettings('guild_id_here')
// Update settings
await setGuildSettings('guild_id_here', {
defaults: {
winners: 2,
duration: '2h',
buttonLabel: 'Join Now!',
dmWinners: true
},
limits: {
maxWinners: 10,
maxDurationDays: 7
},
restrictions: {
allowRoleIds: [],
denyRoleIds: [],
minAccountAgeDays: 7
}
})
Optional Integrations
Web API (@robojs/server)
Install the server plugin to enable REST API endpoints:
npx robo add @robojs/server
Endpoints:
GET /api/giveaways/:guildId- List active/recent giveawaysGET /api/giveaways/:guildId/giveaway/:id- Get specific giveawayPATCH /api/giveaways/:guildId/giveaway/:id- Mutate giveaway (end/cancel/reroll)GET /api/giveaways/:guildId/settings- Get settingsPATCH /api/giveaways/:guildId/settings- Update settings
Enhanced Scheduling with @robojs/cron (Optional)
Giveaway data always persists via Flashcore — with or without cron. Adding @robojs/cron enables enhanced scheduling capabilities suited for production (higher accuracy, longer time windows, job persistence, and better recovery), but it is not required for basic persistence.
| Capability | Without @robojs/cron | With @robojs/cron |
|---|---|---|
| Giveaway Data Persistence | ✅ Yes (Flashcore) | ✅ Yes (Flashcore) |
| Recovery on Restart | ✅ Manual (handled in src/events/ready.ts) | ✅ Auto + Manual validation |
| Timing Accuracy | ⚠️ setTimeout cascading | ✅ Precise cron expressions |
| Maximum Duration | ⚠️ ~24.8 days per cycle | ✅ No practical limit |
| Job Persistence | ❌ In-memory only | ✅ Stored in Flashcore |
| Job Auditability | ❌ No | ✅ Can query job status |
Install:
npx robo add @robojs/cronDetails:
- Higher Accuracy: Without cron, long giveaways exceed the
MAX_TIMEOUT_MSlimit (~2^31−1 ms, ~24.8 days) and are broken into cascadingsetTimeoutcalls inscheduleGiveawayEnd(), which can introduce small timing drift. With cron, a single job is scheduled using a precise cron expression generated bytimestampToCronExpression()for accurate execution. - Longer Time Windows:
setTimeoutis bounded byMAX_TIMEOUT_MS, requiring reschedules for >24.8 day durations. Cron schedules 30+ day giveaways without cascading or drift. - Redundant Recovery: Without cron, recovery relies on the
ready.tsstartup handler scanning Flashcore and callingscheduleGiveawayEnd()to restore timers. With cron, jobs are automatically restored by the cron plugin during initialization, and theready.tshandler double-checks viaCron.get()— providing redundancy. - Job Auditability: Cron-backed jobs have IDs and can be inspected via
Cron.get(jobId). PlainsetTimeouttimers provide no audit trail.
Note: The plugin works perfectly without cron. For production, @robojs/cron is recommended for improved reliability and accuracy.
API Reference
All endpoints rely on path parameters to scope requests to a specific guild and, when applicable, a giveaway ID. Examples assume your Robo.js server is running locally on port 3000.
GET /api/giveaways/:guildId
Fetch the active giveaway roster and recent history for a guild.
Response:
{
active: Giveaway[]
recent: Giveaway[]
}
Example:
curl http://localhost:3000/api/giveaways/123456789012345678GET /api/giveaways/:guildId/giveaway/:id
Retrieve details for a single giveaway. Returns 404 when the giveaway does not exist.
Example:
curl http://localhost:3000/api/giveaways/123456789012345678/giveaway/01HQRS5F1GZ9J3YF7WXT7H2K2BPATCH /api/giveaways/:guildId/giveaway/:id
Mutate giveaway state by ending, cancelling, or rerolling winners. Pass one of end, cancel, or reroll in the action field. When rerolling, include a count value indicating how many new winners to draw.
Example body:
{
"action": "reroll",
"count": 2
}
Example:
curl -X PATCH -H 'Content-Type: application/json' -d '{"action":"reroll","count":2}' http://localhost:3000/api/giveaways/123456789012345678/giveaway/01HQRS5F1GZ9J3YF7WXT7H2K2BGET /api/giveaways/:guildId/settings
Read the persisted GuildSettings for a guild.
Example:
curl http://localhost:3000/api/giveaways/123456789012345678/settingsPATCH /api/giveaways/:guildId/settings
Update giveaway settings by sending a partial or complete GuildSettings payload.
Example:
curl -X PATCH -H 'Content-Type: application/json' -d '{"defaults":{"winners":2,"duration":"2h"}}' http://localhost:3000/api/giveaways/123456789012345678/settingsHow It Works
Entry System
- User clicks "Enter Giveaway" button
- Bot validates eligibility (roles, account age)
- Entry is recorded in Flashcore
- User receives confirmation message
Winner Selection
- At end time, bot retrieves all entries
- Re-validates eligibility at draw time
- Randomly selects unique winners
- Updates message and announces results
Persistence
- All data stored in Flashcore (key-value database)
- Active giveaways recovered on bot restart
- Scheduling automatically resumes
Persistence & Recovery
Data Persistence
All giveaway data is always stored in Flashcore regardless of cron availability:
- Giveaway records are stored under the
['giveaways', 'data']namespace (seegiveawayDataNamespaceinsrc/events/ready.ts). - Active giveaway IDs per guild are tracked under
['giveaways', 'guilds', guildId, 'active'](seeguildActiveNamespace()). - Entry records for each giveaway are persisted.
- Guild-level settings are stored persistently.
Scheduling Persistence
- Without
@robojs/cron: Timer handles live in memory only (lost on restart), but giveaway data persists. On startup, theready.tshandler automatically reschedules all active giveaways by callingscheduleGiveawayEnd()for each one. - With
@robojs/cron: Both giveaway data and cron jobs persist in Flashcore. Jobs are saved (e.g.,job.save(jobId)) and automatically restored during cron initialization. Theready.tshandler still validates that each job exists viaCron.get()and reschedules if missing.
Recovery Process
- Bot starts and
readyevent fires. initCron()runs to detect cron availability.- Handler enumerates all guilds the bot is in.
- For each guild, load active giveaway IDs from Flashcore.
- For each active giveaway, load the full record from Flashcore.
- Verify status; skip if not
active. - If
endsAtis in the past, callendGiveaway()immediately. - If still active and cron is available, check for an existing job via
Cron.get(jobId). - If missing (or when cron is unavailable), call
scheduleGiveawayEnd()to (re)schedule. - Persist any updated
cronJobIdon the giveaway record.
Key takeaway: Giveaway data never gets lost on restart, and scheduling always resumes automatically — with or without cron.
Edge Cases
✅ No Entrants - Shows "Not enough entrants"
✅ Insufficient Entrants - Selects all available entrants
✅ Bot Restart - Recovers and reschedules active giveaways
✅ Deleted Channels - Handles gracefully without crashing
✅ Role Changes - Re-validates eligibility at draw time
✅ Duplicate Entries - Prevented (one entry per user)
✅ Spam Clicking - Rate limited (3-second cooldown)
Architecture & Design
- Single-Instance Runtime: Active giveaways, interaction cooldowns, and timer handles are kept in memory. Running multiple bot instances in parallel will cause duplicated timers and inconsistent state because in-memory data is not synchronized. Horizontal scaling currently requires external coordination.
- Data Persistence: All giveaway data (records, entries, settings) is stored in Flashcore and survives restarts regardless of cron availability.
- Scheduling Modes: The plugin supports two scheduling approaches:
- Without
@robojs/cron: UsessetTimeoutwith cascading reschedules for long durations (max ~24.8 days per cycle as defined inMAX_TIMEOUT_MS). Timer handles are in-memory only, but theready.tshandler automatically reschedules all active giveaways on restart by scanning Flashcore. - With
@robojs/cron: Uses persistent cron jobs stored in Flashcore viajob.save(). Jobs are automatically restored during cron initialization and validated by theready.tshandler. Provides higher accuracy through precise cron expressions generated bytimestampToCronExpression(), supports unlimited duration giveaways, and enables job auditability viaCron.get().
- Without
- Production Recommendation: While both modes ensure giveaways complete successfully,
@robojs/cronis recommended for production deployments due to enhanced reliability, accuracy, and redundant recovery. - Idempotent Operations: Core lifecycle helpers such as
endGiveaway()andcancelScheduledJob()check the giveaway status before mutating state, allowing safe retries. These guards are not atomic; they rely on the single-instance assumption to prevent race conditions. Distributed deployments would require a shared lock. - Startup Recovery: On bot startup the
ready.tshandler scans Flashcore for active giveaways and reschedules them, ensuring long-running giveaways continue after crashes. When using cron, jobs are automatically restored from storage during initialization.
Troubleshooting
Commands not appearing?
- Wait 1-2 minutes for Discord to sync
- Try
/in a text channel - Run
npx robo build --forceand restart bot
Giveaway not ending?
- Check bot is online
- Verify bot has message permissions
- Check terminal for errors
Winners not announced?
- Verify bot can send messages in the channel
- Check if channel was deleted
- Look for error logs in terminal
Support
Contributing
Contributions welcome! Please read our contributing guidelines before submitting PRs.
License
MIT © Aditya Telsinge
Credits
Built with Robo.js - The all-in-one Discord bot framework.