Discourse Daily Challenges

:information_source: Summary Run time-limited challenges with leaderboards, automated posts, and badge awards.
:hammer_and_wrench: Repository Link GitHub - R23DPrinting/discourse-daily-challenge · GitHub
:open_book: Install Guide How to install plugins in Discourse

Current Version: 1.3.0 (updated 3/27/2026)
License: MIT


The Story

I run a health and wellness community that recently migrated from Discord to Discourse. On Discord, I’d built a custom bot to run monthly fitness challenges — members would post their daily workouts, the bot tracked check-ins, posted weekly leaderboards, and announced winners at the end of each month. It was one of our most popular features.

After the move to Discourse, members started asking for the challenges back. I looked for an existing plugin and came up empty. So I built one.

What started as a quick solution turned into a full-featured challenge system that honestly works better than the original Discord bot. Since it might be useful to other communities, I’m sharing it here.


What It Does

discourse-daily-challenge lets you run structured, time-limited challenges directly in Discourse. Members check in by posting to a designated topic with a hashtag or photo. The plugin tracks everything automatically.

Core Features

:white_check_mark: Flexible Check-ins
Members check in by posting in the challenge topic with the configured hashtag or a photo attachment. Challenges can be configured for daily or weekly check-ins. The check-in window is evaluated in the challenge’s configured timezone, so global communities work correctly.

:bar_chart: Live Leaderboard & Streaks
The admin dashboard shows real-time standings including total check-ins, current streaks, and completion percentage for every participant. Streak labels automatically reflect the challenge interval (“Day streak” vs “Week streak”).

:robot: Automated Leaderboard Posts
Set a day and time for a weekly automated leaderboard post directly to the challenge topic. A final results post is also generated automatically the day after the challenge ends.

:sports_medal: Custom Badges
Define a minimum number of check-ins required to “complete” the challenge. Eligible participants automatically receive a custom Discourse badge when the challenge ends. The badge name auto-populates from the topic title.

:card_index_dividers: Multiple Simultaneous Challenges
Run as many challenges as you want at the same time. Each challenge has its own topic, schedule, time zone, badge, and leaderboard.

:file_folder: Archived Challenges
Completed challenges are preserved in an archived accordion view in the dashboard, so you can look back at historical results.

:hammer_and_wrench: Admin Check-in Management
Admins can manually add or remove check-ins — useful for catching missed posts or resolving disputes.

:pushpin: Manual Leaderboard Trigger
A “Post Leaderboard Now” button lets admins publish a leaderboard update on demand, outside the scheduled window.

:police_officer: Moderator Access
Full site moderators can create, edit, and manage challenges via a dedicated sidebar section. Can be enabled or disabled via site settings.

:card_index_dividers: Category Moderator Access
Category moderators can manage challenges scoped to their assigned categories via a dedicated /challenges route — no admin access required. Can be enabled or disabled independently via site settings.

:robot: ChallengeBot DMs
If a bot account is configured, ChallengeBot automatically sends members a confirmation DM after each check-in (with streak info and a link to the topic), reminder DMs when they haven’t checked in for a few days, and responds to @mention commands in challenge topics with personal stats via DM. Leaderboard and final results posts are also sent from ChallengeBot instead of the system account.


Installation

Add the following to your containers/app.yml under hooks > after_code > exec > cmd:

- git clone https://github.com/R23DPrinting/discourse-daily-challenge.git

Then rebuild:

cd /var/discourse && ./launcher rebuild app

After the initial install, future updates can be applied via the in-panel Update button unless otherwise noted in the changelog.


Configuration

Creating a Challenge

Navigate to Admin → Plugins → Challenges → Discourse Challenges and click New Challenge.

