Skip to content

Event Requests

Allow your community to submit watch party event requests through a web form! Server moderators can approve or deny submissions, and approved events are automatically created as Discord Scheduled Events.

Overview

The Event Request System provides:

  • 🔐 Discord OAuth Login - Secure authentication with Discord
  • 📝 Web-based Form - User-friendly event submission interface
  • Moderation Queue - Review and approve/deny requests
  • 🎉 Auto-create Events - Approved events become Discord Scheduled Events
  • ⏱️ Rate Limiting - Prevents spam (1 request per 5 minutes per user)
  • 🌐 Dedicated Deployment - Each website serves one specific Discord server

How It Works

  1. User visits your event request form at your configured website
  2. User logs in with Discord (OAuth authentication)
  3. System validates guild membership:
    • Checks if user is a member of the target server
    • Non-members see error with invite link (if configured)
    • Only server members can proceed
  4. User submits event details:
    • Simple Mode (default): Title, description, time only. Moderators assign channels during approval.
    • Advanced Mode (opt-in): Full control including channel selection from admin-configured whitelists.
  5. System revalidates membership at submission (prevents edge case of leaving server after login)
  6. Request appears in your moderation channel with Approve/Deny buttons
  7. Moderator clicks a button to approve or deny
  8. If approved, Discord Scheduled Event is created automatically

Security: Guild Membership Required

The bot validates that users are actual members of your Discord server both when they log in and when they submit the form. Non-members see a friendly error message with an invite link to join. This prevents spam from users outside your community.

Default Behavior

By default, event requests use Simple Mode: users submit event ideas and moderators handle channel logistics. This reduces decision fatigue and simplifies the user experience. Enable Advanced Mode with /eggshen-config-events event-requests allow-user-channel-selection allow:true to let users select channels.

Deployment Model

One Website = One Discord Server

Each event request form deployment is dedicated to one specific Discord server. If you host the bot for multiple servers, each server should deploy its own instance of the web form on their own domain.

Example:

  • moviefans.example.com → Movie Fans Discord Server
  • yourdomain.com → Your Discord Server

Setup Requirements

Prerequisites

This feature requires:

  • A web server to host the event request form (e.g., yourdomain.com)
  • Discord Client Secret from the Developer Portal
  • Web server configuration (e.g., nginx)
  • Additional environment variables

Step 1: Discord Developer Portal Setup

CRITICAL: OAuth Redirect URI

