[Open source] Discourse reply-to-view/login-to-view plugin (discourse-reply-to-view): Reply to unlock hidden content

Preface

Recently, while working on my own forum (bbs.eeclub.top), 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 in the post. On the one hand, this can encourage forum activity; on the other, it helps prevent people from taking content without contributing.

Discourse itself only has a [hide] tag (which hides content from anonymous users), so it doesn’t meet the need for “reply to unlock” functionality at all. After searching online and finding no ready-made plugin, I decided to write one myself, naming it discourse-reply-to-view. The entire project was developed with assistance from AI (GLM). I handled requirements, testing, and acceptance. The backend, frontend, and tests total more than 2,000 lines of code, and the development experience was remarkably smooth.

Recommendations and introductions to various AI large-model API platforms: https://blog.zeruns.com/archives/947.html

Plugin Introduction

Discourse-reply-to-view is a Discourse content-protection plugin that provides three BBCode tags:

Tag Function
[reply-visible]content[/reply-visible] Reply to view: visible only after replying in the current topic
[​login-visible]content[​/login-visible] Login to view: visible after registering or logging in
[reply-visible=N]content[/reply-visible] Reply count: visible after posting at least N replies in the current topic (optional feature)

Markdown can be nested normally inside the tags. Code blocks, images, links, and lists are unaffected. The two tags can also be placed on a single line ([reply-visible]xxx[/reply-visible]).

Plugin Screenshots

Plugin appearance in the editor:

Appearance on article pages, with support for multiple languages:

Main Features

  • Server-side permission checks: Visibility of hidden content is determined 100% on the server. The cooked field in the database does not store any hidden source text. Search indexes, notification emails, daily digests, Onebox previews, topic-list summaries, RSS, and the raw export API are all protected against leaks. This is not a fake frontend-only concealment using CSS.
  • Three unlocking modes: any_reply (any reply unlocks everything, by default), exact_post (you must reply to the post containing the content), and count mode [reply-visible=N]
  • Trust-level exemptions: Users at or above a specified TL can view content without replying (disabled by default)
  • Usage permission control: Restrict the use of hidden-content tags to users above a specified trust level
  • Editor integration: The editor’s “+” menu includes two insertion buttons on both desktop and mobile. Clicking one automatically wraps the selected text.
  • WYSIWYG for authors: When authors view their own posts, hidden content is shown with a dashed border and an info bar, making it easy to confirm the tag’s position.
  • Automatic unlocking after replying: After a successful reply, the page automatically refreshes the relevant section, without requiring a manual full-page refresh.
  • Protection against misplaced injection: Each hidden block includes a fingerprint check. If the values do not match during cooking and serialization, the entire post is downgraded to a placeholder. It is better to hide content than inject it in the wrong place.
  • Full language support: All 49 languages supported by Discourse are included, with both frontend text and backend settings translated (including Simplified Chinese, English, Japanese, and Korean).
  • Multilingual content leak protection: Sites with content localization enabled (automatic post translation) are also handled, preventing translated versions from bypassing permissions and exposing hidden content.

Usage

Insert tags through the “+” menu in the editor, or write the BBCode directly:

[reply-visible]
Write content here that can only be seen after replying, such as a download link
[/reply-visible]

[​login-visible]
Write content here that can only be seen after logging in
[​/login-visible]

[reply-visible=5]
Content that can only be seen after posting 5 replies (count syntax must be enabled in the admin panel)
[/reply-visible]

Frontend behavior: users who do not meet the requirements see a placeholder box (reply-to-view uses a blue theme, while login-to-view uses a green theme). Clicking the button takes them directly to the reply box or login page. Once the requirements are met, the full content is displayed automatically.

Security Design

Security is what I value most about this plugin. After all, “fake concealment” (hiding content on the frontend while leaving all the data available) is essentially useless. Its security architecture is designed as follows:

  1. No source text in storage: During Markdown rendering, the source text inside the tags is discarded, and the database stores only a fingerprinted placeholder container, <div class="rtv-block">;
  2. Injection during serialization: The source text is dynamically injected according to the current user’s permissions only when the API response is serialized. If the content is not unlocked, it cannot be retrieved—not even from the HTML source;
  3. All output channels blocked: Vulnerable channels such as raw exports, search indexes, email notifications, revision-history diffs, and localized translation versions are all covered;
  4. No cache-based leak window: Permissions are checked in real time and take effect immediately after a reply. Low-permission users cannot receive high-permission cached content.

Installation

For Discourse deployed using the official Docker setup, edit containers/app.yml and add one 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 must be recompiled, so you must rebuild rather than restart):

cd /var/discourse
./launcher rebuild app

After installation, the following settings can be adjusted under Admin → Settings → Plugins:

  • enable_rtv: Master plugin switch
  • reply_to_view_mode: Reply-to-unlock mode (any reply / exact post)
  • reply_to_view_allow_count: Whether to enable [reply-visible=N] count syntax
  • min_trust_level_to_bypass: Trust-level exemption (0 = no exemption)
  • min_trust_level_to_use: Minimum trust level allowed to use hidden-content tags

Compatibility

Developed and tested on the latest version of Discourse (the master branch, built in 2026). It depends on the newer markdown-it BBCode system, and Discourse 3.4 or later is recommended. Testing includes 79 RSpec cases covering the permission matrix and various leak vectors; all tests pass consistently when run in randomized order.

Related Tutorials

Project Repository and Demo

If you find it useful, please support the project by giving it a Star on GitHub~

Conclusion

This project was also an experiment in “AI-led development with human acceptance testing”: I defined the requirements, edge cases, and security scenarios, while AI generated the code, test cases, and documentation. Along the way, it uncovered several leak vectors I hadn’t even considered myself, including the raw export API, revision-history diffs, and translated versions. AI really is more reliable than humans at enumerating edge cases of this kind. If you enjoy tinkering with forums, give it a try. If you encounter any issues, feel free to open an issue on GitHub or leave a comment.

Recommended Reading

English Version of the Article: https://blog.zeruns.top/archives/100.html

2 Likes

It seems like all of this used to be here before.