A CTFd 3.8 theme for hosting a large archive of challenges from many events
(every SunshineCTF since 2017, plus Hack@UCF's own originals) on one CTFd. It's
a fork of CTFd's core theme (core-beta) that replaces the challenge board
with a browser built for hundreds of challenges. Every other page is unchanged.
- Event tabs: All, then one tab per event (SunshineCTF 2025 ... 2017), then Hack@UCF Originals. Each tab shows your solved/total count.
- Filters: search by name, category, or tag (press
/to focus it), filter by solved/unsolved, and pick categories and difficulty levels. Each option shows how many challenges match the current filters. - Sorting: default order, name, most or least solved, points, difficulty, or event.
- Board view: compact cards with points, solve count, and difficulty, grouped by event, category, difficulty, or not at all. Groups collapse and show a progress bar.
- List view: a sortable table in the style of picoCTF's challenge list.
- Shareable URLs: the view lives in the query string, for example
/challenges?event=2019&category=Pwn&status=unsolved&view=list. - Remembered preferences: each player's view, sort, grouping, and collapsed groups are kept in their browser.
- Random: opens a random unsolved challenge from the current list.
- Challenge modal: shows the category, event, and difficulty.
Everything comes from challenge tags, so no CTFd plugin or database change is needed:
| Tag | Meaning |
|---|---|
sunshinectf-2019 |
Belongs to event "SunshineCTF 2019". The name template is a theme setting. |
event:<name> |
Belongs to the event <name>, which also gets its own tab. Use this for events that aren't a SunshineCTF year. |
beginner, intermediate, expert |
Difficulty. difficulty:<level> also works, with 1-3 or an alias like easy, medium or hard. Difficulty filters appear once any challenge has one. |
(no sunshinectf-YYYY or event: tag) |
Goes to the "Hack@UCF Originals" tab. |
speedrun / i95 |
The "mini pwn" sub-category. The challenge keeps its real CTFd category (Pwn) but is shown in the shared "Speedrun/I95 (Mini Pwn)" group — the series was called Speedrun through 2023 and I95 since 2024 — to mirror the original SunshineCTF boards. Hidden from the tag chips. |
The challenge cards and modal hide these tags. Any other tags show as normal
tags. The SunshineCTF archive's challenge.yml files carry the
sunshinectf-YYYY tag, and every challenge there and in the Hack@UCF originals
has a beginner, intermediate or expert tag.
Descriptions can also list handouts that the web server hosts outside CTFd, as
Markdown list items holding only a link under /files/ on the same site:
* [rot13](/files/sun/18/Pwn/Rot13/rot13)The challenge modal moves those items out of the description and shows them as
file buttons, like uploaded files. The dev compose file's files profile adds
an nginx front on port 8080 that serves them from the workspace's
publish/challenges, like production.
Set these in CTFd at Admin → Config → Theme → Theme Settings. The core theme's settings still work.
- Event Name Template: label for
sunshinectf-YYYYtags, where{year}is replaced. Default:SunshineCTF {year}. - Originals Label: default
Hack@UCF Originals. - Event Tab Order: newest or oldest first.
- Originals Tab Position: before or after the events.
- Default Tab: All, or the first event.
- Default View: board or list.
- Category Aliases: one
From = Toper line, used to merge category names that differ between years. For example,RE = Reversingshows any "RE" challenges under "Reversing". - Challenge Order: the core theme's custom sort function, still honored. It sets the "Default" sort order.
- Category Order: the core theme's custom sort function, still honored. It orders the category filters and category groups.
CTFd loads themes from CTFd/themes/<name>/, and the built static/ directory
is committed. Copy or mount this repo there, for example as
CTFd/themes/hackucf, then select it under Admin → Config → Theme. With the
official Docker image:
volumes:
- ./theme:/opt/CTFd/CTFd/themes/hackucf:royarn install
yarn build # build assets into static/ (yarn dev = watch mode)
yarn test # unit tests for the filtering/sorting model (node --test)
yarn lint # prettierdev/ runs a disposable local CTFd (official ctfd/ctfd:3.8.8 image) with
this repo mounted as the hackucf theme:
docker compose -f dev/compose.yml up -d # http://127.0.0.1:8000
python3 dev/setup.py # setup wizard + admin token -> dev/.env
. dev/.env
(cd ../challenges/sun && ctf challenge install) # the SunshineCTF archive
(cd ../challenges/hackucf && ctf challenge install) # the Hack@UCF originals
python3 dev/demo.py # optional FAKE data: see belowdev/demo.py exists only to exercise the UI. It creates player accounts with
random solves, so solve counts and solved states have something to show. Logins
are admin / admin-password-dev and player / player-password-dev. To
reset everything, run docker compose -f dev/compose.yml down -v.
Template edits show up on reload. After changing JS or SCSS, run yarn reload
(build and restart CTFd), because CTFd caches the asset manifest.
assets/js/archive/model.js: tag parsing, filtering, sorting, and grouping. It's pure functions, unit tested intest/.assets/js/archive/browser.js: theChallengeBrowserAlpine component, covering URL state, preferences, and the challenge modal.templates/challenges.html: page markup.templates/challenge.html: modal markup, with the context line and tag filtering.assets/scss/includes/components/_archive.scss: styles.templates/config.html: admin theme-settings form.
To update from upstream, diff against CTFd/themes/core in CTFd. This fork
started at CTFd commit 6b01b531 (3.8.8).
Apache 2.0, the same as CTFd's core theme. See LICENSE.