TL;DR: A small console app that clones a template category on your Discord server (guild) into a brand‑new category — including text, announcement/news, voice, and forum channels, their topics, slow‑mode, bitrate/user limits, permission overwrites, and (for forums) available tags via a REST patch.
-
✅ Connects to your Discord server using your bot token.
-
✅ Finds a source (template) category by name (from
appsettings.json). -
✅ Creates a new category (you enter its name at runtime).
-
✅ Clones channels in the same order as in the template:
- Text channels — name, topic, slow mode, NSFW; new channels inherit permissions from the category by default.
- Announcement/News channels (type 5).
- Voice channels — name, bitrate, user limit.
- Forum channels — name, topic, default sort order, and available tags (patched via Discord REST API v10).
-
👥 Optional per-channel roles —
Discord:SpecialChannelRolescan create an extra Discord role per matching channel (see below); the role is named{NewCategoryName} {RoleSuffix}and is granted only on that cloned channel (not on the whole category). -
🔒 No secrets in Git —
Discord:TokenandDiscord:ServerIdbelong indotnet user-secrets(or private env overrides), not in committedappsettings.json.
DiscordArchitect/
├─ Program.cs
├─ DiscordArchitect.csproj
├─ appsettings.json
├─ Configuration/ — OptionsBuilder, ServiceConfiguration, command-line config helpers
├─ Options/
│ ├─ DiscordOptions.cs
│ └─ SpecialChannelRoleOptions.cs
├─ Hosting/
│ └─ DiscordHostedService.cs
├─ DiscordFactories/
│ ├─ DiscordClientFactory.cs
│ └─ RestClientFactory.cs
├─ Logging/ — Serilog wiring, structured events
├─ Services/
│ ├─ CategoryCloner.cs
│ ├─ CleanupService.cs
│ ├─ ConfigurationValidator.cs
│ ├─ VerificationService.cs
│ ├─ ForumTagService.cs
│ ├─ PermissionPlanner.cs
│ ├─ DiagnosticsService.cs
│ ├─ Prompt.cs
│ └─ Pure/ChannelOrdering.cs
└─ Utils/HttpExtensions.cs
Responsibilities
Program.cs— builds the Host, wires DI, reads config (UserSecrets + appsettings), starts hosted service.DiscordHostedService— logs in the bot, waits for Gateway Ready, prompts for a new category name, callsCategoryCloner.CategoryCloner— core logic: find template, create target category, toggles (role/@everyone), optional per‑category role, clone channels in order, optional special roles for configured channel names, apply forum tags.PermissionPlanner— helpers for category/channel overwrites (bot, @everyone, per‑category role, per‑channel special role).ConfigurationValidator— validates required Discord settings before startup.VerificationService&CleanupService— post‑run checks and test‑mode teardown (including extra special roles).ForumTagService— PATCH/api/v10/channels/{id}withavailable_tags.DiagnosticsService— prints bot guild permissions & role stack.DiscordClientFactory&RestClientFactory— constructDiscordSocketClientand preconfiguredHttpClient.
- .NET SDK 10.0+
- A Discord Application with a Bot user (Discord Developer Portal).
- Bot invited to the target guild with permissions below.
- If you want to clone Announcement/News channels, the guild must have Community features enabled.
Only the standard Guilds intent is needed. Privileged intents can stay OFF:
- Presence — OFF
- Server Members (GUILD_MEMBERS) — OFF
- Message Content — OFF
Official entry points (bookmark these):
- Discord Developer Portal — Applications — create/select your app, configure the Bot, copy the token.
- Discord Developer Documentation — Introduction — overview of apps, bots, and API concepts.
- OAuth2 and bot accounts — how bot tokens relate to the OAuth2 model.
Steps in the portal:
- New Application → name it (this is only the app name in the dashboard).
- Left sidebar → Bot → Add Bot (if you have not already).
- Under TOKEN, use Reset Token or Copy and store it safely. If it ever leaks, reset it here immediately.
Invite URL pattern (replace YOUR_CLIENT_ID and integer):
https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&scope=bot&permissions=PERMISSIONS_INT
Permission presets
-
Minimal (clone categories/channels): 1040
View Channels (1024)+Manage Channels (16)
https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&scope=bot&permissions=1040 -
Forum‑friendly: 1073742864
Manage Threads (1073741824)
https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&scope=bot&permissions=1073742864 -
If you also create roles: 1342178320
Manage Roles (268435456)
https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&scope=bot&permissions=1342178320
Notes
- The Bot Permissions checkboxes in the Developer Portal only help build the URL; they do not change live server permissions. Real permissions = the bot’s role(s) on the server + the permissions integer used at invite time.
- To create/edit roles, the bot needs a non‑managed role with Manage Roles placed above the roles it will manage.
These two values are what the app reads as Discord:Token (bot token) and Discord:ServerId (numeric guild/server ID).
Bot token → Discord:Token
- Same as in step 1) above: Developer Portal → your application → Bot → copy or reset the token. That string is your
Discord:Token.
Server (guild) ID → Discord:ServerId
- Official help: Discord Support — Where can I find my User/Server/Message ID?
- In the Discord app: User Settings (gear) → App Settings → Advanced → turn Developer Mode On.
- Right‑click your server icon in the left sidebar (not a channel) → Copy Server ID. Store that 17–19 digit string as
Discord:ServerIdvia user-secrets (step 4)).
Invite link only — Client ID (not the same as token)
- Developer Portal → your app → OAuth2 → General → Client ID. Use it in the invite URLs in step 2). More detail: OAuth2 documentation.
4) Secrets (User Secrets) & config
For a public clone of this repo, keep Discord:Token and Discord:ServerId out of Git. Set them locally:
cd DiscordArchitect
dotnet user-secrets init
dotnet user-secrets set "Discord:Token" "YOUR_BOT_TOKEN"
dotnet user-secrets set "Discord:ServerId" "123456789012345678"appsettings.json in the repo stays free of secrets — only non‑sensitive options, safe to commit:
{
"Discord": {
"SourceCategoryName": "Template",
"CreateRolePerCategory": true,
"EveryoneAccessToNewCategory": false,
"SyncChannelsToCategory": true,
"TestMode": false,
"Verbose": false,
"JsonOutput": false,
"SpecialChannelRoles": [
{
"ChannelName": "🐛・testing",
"RoleSuffix": "Testers"
}
]
}
}(Later configuration sources override earlier ones; see DiscordArchitect/Program.cs.)
When you clone a category, any source channel whose name equals ChannelName (exact string match, case‑sensitive / ordinal — same rule as category lookup) triggers:
- After the new channel is created and after
SyncPermissionsAsync()(if enabled), a new role is created:{your new category name} {RoleSuffix}(e.g.Sprint42 Testers). - That role gets a channel‑only permission overwrite (not the parent category): text‑like channels get View + Send; voice gets View + Connect + Speak.
- The bot still needs Manage Roles (see invite presets above). Listing this section in config enables validation: each entry must have non‑empty
ChannelNameandRoleSuffix.
Remove the array or set it to [] if you do not need this behavior. Rename a template channel in Discord → update ChannelName to match.
Exact versions live in DiscordArchitect/DiscordArchitect.csproj. At a glance:
- Discord.Net — Socket + REST client for Discord.
- Microsoft.Extensions.* — hosting, configuration (including user-secrets), logging, options.
- Serilog + sinks / enrichers — structured and file/console logging.
dotnet build
dotnet run# Command line
dotnet run -- --test-mode
# Or via configuration
dotnet run -- --TestMode=trueYou'll see login logs and be prompted: "Enter new category name:". Type it (e.g., Event‑042).
The app will:
- Find the
SourceCategoryNametemplate category. - Create a new category.
- (Optionally) create a same‑named role and grant it access; enforce
@everyonetoggle. - Clone text/news/voice/forum channels in template order.
- For forum channels, PATCH available tags via REST.
When running in test mode (--test-mode or TestMode: true in configuration):
- Resource Tracking: All created resources (category, channels, main category role, and any special roles from
SpecialChannelRoles) are tracked - Post-Run Verification: Automatic verification that all resources exist and have correct permissions
- Verification Prompt: After creation, you'll be asked to verify resources in Discord
- Cleanup Option: You can choose to delete all created resources automatically
- Safe Testing: Perfect for testing configurations without cluttering your server
Test Mode Flow:
🧪 Running in TEST MODE - resources will be tracked for cleanup
✅ Test mode: Resources created successfully!
📁 Category: 123456789012345678
📺 Channels: 3
🧩 Role: 987654321098765432
🔍 Running post-creation verification...
📊 Verification Results:
✅ [Category] Category 'TestCategory' exists and is accessible
✅ [Channel] Channel 'general' exists and is accessible
✅ [Channel] Channel 'voice' exists and is accessible
✅ [Role] Role 'TestCategory' exists
✅ [Permissions] All channels are synced to category
ℹ️ [Channels] Verified 2/2 channels
📋 Verification Summary: 5 ✅ Success, 0 ⚠️ Warnings, 0 ❌ Errors, 1 ℹ️ Info
🔍 Please verify the created resources in Discord, then press ENTER to continue...
🗑️ Do you want to delete the created resources? (y/n): y
🧹 Starting cleanup...
✅ Deleted category: TestCategory (ID: 123456789012345678)
✅ Deleted channel: general (ID: 111111111111111111)
✅ Deleted channel: voice (ID: 222222222222222222)
✅ Deleted role: TestCategory (ID: 987654321098765432)
🎉 Cleanup completed successfully!
The application now includes comprehensive post-run verification that automatically checks:
- Verifies that all created categories, channels, and roles exist
- Checks for any missing or deleted resources
- Validates that channels are properly synced to their category
- Checks @everyone access permissions
- Verifies role permissions on categories
- Ensures channels are visible to appropriate users
- Identifies hidden channels that may need attention
- Validates permission inheritance
- Provides actionable recommendations for permission issues
- Suggests fixes for common configuration problems
- Helps optimize Discord server setup
- Color-coded verification results (✅ Success,
⚠️ Warning, ❌ Error, ℹ️ Info) - Summary statistics of verification findings
- Clear categorization of issues by type
Example Verification Output:
📊 Verification Results:
✅ [Category] Category 'MyCategory' exists and is accessible
✅ [Channel] Channel 'general' exists and is accessible
⚠️ [Channel] Channel 'private' is hidden from @everyone
✅ [Role] Role 'MyCategory' exists
✅ [Permissions] All channels are synced to category
ℹ️ [Channels] Verified 2/2 channels
💡 Recommendations:
• Consider reviewing permissions for 1 hidden channels.
📋 Verification Summary: 4 ✅ Success, 1 ⚠️ Warnings, 0 ❌ Errors, 1 ℹ️ Info
DiscordSocketClientwithGatewayIntents.Guildsonly.- Clone with
CreateTextChannelAsync,CreateNewsChannelAsync,CreateVoiceChannelAsync,CreateForumChannelAsync. - Permission model: new channels are created without per‑channel overwrites → they inherit from the new category; optionally call
SyncPermissionsAsync()on each created channel. - Forum tags: PATCH
available_tagswithAuthorization: Bot <token>.
- Never commit your token or ServerId.
- If your token leaks, reset it in the Developer Portal → Bot → Reset Token.
- Store secrets in a password manager; this workflow expects
Discord:TokenandDiscord:ServerIdin UserSecrets, not in tracked files.
.gitignore essentials:
.vs/
bin/
obj/
TestResults/
appsettings.Development.json“❌ Token not found” / validation says Discord:Token is required
Run dotnet user-secrets set "Discord:Token" "YOUR_BOT_TOKEN". For where the value comes from, see step 3) above.
“❌ ServerId not found” / Discord:ServerId is required
Run dotnet user-secrets set "Discord:ServerId" "123456789012345678". See step 3) for Copy Server ID.
“❌ Server not found” / Bot offline
Ensure the bot is invited and not disabled; verify the ServerId.
Category not found
Check the exact Discord:SourceCategoryName (case‑sensitive match on the raw string).
Forum tags didn’t clone
The app clones the forum channel, then PATCHes available_tags. Ensure Manage Channels (and usually Manage Threads).
Announcement channel creation fails (403/50013) The guild must have Community enabled; verify Manage Channels.
Role creation fails — Discord.Net.HttpException 50013: Missing Permissions
Give the bot a non‑managed role with Manage Roles (268435456) placed above target roles; or reinvite with the 1342178320 preset.
Namespace collision: DiscordArchitect.Discord vs Discord (Discord.Net)
If you ever see “The type or namespace name 'Net' does not exist in the namespace 'DiscordArchitect.Discord'”, qualify the exception as global::Discord.Net.HttpException or rename your app folder (e.g., Infrastructure).
Why don’t Bot Permissions checkboxes persist in the Developer Portal? They only build the invite URL; they don’t change live server permissions. Adjust the bot’s server role instead.
Rate limits Discord throttles bursts; the client handles it. Large templates may take a bit.
- Clones structure & metadata, not messages/threads content.
- Only same‑guild cloning (no cross‑guild).
- With
SyncChannelsToCategory = true, individual overwrites from the template are not copied; inheritance from the new category is enforced.
Use the committed DiscordArchitect/appsettings.json as the source of truth for keys such as Verbose, JsonOutput, and SpecialChannelRoles. Minimal shape:
{
"Discord": {
"SourceCategoryName": "Template",
"CreateRolePerCategory": true,
"EveryoneAccessToNewCategory": false,
"SyncChannelsToCategory": true,
"TestMode": false,
"Verbose": false,
"JsonOutput": false,
"SpecialChannelRoles": []
}
}Set Discord:Token and Discord:ServerId with dotnet user-secrets (see Install & Configure step 4)); do not add them here in a public fork.
# Build & run
dotnet build
dotnet run
# Initialize secrets
dotnet user-secrets init
# Set secrets
dotnet user-secrets set "Discord:Token" "YOUR_BOT_TOKEN"
dotnet user-secrets set "Discord:ServerId" "123456789012345678"
# Tests
dotnet test DiscordArchitect.Tests/DiscordArchitect.Tests.csprojHappy cloning.