Foreword
While tinkering with my own forum (bbs.eeclub.top) recently, I needed a "reply to view" feature: members must reply to a topic before they can see hidden resource links, download addresses, and other content within the post. On one hand, this encourages forum activity; on the other hand, it prevents content from being outright freeloaded.
Discourse only ships with a [hide] tag (which hides content from anonymous users), which is nowhere near enough for a "reply to unlock" requirement. I searched around and found no existing plugin, so I just wrote one myself, naming it discourse-reply-to-view. The whole project was built with AI assistance (GLM) — I handled requirements, testing, and acceptance, while the frontend, backend, and tests came to over 2,000 lines of code. It was a remarkably smooth development experience.
AI large model API platform recommendations and overview: https://blog.zeruns.com/archives/947.html
Plugin Overview
discourse-reply-to-view is a Discourse content protection plugin that provides three BBCode tags:
| Tag | Function |
|---|---|
[reply]content[/reply] |
Reply to view: only visible after replying to this topic |
[login]content[/login] |
Login to view: visible once registered/logged in |
[reply=N]content[/reply] |
Reply count: visible after posting at least N replies in this topic (optional toggle) |
Markdown nests normally inside the tags — code blocks, images, links, and lists all work fine. Writing both tags inline on one line ([reply]xxx[/reply]) is also supported.
Key Features
- Server-side permission checks: Hidden content visibility is determined 100% on the server. The
cookedfield in the database stores no hidden original text. Search indexes, notification emails, daily digests, Onebox previews, topic list excerpts, RSS, and raw export endpoints are all protected against leaks. This is not fake hiding with frontend CSS. - Three unlock modes:
any_reply(any single reply unlocks everything, default),exact_post(must reply to the specific post containing the content), and count mode[reply=N]. - Trust level exemption: You can configure users at or above a certain trust level to skip replying and view directly (off by default).
- Usage permission control: You can restrict which trust levels are allowed to use the hide tags.
- Editor integration: Two insert buttons are added to the "+" menu, available on both desktop and mobile — one click wraps the selected text.
- Author WYSIWYG: When authors view their own posts, hidden content is shown with a dashed border and a hint bar, making it easy to verify tag placement.
- Auto-unlock after replying: After a user replies successfully, the page refreshes locally — no need to manually reload the whole page.
- Anti-misalignment injection: Each hidden block carries a fingerprint check. If the cooking and serialization stages don't match, the entire post degrades to a placeholder — it would rather hide than misalign.
- Full language support: All 49 languages supported by Discourse are built in, with both frontend copy and admin settings translated (Simplified Chinese, English, Japanese, Korean, etc.).
- Multilingual content leak prevention: Sites with content localization (automatic post translation) are also handled, preventing translated versions from bypassing permissions and leaking hidden content.
Usage
Insert via the "+" menu in the editor, or just write the BBCode by hand:
[reply]
Content here is only visible after replying, e.g. download links
[/reply]
[login]
Content here is visible once logged in
[/login]
[reply=5]
Content visible only after posting 5 replies (requires enabling count syntax in admin settings)
[/reply]
Frontend behavior: users who don't yet meet the condition see a placeholder box (reply-to-view uses a blue theme, login-to-view uses green). Clicking the button jumps straight to the reply box or login page. Once the condition is met, the full content displays automatically.
Security Design
Security is what I care about most with this plugin — after all, "fake hiding" (hiding on the frontend while all the data is still there) is essentially useless. The security architecture is designed like this:
- Zero original text at the storage layer: During Markdown rendering, the original text inside the tags is discarded; the database only stores a fingerprinted placeholder container
<div class="rtv-block">. - Injection at serialization time: The original text is only injected dynamically at API serialization based on the current user's permissions. If you haven't unlocked it, you can't get it — it's not even in the HTML source.
- All exit points sealed: The easily-overlooked holes — raw export, search index, email notifications, revision history diffs, and localized translation versions — are all plugged.
- No cache leak window: Permission checks are computed in real time and take effect immediately after replying, so there's no risk of a low-privilege user hitting a high-privilege cached response.
Installation
For Discourse deployed via official Docker, edit containers/app.yml and add a line under hooks → after_code:
hooks:
after_code:
- exec:
cd: $home/plugins
cmd:
- git clone https://github.com/discourse/docker_manager.git
- git clone https://github.com/zeruns/discourse-reply-to-view.git
Then rebuild the container (frontend assets need recompiling, so you must rebuild, not restart):
cd /var/discourse
./launcher rebuild app
After installation, you can adjust the settings in Admin → Settings → Plugins:
enable_rtv: master switch for the pluginreply_to_view_mode: reply unlock mode (any reply / exact post)reply_to_view_allow_count: whether to enable[reply=N]count syntaxmin_trust_level_to_bypass: trust level exemption (0 = no exemption)min_trust_level_to_use: minimum trust level allowed to use the hide tags
Compatibility
Developed and verified on the latest Discourse (master branch, 2026-09 build). It depends on the new markdown-it BBCode pipeline, so Discourse 3.4 or above is recommended. For testing, 79 RSpec cases cover the permission matrix and each leak surface, all green across consecutive randomized-order runs.
Related Tutorials
- Discourse forum setup tutorial — deploy an open-source Discourse community forum from scratch: https://blog.zeruns.com/archives/919.html
- Discourse AI configuration tutorial — using SiliconFlow API to enable AI features for free: https://blog.zeruns.com/archives/917.html
Project Links & Demo
- GitHub repository: https://github.com/zeruns/discourse-reply-to-view
- Demo forum: https://bbs.eeclub.top/
- Author's blog: https://blog.zeruns.com/
If you find it useful, head over to GitHub and drop a Star to show support~
Summary
This project was also an experiment in "AI-led development with human acceptance": I set the requirements, boundary conditions, and security scenarios, while AI generated the code, test cases, and documentation. Along the way, it even caught several leak surfaces I hadn't thought of myself (raw export endpoint, revision history diff, translated versions) — AI really is more reliable than humans at enumerating these kinds of edge cases. If you're into running forums, give it a try. Questions are welcome — open an issue on GitHub, or leave a comment below.
Recommended Reading
- Affordable & high-value VPS / cloud server recommendations: https://blog.zeruns.com/archives/383.html
- Minecraft server setup tutorials: https://blog.zeruns.com/tag/mc/
- Hermes Agent deployment guide — set up your first AI assistant step by step: https://blog.zeruns.com/archives/939.html
- Weijian W96Pro mini fan review and teardown analysis: https://blog.zeruns.com/archives/954.html
- Ugreen DXP4800Pro NAS unboxing, review, and teardown: https://blog.zeruns.com/archives/949.html
- [Open Source] Pure IP database online query system — IP geolocation lookup, your IP lookup, DNS resolution: https://blog.zeruns.com/archives/944.html
Comment