You MUST add the redirect URI to Discord Developer Portal BEFORE testing! Without this, users will get "Invalid OAuth2 redirect_uri" errors when trying to login.

  1. Go to Discord Developer Portal
  2. Select your bot application
  3. Navigate to OAuth2 section
  4. Click Add Redirect and enter:
    • For local testing: http://localhost:3000/api/auth/discord/callback
    • For production: https://yourdomain.com/api/auth/discord/callback
    • The URL MUST match your OAUTH_REDIRECT_URI environment variable exactly
  5. Click Save Changes (don't forget this!)
  6. Copy your Client Secret from the OAuth2 page (keep it secure!)

Step 2: Environment Variables

Add these to your .env file:

env
# Required for event requests
DISCORD_CLIENT_SECRET=your_client_secret_here

# API Configuration
API_PORT=3000
OAUTH_REDIRECT_URI=https://yourdomain.com/api/auth/discord/callback
FORM_URL=https://yourdomain.com
ALLOWED_ORIGINS=https://yourdomain.com

# Powers the moderator "Crop Image" link button — see Moderator section below
EVENT_CROP_LINK_SECRET=your_generated_secret_here
PUBLIC_BOT_URL=https://yourdomain.com

Local Testing

For local testing, use:

env
OAUTH_REDIRECT_URI=http://localhost:3000/api/auth/discord/callback
FORM_URL=http://localhost:PORT
ALLOWED_ORIGINS=http://localhost:PORT

Step 3: Deploy Web Form

The bot includes a web form in the /public folder. Deploy these files to your web server:

  • public/index.html - Event request form
  • public/app.js - Form logic
  • public/style.css - Styling

Required Configuration:

  1. Copy public/config.example.js to public/config.js
  2. Set GUILD_ID to your Discord server's Guild ID:
javascript
window.EGG_SHEN_CONFIG = {
  GUILD_ID: 'YOUR_GUILD_ID_HERE', // Change this!
};

public/config.js is gitignored, so a future git pull on your server will never overwrite it.

Finding Your Guild ID

Run /eggshen-config-events event-requests get-link in your Discord server to see your Guild ID.

Example deployment:

  • Copy config.example.js to config.js in public/ and set GUILD_ID
  • Upload /public contents (including your new config.js) to https://yourdomain.com
  • Ensure the domain matches your environment variables

Reverse Proxy Upload Size Limit

If you're proxying /api/ to the bot through nginx (or a similar reverse proxy) rather than exposing the bot's API port directly, set that proxy's own request body size limit — nginx defaults to 1MB, which silently rejects most real event images before they ever reach the bot. The bot's own upload limit is 8MB, so configure the proxy to allow at least that much:

nginx
location /api/ {
    client_max_body_size 10M;
    proxy_pass http://localhost:3000;
    # ...
}

Without this, image uploads on the web form fail silently — the browser gets a proxy-level 413 Request Entity Too Large error page instead of the bot's normal JSON error response, and nothing shows up in the bot's own logs since the request never reached it.

Step 4: Configure Your Server

Event Requests Disabled by Default

Event requests are disabled by default for all servers. You must enable them using the toggle command below. Until enabled, the event request form will show a "disabled" message to users.

Use /eggshen-config-events event-requests commands in your Discord server:

1. Enable event requests (required first step):

/eggshen-config-events event-requests toggle enabled:true

2. Configure required settings:

/eggshen-config-events event-requests moderation-channel channel:#event-requests
/eggshen-config-events event-requests server-name name:"Your Server Name"
/eggshen-config-events event-requests website-url url:https://yourdomain.com

3. Optional settings:

/eggshen-config-events event-requests invite-url url:https://discord.gg/yourserver
/eggshen-config-events event-requests get-link

This shows your configured Form URL, plus a reminder to set GUILD_ID in public/config.js on your web server if you haven't already.

Share your Form URL with your community!

Configuration Commands

All configuration is done via /eggshen-config-events event-requests subcommands:

View Current Settings

/eggshen-config-events event-requests view

Shows your current configuration including the event request link.

Enable/Disable

/eggshen-config-events event-requests toggle enabled:true
/eggshen-config-events event-requests toggle enabled:false

Turn event requests on or off for your server.

Default State

Event requests are disabled by default. You must explicitly enable them with enabled:true before users can submit requests. When disabled, the event request form shows a message telling users the feature is not available.

Set Moderation Channel

/eggshen-config-events event-requests moderation-channel channel:#event-requests

Choose where event requests will be sent for approval. Must be a text channel.

Set Server Display Name

/eggshen-config-events event-requests server-name name:"My Awesome Server"

This name appears on the event request form.

Set Website URL

/eggshen-config-events event-requests website-url url:https://yourdomain.com

The website where your event request form is hosted.

/eggshen-config-events event-requests invite-url url:https://discord.gg/yourserver

Discord invite link shown on the form. Leave empty to hide.

Allow/Disallow Voice Requests

/eggshen-config-events event-requests allow-voice-requests allow:true
/eggshen-config-events event-requests allow-voice-requests allow:false

Control whether users can request voice/stage channels for events. When disabled, all events will be text-channel only.

Use cases for disabling:

  • Server primarily uses text chat for watch parties
  • Voice channels are reserved for specific purposes
  • Simplify the event request form

Get Configuration Summary

/eggshen-config-events event-requests get-link

Shows your form URL and reminds you to configure the GUILD_ID in your web form deployment.

Optional: Choose Simple or Advanced Mode

Simple Mode (default):

/eggshen-config-events event-requests allow-user-channel-selection allow:false

Users submit basic event details only (title, description, time). The form hides channel selectors and shows: "Moderators will select the channels when approving your event." Moderators assign channels during approval.

Best for:

  • New communities still establishing channel structure
  • Servers with dedicated event coordinators
  • Reducing decision fatigue for casual users
  • Simplifying the submission process

Advanced Mode (opt-in):

/eggshen-config-events event-requests allow-user-channel-selection allow:true

Users select specific text/voice channels from admin-configured whitelists (see below). Form shows Location field (required) and optional Voice Channel checkbox.

Best for:

  • Experienced communities with established channels
  • Power users who understand channel structure
  • Community-driven events where users know best location
  • Servers where moderators prefer not to assign channels

Recommended Default

Keep Simple Mode enabled by default. It provides the best experience for most users by removing the complexity of channel selection. Only switch to Advanced Mode if your community specifically requests more control.

Optional: Whitelist Allowed Channels

Advanced Mode Only

Channel whitelisting only applies when Advanced Mode is enabled. In Simple Mode, moderators assign channels during approval, so whitelisting is not needed.

By default (in Advanced Mode), users can select from all text channels and all voice/stage channels in your server. You can restrict this to specific channels:

Whitelist text channels:

/eggshen-config-events event-requests set-allowed-text-channels channel-ids:"123456,789012,345678"

Whitelist voice channels:

/eggshen-config-events event-requests set-allowed-voice-channels channel-ids:"111222,333444"

Allow all channels (reset to default):

/eggshen-config-events event-requests set-allowed-text-channels channel-ids:"all"
/eggshen-config-events event-requests set-allowed-voice-channels channel-ids:"all"

When to Use Channel Whitelisting

Good use cases:

  • Limit events to dedicated watch party channels (#movie-night, #tv-night)
  • Exclude announcement or admin-only channels
  • Simplify dropdown for servers with 50+ channels
  • Prevent accidental selection of inappropriate channels

How to get Channel IDs:

  1. Enable Developer Mode in Discord Settings → Advanced
  2. Right-click any channel → Copy Channel ID
  3. Paste IDs comma-separated (no spaces)

Independent Controls

Text and voice whitelists are independent:

  • You can limit text channels while allowing all voice channels
  • Or vice versa
  • Or limit both
  • Empty list (default) = all channels of that type allowed

User Experience

Submitting a Request

  1. Visit the link provided by server admins
  2. Click "Login with Discord" to authenticate
  3. Fill out the form:
    • Event title (required)
    • Description (optional)
    • Event image (optional) - Upload an image file or paste an image URL (not both) — either way you'll get a chance to crop it
    • Location (required) - Text channel for the watch party
    • Voice channel (optional) - Check the box to add voice/stage channel
    • Start date and time (required)
    • End date and time (optional)
    • Frequency (optional: Once, Weekly, Biweekly, Monthly)
  4. Click "Submit Request"
  5. Wait for moderator approval

Event Image

Picking a file (PNG, JPEG, GIF, or WebP, up to 8MB) or pasting a direct image URL and clicking Fetch & Crop both upload it right away and load it into a crop tool, pre-framed to match Discord's event cover shape. Providing neither is fine too — moderators can always add or change the image when they approve your request (see below).

Drag to adjust the framing as much as you like — this only updates the preview, not the server, so there's no limit on how many times you re-adjust it. The final framing is uploaded once you submit the form. If you change your mind about the image entirely, click Change Image to clear it and pick a different file or URL.

Channel Selection

Location is where the watch party chat happens (text channel, always required).

Voice channel is optional and only shown if enabled by server admins. Check the box if you want to include a voice/stage channel for the event.

Time Zones

The form automatically detects and shows your own local timezone next to the time fields (e.g. "Times shown in your local timezone: America/Los_Angeles"), so type the start/end time as it would appear on your own clock — no conversion needed. Once approved, the event is created correctly for everyone: Discord shows scheduled events in each viewer's own local timezone automatically, the same way it handles every other timestamp, so a moderator (or anyone else) sees the right time on their own clock too, regardless of where they are.

For Moderators

When a request is submitted:

  1. Request appears in moderation channel with all details
  2. Shows location (text channel) and voice channel (if requested)
  3. Approval buttons vary based on request:
    • Text-only request: ✅ Approve, ✏️ Edit, or ❌ Deny
    • Request with voice: ✅ Approve Both, 💬 Text Only, ✏️ Edit, or ❌ Deny
  4. Choose approval type:
    • Approve Both → Creates voice channel event with text channel coordination
    • Text Only → Creates text-only event (removes voice channel from request)
    • Edit → Opens a form to correct the title, description, or start/end time — saving it immediately approves the request too (see below)
    • Deny → Opens a form for an optional reason, then removes the request without creating an event

Editing a Request (Immediately Approves It)

If a submitter's title is wrong or the description needs more detail, click ✏️ Edit. This opens a form pre-filled with the current title and description, along with the event's start/end time — update any field and submit.

The Edit form also includes an optional Image URL field. Use it to add an image to a request that didn't include one, or to replace whatever image the submitter provided (upload or URL) — an image URL entered here always wins over the submitter's. Leaving it blank doesn't clear an existing image; there's no separate "remove image" action, only "optionally override it."

Saving the edit both updates the request and approves it in the same step — there's no separate Approve click needed afterward:

  • If the request already has a text channel (the submitter picked one, or advanced mode isn't in use), saving creates the event right away, using a voice channel too if one was requested.
  • If no channel is set yet, saving shows the same channel-selection step Approve normally shows — pick a channel there and the event is created from your edited title/description.

If you want to review the edit before it goes live, or need to change something Edit doesn't cover, deny the request instead and ask the submitter to resubmit — editing is meant for quick corrections a moderator is comfortable approving outright, not a staging step.

The Edit form also includes Start Time (UTC) and End Time (UTC, optional) fields, pre-filled with the request's current schedule. Both use a strict YYYY-MM-DD HH:mm 24-hour format (e.g. 2026-09-15 20:00) and are always interpreted as UTC — not your local time or the server's — since a modal has no timezone picker; convert to UTC before typing a value. Leaving End Time blank means the event has no set end time. Start Time must be in the future and, if both are set, End Time must be after Start Time — an invalid or past value is rejected with an explanation and nothing is saved. Discord's own event page (the confirmation's Event URL) still displays the final schedule correctly in every viewer's own local timezone automatically — this is Discord's own behavior (every scheduled event is created in the creator's local time and shown back to each viewer in theirs, with no server-wide timezone concept at all), not something this bot computes.

Cropping or Replacing the Image

Every event request in the moderation channel shows a 🖼️ Crop Image link button alongside Approve/Edit/Deny (if PUBLIC_BOT_URL and EVENT_CROP_LINK_SECRET are configured — see setup guide). Clicking it opens a small page — no login needed, the link itself is what authorizes you — where you can:

  • Adjust the framing of whatever image the submitter already provided
  • Upload a completely different image and crop that instead
  • Add an image to a request that didn't have one at all

The link is tied to that one request and works for about 30 minutes; clicking Edit again on the request generates a fresh one if it's expired. Once you save a crop, it becomes the image used when the request is approved — this is a separate, more visual alternative to typing a plain URL into the Edit modal's Image URL field, which still works too for a quick direct swap.

Cropping From the True Original

The crop page always loads the submitter's original, uncropped upload — not their already-cropped result — so you can reframe it freely without compounding crops on top of crops. If you upload a different image instead, that becomes the new original for any future re-crop.

The moderation-channel embed shows a 🖼️ Image field (✅ Uploaded, 🔗 Linked (URL), or ❌ None) so you can see at a glance whether a request has an image before deciding whether to crop, override, or leave it as-is.

Denying with a Reason

Clicking ❌ Deny opens a form with an optional reason field. Whatever you enter (or leave blank) is:

  • Shown on the moderation-channel embed, so other moderators can see why without asking
  • Sent as a direct message to the person who submitted the request, so they're not left wondering what happened

If the bot can't DM the submitter (for example, they have server DMs disabled), the denial still completes — you'll just see a note that the notification couldn't be delivered.

Everyone Sees What Happened

Approving or denying a request updates the original request's message in place (title, color, and buttons change to reflect the outcome) — but a silent edit to an old message is easy to miss if you weren't already looking right at it. On top of that, the bot posts a new message to the moderation channel announcing the outcome:

  • Approved: who approved it and a link to the newly created event
  • Denied: who denied it, and the reason if one was given

This applies to every way a request gets approved — the Approve buttons, picking a channel after Approve, and saving an edit — so the whole moderation team can see what happened and why without having to notice an old message changed.

This is on by default. To turn it off (only the original request message updates, no separate announcement):

/eggshen-config-events event-requests announce-decisions enabled:false

Moderator Permissions

Only members with Manage Events permission or Administrator/Moderator roles can approve/deny/edit requests.

Rate Limiting

Built-in protection against spam:

  • 1 request per 5 minutes per user (by IP address)
  • 10 channel lookups per minute per user
  • 5 image uploads per 5 minutes per user (by IP address)
  • Sessions expire after 24 hours

Limits are scoped per domain, not just per IP — if you run a dev deployment alongside production (see Multiple Servers below), submitting on one domain doesn't use up the other domain's limit, even though both are served by the same bot process.

Security Features

  • ✅ Discord OAuth authentication (no passwords stored)
  • ✅ Session-based login (24-hour expiration)
  • ✅ HTTP-only secure cookies
  • ✅ CORS protection
  • ✅ Rate limiting
  • ✅ Request expiration (7 days)
  • ✅ Uploaded images are validated by type (PNG/JPEG/GIF/WebP) and capped at 8MB
  • ✅ Uploaded images are automatically deleted ~90 days after their event's date has passed (or sooner if the request is never approved)

Customizing for Your Server

Making It Your Own

The event request system is designed to be easily customized:

  1. Set GUILD_ID in public/config.js (copied from public/config.example.js) - Point to your Discord server
  2. Configure server name and invite - Use /eggshen-config-events event-requests commands
  3. Customize styling - Edit public/style.css to match your branding
  4. Deploy to your domain - Host on any web server (Apache, nginx, Netlify, Vercel, etc.)

Multiple Servers (Advanced)

If you host the bot for multiple communities, each should deploy their own instance:

Server 1: Deploy to movienight.com with config.js → GUILD_ID = 'SERVER_1_ID'
Server 2: Deploy to gamenight.com with config.js → GUILD_ID = 'SERVER_2_ID'
Server 3: Deploy to bookclub.com with config.js → GUILD_ID = 'SERVER_3_ID'

Each deployment is independent with its own:

  • Domain/subdomain
  • config.js (gitignored, so a shared repo checkout never overwrites another deployment's Guild ID)
  • Custom branding and styling
  • Separate Discord OAuth configuration

For Bot Hosts

If you're running the bot for multiple servers and want to offer event requests as a feature, provide each server with:

  1. Instructions to deploy the web form files
  2. Their specific Guild ID to set in config.js
  3. Discord OAuth setup guide

Local Testing

Want to test before deploying?

1. Start the Bot

The API server starts automatically:

bash
npm start

You should see: ✓ API server listening on port 3000

2. Serve the Web Form

bash
# Using Python
cd public && python3 -m http.server 8080

# OR using Node.js http-server
npx http-server public -p 8080

3. Configure for Testing

env
# .env
OAUTH_REDIRECT_URI=http://localhost:3000/api/auth/discord/callback
FORM_URL=http://localhost:PORT
ALLOWED_ORIGINS=http://localhost:PORT

Don't forget to add http://localhost:3000/api/auth/discord/callback to Discord Developer Portal!

4. Test the Flow

  1. Copy public/config.example.js to public/config.js and set GUILD_ID to your test server's ID
  2. Configure your test server:
# In Discord
/eggshen-config-events event-requests toggle enabled:true
/eggshen-config-events event-requests moderation-channel channel:#test-events
/eggshen-config-events event-requests server-name name:"Test Server"
/eggshen-config-events event-requests website-url url:http://localhost:PORT

# Check configuration
/eggshen-config-events event-requests view
  1. Open http://localhost:PORT in browser and test the full flow!

Troubleshooting

"Site can't be reached" Error

  • ✅ Check if the bot is running (API server starts with bot)
  • ✅ Verify API_PORT in .env (default: 3000)
  • ✅ Check firewall rules if deployed to production
  • ✅ Ensure the bot process is running with pm2 status or similar

"Guild not found" Error

  • ✅ Verify the guild ID in the URL is correct
  • ✅ Ensure the bot is a member of that server
  • ✅ Check event requests are enabled: /eggshen-config-events event-requests view

"Invalid OAuth2 redirect_uri" Error

Common Issue

This is the most common error when setting up event requests. It means Discord doesn't recognize your callback URL.

Solution:

  1. Go to Discord Developer Portal
  2. Select your application → OAuth2 → Redirects
  3. Add the exact URL from your OAUTH_REDIRECT_URI environment variable
  4. Click Save Changes (required!)
  5. Wait 1-2 minutes for changes to propagate
  6. Try logging in again

Common mistakes:

  • ❌ Forgetting to click "Save Changes" in Discord portal
  • ❌ Using HTTP instead of HTTPS in production
  • ❌ Typo in the URL (even /callback vs /callbacks matters)
  • ❌ Port number mismatch

Validate your configuration:

bash
npm run validate-oauth

OAuth Redirect Mismatch

  • OAUTH_REDIRECT_URI in .env must exactly match Discord Developer Portal
  • ✅ Check for HTTP vs HTTPS mismatches
  • ✅ Verify port numbers match
  • ✅ Restart the bot after changing .env
  • ✅ Run npm run validate-oauth to check configuration

Requests Not Appearing

  • ✅ Check moderation channel is set: /eggshen-config-events event-requests view
  • ✅ Verify the bot's role has View Channel, Send Messages, and Embed Links in that specific channel — a channel-level permission override (common in mod-only channels) can block the bot even when its server-wide role looks correct
  • ✅ Check bot logs for errors

"Failed to Create Event: Missing Permissions" on Approval

  • ✅ The bot's role needs the server-wide Manage Events permission — this is separate from the moderation-channel permissions above and isn't something you can grant per-channel
  • ✅ Server Settings → Roles → (the bot's role) → enable Manage Events, then retry the approval — no restart needed

CORS Errors

  • ✅ Add your website URL to ALLOWED_ORIGINS in .env
  • ✅ Restart the bot after changing environment variables
  • ✅ Check browser console for specific CORS errors

Login Not Working

  • ✅ Verify DISCORD_CLIENT_SECRET is set correctly in .env
  • ✅ Check redirect URI matches in Discord Developer Portal
  • ✅ Clear browser cookies and try again
  • ✅ Check bot logs for OAuth errors

API Endpoints

The bot exposes these endpoints for the event request system:

EndpointMethodDescription
/api/healthGETHealth check
/api/guild-config/:guildIdGETGet server configuration
/api/auth/discordGETStart OAuth flow
/api/auth/discord/callbackGETOAuth callback handler
/api/auth/sessionGETCheck current session
/api/auth/logoutPOSTLogout user
/api/channels/:guildIdGETList voice/stage channels
/api/event-request/upload-imagePOSTUpload an event image ahead of submission (returns a token)
/api/event-requestPOSTSubmit event request
/crop/:requestIdGETModerator crop page (signed-token gated)
/crop/:requestId/current-imageGETThe request's currently-attached image, for pre-loading the cropper
/crop/:requestId/savePOSTSave a moderator's cropped image (single-use signed token)

Best Practices

For Server Admins

  • 📝 Test locally first before going live
  • 🔒 Keep your client secret secure - never share it
  • 📢 Share the link in an announcement channel
  • 👀 Monitor the moderation queue regularly
  • ⚙️ Set clear guidelines for acceptable events

For Web Hosting

  • 🔐 Use HTTPS in production for security
  • 🌐 Use a memorable domain that matches your community brand
  • 📊 Monitor traffic and adjust rate limits if needed
  • 💾 Back up your configuration regularly
  • 🎨 Customize the styling to match your server's theme

Example Use Cases

Movie Watch Party Server

Server Name: "Friday Horror Nights"
Invite URL: https://discord.gg/horrorlovers
Website: https://horrornights.com
GUILD_ID: '1234567890123456789'

Users submit horror movie requests for Friday watch parties.

Gaming Community

Server Name: "Speedrun Central"
Invite URL: https://discord.gg/speedruns
Website: https://speedrungaming.com
GUILD_ID: '9876543210987654321'

Coordinate speedrun events and tournaments. Website: https://speedruncalendar.com


Users can request streaming/racing events in the server.

### Anime Club

Server Name: "Anime Watch Party" Invite URL: https://discord.gg/animewp Website: https://animewatchparty.com


Members submit anime screening requests for weekly viewings.

## Support

Need help setting this up? Check:

- [Technical Setup Guide](https://github.com/r3volution11/Egg-Shen-Bot/blob/main/EVENT_REQUEST_SETUP.md)
- [Bot Installation](./installation.md)
- [Configuration Guide](./configuration.md)
- [GitHub Issues](https://github.com/r3volution11/Egg-Shen-Bot/issues)

::: info Note
The Event Request System is an optional feature. Your bot works perfectly without it! This is for communities that want to formalize their event submission process.
:::

Egg Shen — A Discord bot for movie, TV, video and board gaming, and book communities.