Field Description
Topic ID The Discourse topic where members will check in. The topic title will auto-populate the badge name field.
Hashtag Trigger Required hashtag for check-ins (without #, e.g. workout)
Check-in Interval Daily or weekly check-in mode
Week Start For weekly challenges: which day starts the week (Sunday, Monday, or Saturday)
Start Date First day of the challenge (inclusive)
End Date Last day of the challenge (inclusive)
Challenge Timezone Timezone for evaluating the check-in window (optional, defaults to UTC)
Check-ins Needed Minimum check-ins to earn the completion badge
Description Optional description of the challenge
Enable Weekly Leaderboard Post Toggle automated weekly leaderboard posts
Post Day of Week Day of week for the automated leaderboard post
Post Hour Hour (0–23, in challenge timezone) for the automated leaderboard post
Award Completion Badge Toggle automatic badge awarding at challenge end
Badge Name Name of the badge to create and award — auto-populates from the topic title
Enable Reminder DMs Send reminder DMs to participants who haven’t checked in recently (requires bot username to be configured). Defaults to enabled.

Site Settings

Setting Default Description
daily_challenge_enabled true Globally enable or disable the plugin
daily_challenge_mod_access_enabled true Allow full site moderators to manage challenges
daily_challenge_category_mod_access_enabled true Allow category moderators to manage challenges in their assigned categories
daily_challenge_bot_username "" Username of the bot account that sends DMs and posts leaderboards

How Check-ins Work

  1. A member posts a reply to the challenge topic
  2. The post must contain the configured hashtag or a photo attachment
  3. The plugin checks whether the member has already checked in during the current period (day or week, depending on the challenge interval)
  4. If not, the check-in is recorded and the leaderboard is updated
  5. One check-in per member per period is allowed

Admins can view and manage all check-ins from the admin dashboard.


Moderator & Category Mod Access

Full site moderators can access challenge management via a CHALLENGES section in the main sidebar (if daily_challenge_mod_access_enabled is on).

Category moderators can access a scoped version at /challenges/dashboard and /challenges/challenges — they can only see and manage challenges linked to topics in their assigned categories. This requires daily_challenge_category_mod_access_enabled to be on and the user to be assigned as a category moderator via Admin → Categories → [Category] → Moderation.


Setting Up ChallengeBot

ChallengeBot is optional but enables DM features and attributed leaderboard posts. Skip this section if you don’t need DM functionality.

  1. SSH into your server and run:
cd /var/discourse
./launcher enter app
rake admin:create

When prompted, enter the bot’s email and a strong password. Answer n to admin privileges.

  1. In Admin → Users, find the newly created account and:

    • Click Approve
    • Set Trust Level to 1
    • Update the username to ChallengeBot (or your preferred name)
  2. Optionally set a profile picture for the bot account.

  3. Go to Admin → Plugins → Challenges → Settings and enter the exact username in the Bot username field. Note: this field is case-sensitive.

@ChallengeBot Commands

Members can mention the bot in any active challenge topic to receive stats via DM. The bot never posts in the topic itself.

Command Description
@ChallengeBot status Stats across all active challenges
@ChallengeBot leaderboard Current standings for that challenge
@ChallengeBot streak Your current streak
@ChallengeBot checkins List of all your check-in dates
@ChallengeBot progress Check-ins done vs needed, days remaining
@ChallengeBot help List all available commands

Rate limited to 10 commands per user per hour.


Automated Posts

Weekly Leaderboard

The plugin runs an hourly background job that checks whether it’s time to post the weekly leaderboard. When the configured day + hour arrives (in the challenge timezone), it creates a formatted post in the challenge topic showing the current standings.

Final Results Post

The day after the challenge ends, a final results post is automatically published. It lists all participants, their total check-ins, completion status, and congratulates badge earners.


Database Tables

  • daily_challenges — Challenge definitions
  • daily_check_ins — Individual check-in records per user per challenge per period

Compatibility

  • Discourse 3.x+
  • Tested on self-hosted installations
  • Ruby 3.x, Rails 7.x+

Roadmap (v1.5.0)

No specific features planned yet. Have a suggestion? Open an issue on GitHub or reply to this topic.


Changelog

Show Changelog

v1.4.0

  • ChallengeBot check-in confirmation DMs — automatic DM after each successful check-in with streak info and a clickable link to the challenge topic
  • Reminder DMs — daily challenges nudge after 2+ missed days; weekly challenges nudge on the last day of the week if not yet checked in. Includes current check-in count and goal. Per-challenge toggle, defaults to enabled.
  • @mention commands — members mention the bot in challenge topics to get stats via DM. Never posts in the topic. Commands: status, leaderboard, streak, checkins, progress, help. Rate limited to 10 per user per hour.
  • Leaderboard and final results posts — now posted by ChallengeBot if configured, falls back to system account
  • New site settingdaily_challenge_bot_username
  • New challenge field — reminder DMs toggle (defaults to enabled)

v1.3.0

  • Category moderator access — category mods can create, edit, delete, and manage check-ins for challenges in their assigned categories via /challenges/dashboard and /challenges/challenges — no admin access required
  • Badge name auto-populate — badge name field auto-populates from the topic title when a topic ID is entered
  • Badge name validation — badge name is required when “Award completion badge” is enabled; returns a proper error instead of a 500
  • Separate mod/category mod togglesdaily_challenge_mod_access_enabled and daily_challenge_category_mod_access_enabled can be toggled independently

v1.2.0

  • Configurable check-in interval — run daily or weekly challenges
  • Week start setting — for weekly challenges, choose whether weeks start on Sunday, Monday, or Saturday
  • Interval-aware streaks — streak labels and calculations now reflect the challenge interval
  • Moderator access — full site moderators can manage challenges via a dedicated sidebar section
  • Renamed to “Discourse Challenges” in the admin UI

v1.1.1

  • Multi-challenge dashboard showing all active challenges simultaneously
  • Archived challenges accordion showing historical results

v1.1.0

  • Per-challenge timezone support
  • Weekly post schedule now per-challenge (day, hour, enable/disable toggle)

v1.0.0

  • Initial release

Feedback & Contributions

This plugin was built to solve a real need in our community, and I’m sharing it in case it helps yours. Issues, PRs, and feature requests are welcome on GitHub.

If you’re running a community where accountability and consistency matter — fitness, language learning, writing, creative habits — daily challenges can be a genuinely powerful engagement tool. I hope this plugin makes it easier to bring that to your Discourse community.

:link: GitHub - R23DPrinting/discourse-daily-challenge · GitHub

15 Likes

This is great, thank you for sharing. As far as features, I could see it being useful for us if it was possible to specify the challenge interval as something other than daily, ie, choose a weekly challenge check-in rather than daily across the span of the challenge. It would also be great if mods/category mods could create challenges so it’s not just on admin to set things up.

1 Like

Let me make sure I understand the requests:

So, you’d want to set the challenge up for say 1 month (April 1-30), but specify that it only counts 1 entry per week instead of 1 entry per day? Would you want it to be a Sun-Sat week or every 7 days?

This is a good idea. I honestly don’t have any Mods in my community, so I don’t know how the admin panel looks for them. Let me create a Mod account in my dev environment and play around with it.

1 Like

Yes, exactly. Our community has a 52 in 52 challenge where there’s a weekly challenge for a year, so non-daily checkins would give more flexibility for different kinds of challenges.

Discourse Calendar (and Event) allows admin to set the week starting on Saturday, Sunday, or Monday, so if you want to plan for compatibility with that plugin down the road (like generating calendar events for checkins or start/end date?) I might follow how Calendar sets the week? For my community specifically I don’t think it matters.

Category mods in addition to regular mods would be really great – we have little interest groups that have their own sub categories, and it would be nice to allow those category mods to run separate challenges without making them full mods.

1 Like

These are excellent ideas. Thanks for sharing! I’ll see what I can do to implement them.

1 Like

Does this plugin require the gamification plugin to be enabled, or did you select the tag because your plugin is for gamification in general?

2 Likes

It does not require the plugin. I just selected the tag. Sorry about that.

2 Likes

v1.2.0 is now available!

New in this release:

  • Configurable check-in interval — run daily or weekly challenges
  • Week start setting — for weekly challenges, choose whether weeks start on Sunday, Monday, or Saturday
  • Interval-aware streaks — streak labels and calculations now reflect the challenge interval (“week streak” vs “day streak”)
  • Moderator access — full site moderators can now create, edit, and manage challenges via a dedicated sidebar section. This can be enabled or disabled in Admin → Settings → Plugins (daily_challenge_mod_access_enabled), so communities that prefer to keep challenge management admin-only can do so.
  • Renamed to “Discourse Challenges” in the admin UI
Screenshots

Challenge Form — Check-in Interval

Dashboard — Week Starts On

Moderator Sidebar Access

Note: This release requires ./launcher rebuild app to apply database migrations — the in-panel Update button won’t work for this release.


Regarding category mod access — this is something I’m actively working on. The challenge is that Discourse doesn’t grant category moderators staff status, which means they can’t reach /admin routes at all regardless of what the plugin does. I’m exploring a separate non-admin route specifically for category mods that would give them scoped access to challenges in their categories. Expect this in v1.3.0.

1 Like

I can’t create a challenge it’s returning error 500 for any action

That is strange. I’m running it on 2 different forums without issues, and I’ve done quite a bit of testing. What Discourse version are you on? Are you on the latest plugin version (1.2.0)?

Can you SSH to your server, run a few commands, and post the results?

First, Check the plugin version number:

./launcher enter app
cat /var/www/discourse/plugins/discourse-daily-challenge/plugin.rb | grep version

Next, try creating a challenge. When you get the 500 error, immediately run this in the SSH session:

./launcher enter app
tail -50 /var/www/discourse/log/production.log

Let me know the results, and we can try to figure it out.

Discourse 2026.3.0-latest.1 - https://github.com/discourse/discourse version f2781e1455274ea2c011fd1d161ac2ad9fde72bd

# version: 1.2.0
# required_version: 2.7.0
Started POST "/drafts.json" for 2804:1b0:f442:e48c:3056:bd27:b4ae:52b3 at 2026-0                                                                                        3-26 20:49:05 +0000
Processing by DraftsController#create as JSON
  Parameters: {"draft_key" => "topic_17746", "sequence" => "2", "data" => "{\"re                                                                                        ply\":\"@Discipulo na minha opinião eu não vou me estressar\",\"action\":\"reply                                                                                        \",\"categoryId\":14,\"tags\":[],\"archetypeId\":\"regular\",\"metaData\":null,\                                                                                        "composerTime\":25298,\"typingTime\":3900,\"locale\":\"\"}", "owner" => "fee91ce                                                                                        037e64ec297a500af13091165", "force_save" => "false"}
Completed 200 OK in 11ms (Views: 0.1ms | ActiveRecord: 0.0ms (0 queries, 0 cache                                                                                        d) | GC: 0.1ms)
Started GET "/t/os-melhores-blogs-de-games-no-brasil/15399?__cf_chl_rt_tk=5M6_rd                                                                                        _fq46BL9PZ6VvZRAVdIsJ84aL9XIfa1Re_paQ-1773238724-1.0.1.1-g20DH5dkOVXOIHhKYVpNwYR                                                                                        CQNnUboE4.po6L1TFk1E" for 164.52.120.12 at 2026-03-26 20:49:06 +0000
Processing by TopicsController#show as */*
  Parameters: {"__cf_chl_rt_tk" => "5M6_rd_fq46BL9PZ6VvZRAVdIsJ84aL9XIfa1Re_paQ-                                                                                        1773238724-1.0.1.1-g20DH5dkOVXOIHhKYVpNwYRCQNnUboE4.po6L1TFk1E", "slug" => "os-m                                                                                        elhores-blogs-de-games-no-brasil", "topic_id" => "15399"}
  Rendered layout layouts/application.html.erb (Duration: 107.3ms | GC: 1.4ms)
Completed 200 OK in 192ms (Views: 109.0ms | ActiveRecord: 0.0ms (0 queries, 0 ca                                                                                        ched) | GC: 2.4ms)
Started POST "/drafts.json" for 2804:1b0:f442:e48c:3056:bd27:b4ae:52b3 at 2026-0                                                                                        3-26 20:49:11 +0000
Processing by DraftsController#create as JSON
  Parameters: {"draft_key" => "topic_17746", "sequence" => "3", "data" => "{\"re                                                                                        ply\":\"@Discipulo na minha opinião eu não vou me estressar com o comportamento                                                                                         \",\"action\":\"reply\",\"categoryId\":14,\"tags\":[],\"archetypeId\":\"regular\                                                                                        ",\"metaData\":null,\"composerTime\":31344,\"typingTime\":4600,\"locale\":\"\"}"                                                                                        , "owner" => "fee91ce037e64ec297a500af13091165", "force_save" => "false"}
Completed 200 OK in 11ms (Views: 0.1ms | ActiveRecord: 0.0ms (0 queries, 0 cache                                                                                        d) | GC: 0.0ms)
Started GET "/leaderboard" for 2804:1e68:c201:632a:1161:4504:ea4d:9983 at 2026-0                                                                                        3-26 20:49:15 +0000
Processing by DiscourseGamification::GamificationLeaderboardController#respond a                                                                                        s JSON
Completed 200 OK in 30ms (Views: 0.1ms | ActiveRecord: 0.0ms (0 queries, 0 cache                                                                                        d) | GC: 0.0ms)
Started POST "/drafts.json" for 2804:1b0:f442:e48c:3056:bd27:b4ae:52b3 at 2026-0                                                                                        3-26 20:49:16 +0000
Processing by DraftsController#create as JSON
  Parameters: {"draft_key" => "topic_17746", "sequence" => "4", "data" => "{\"re                                                                                        ply\":\"@Discipulo na minha opinião eu não vou me estressar com o comportamento                                                                                         das outras pessoas \",\"action\":\"reply\",\"categoryId\":14,\"tags\":[],\"arche                                                                                        typeId\":\"regular\",\"metaData\":null,\"composerTime\":36481,\"typingTime\":510                                                                                        0,\"locale\":\"\"}", "owner" => "fee91ce037e64ec297a500af13091165", "force_save"                                                                                         => "false"}
Completed 200 OK in 11ms (Views: 0.1ms | ActiveRecord: 0.0ms (0 queries, 0 cache                                                                                        d) | GC: 0.0ms)
Started GET "/leaderboard/6?period=daily" for 2804:1e68:c201:632a:1161:4504:ea4d                                                                                        :9983 at 2026-03-26 20:49:19 +0000
Processing by DiscourseGamification::GamificationLeaderboardController#respond a                                                                                        s JSON
  Parameters: {"period" => "daily", "id" => "6"}
Completed 200 OK in 20ms (Views: 0.1ms | ActiveRecord: 0.0ms (0 queries, 0 cache                                                                                        d) | GC: 0.0ms)
Processing by UserAvatarsController#show as PNG
  Parameters: {"hostname" => "segredin.com", "username" => "mary", "size" => "28                                                                                        8", "version" => "8962_2"}
Completed 418  in 8ms (Views: 0.2ms | ActiveRecord: 0.0ms (0 queries, 0 cached)                                                                                         | GC: 0.0ms)
Sent file /var/www/discourse/public/uploads/default/optimized/2X/8/86ba5981dfff7                                                                                        6df7de0f2141a1a87ad805e2c5c_2_288x288.jpeg (0.2ms)
Started GET "/latest.json" for 2804:79d4:f008:68f:fd8c:855b:db16:bbee at 2026-03                                                                                        -26 20:49:23 +0000
Processing by ListController#latest as JSON
Completed 200 OK in 143ms (Views: 0.1ms | ActiveRecord: 0.0ms (0 queries, 0 cach                                                                                        ed) | GC: 6.8ms)
Started GET "/t/17746/1.json?track_visit=true&forceLoad=true" for 2804:79d4:f008                                                                                        :68f:fd8c:855b:db16:bbee at 2026-03-26 20:49:31 +0000
Processing by TopicsController#show as JSON
  Parameters: {"track_visit" => "true", "forceLoad" => "true", "topic_id" => "17                                                                                        746", "post_number" => "1"}
Completed 200 OK in 114ms (Views: 0.1ms | ActiveRecord: 0.0ms (0 queries, 0 cach                                                                                        ed) | GC: 1.8ms)
Started POST "/drafts.json" for 2804:1b0:f442:e48c:3056:bd27:b4ae:52b3 at 2026-0                                                                                        3-26 20:49:32 +0000
Processing by DraftsController#create as JSON
  Parameters: {"draft_key" => "topic_17746", "sequence" => "5", "data" => "{\"re                                                                                        ply\":\"@Discipulo na minha opinião eu não consigo k\",\"action\":\"reply\",\"ca                                                                                        tegoryId\":14,\"tags\":[],\"archetypeId\":\"regular\",\"metaData\":null,\"compos                                                                                        erTime\":46059,\"typingTime\":8500,\"locale\":\"\"}", "owner" => "fee91ce037e64e                                                                                        c297a500af13091165", "force_save" => "false"}
Completed 200 OK in 13ms (Views: 0.1ms | ActiveRecord: 0.0ms (0 queries, 0 cache                                                                                        d) | GC: 0.0ms)
Started POST "/drafts.json" for 2804:1b0:f442:e48c:3056:bd27:b4ae:52b3 at 2026-0                                                                                        3-26 20:49:36 +0000
Processing by DraftsController#create as JSON
  Parameters: {"draft_key" => "topic_17746", "sequence" => "6", "data" => "{\"re                                                                                        ply\":\"@Discipulo na minha opinião eu não consigo julgar \",\"action\":\"reply\                                                                                        ",\"categoryId\":14,\"tags\":[],\"archetypeId\":\"regular\",\"metaData\":null,\"                                                                                        composerTime\":50400,\"typingTime\":9100,\"locale\":\"\"}", "owner" => "fee91ce0                                                                                        37e64ec297a500af13091165", "force_save" => "false"}
Completed 200 OK in 10ms (Views: 0.1ms | ActiveRecord: 0.0ms (0 queries, 0 cache                                                                                        d) | GC: 0.0ms)
Started POST "/drafts.json" for 2804:1b0:f442:e48c:3056:bd27:b4ae:52b3 at 2026-0                                                                                        3-26 20:49:37 +0000
Processing by DraftsController#create as JSON
  Parameters: {"draft_key" => "topic_17746", "sequence" => "7", "data" => "{\"re                                                                                        ply\":\"@Discipulo na minha opinião eu não consigo julgar \",\"action\":\"reply\                                                                                        ",\"categoryId\":14,\"tags\":[],\"archetypeId\":\"regular\",\"metaData\":null,\"                                                                                        composerTime\":51596,\"typingTime\":9100,\"locale\":\"\"}", "owner" => "fee91ce0                                                                                        37e64ec297a500af13091165", "force_save" => "false"}
Completed 200 OK in 10ms (Views: 0.1ms | ActiveRecord: 0.0ms (0 queries, 0 cache                                                                                        d) | GC: 0.0ms)
Processing by TopicsController#timings as */*
  Parameters: {"timings" => {"1" => "59036"}, "topic_time" => "59036", "topic_id                                                                                        " => "17746"}
Completed 418  in 8ms (Views: 0.2ms | ActiveRecord: 0.0ms (0 queries, 0 cached)                                                                                         | GC: 0.0ms)

Hmmmm. That logs does not show the 500 Error. When exactly does the error happen? Do you see any error on the screen, or just the generic “Something went wrong” message? Are you making sure to fill out all of the fields? Especially the topic ID?

Can you try creating a challenge again and running this as soon as you see the 500 error? We’ll increate the size a little:

./launcher enter app
tail -100 /var/www/discourse/log/production.log | grep -A 30 "500\|Error\|exception"

(post deleted by author)

Wait I will enable again

Ahhh. I think i see the issue. The badge name is supposed to be pulled form the the topic title, but I think I messed it up on my last update. Can yo try filling out the badge name filed and seeing if it passes? I’ll also push an update to fix once I get home.

2 Likes

Something new? I updated here and nothing yet

Hey. Sorry. I forgot to update this post last night. I did push an update that included the fix for the badges (along with a few other improvements). If you go to Admin>Update Discourse, you should see that there is an update available.

I’ll make a full post with the changes in a bit.

2 Likes

I have sure that I doing this the wrong way

First doubt: the topic ID can be an exist or needs create a new?
I understadood that tag is without #

start and end could be a couple days or weeks or can I set util december per example?

anyway I’ll wait for your explations because I liked this plugin and I am here to provide feedbacks.

It has to be an existing Topic ID that your account can access (I assume you are an admin, so that shouldn’t be an issue). Example: If the URL for the topic is:

https://community.com/t/test-formatting/722

The Topic ID that you would enter is: 722

When you type the number, the name of the topic should appear underneath to confirm you entered the correct ID.

You are correct. Enter the hashtag without the #. If you want to use #testtag, just type testtag in the box.

Start and end dates can be whatever you want.

activerecord (8.0.5) lib/active_record/inheritance.rb:76:in 'ActiveRecord::Inheritance::ClassMethods#new'
plugins/discourse-daily-challenge/app/controllers/discourse_daily_challenge/admin_daily_challenges_controller.rb:26:in 'DiscourseDailyChallenge::AdminDailyChallengesController#create'

my first config

And my recent logs