Automated Sticky Comments is a moderation tool that automatically posts a stickied, locked mod comment on new posts based on their title, body text and/or post flair, using a syntax almost identical to u/AutoModerator. It also features an optional fallback Default Comment for posts that do not match any rule, an optional Global Footer appended to every comment the app posts, and an optional backup of all settings to your subreddit wiki.
While AutoModerator is powerful, it has specific limitations when it comes to reliably posting sticky comments. This app is purpose-built to address them.
Stickied comments posted by AutoModerator are collapsed by default - users see a small bar with the username and have to manually click to expand it. Comments posted by this app are always fully expanded, making your message immediately visible without any action required from the reader.
AutoModerator processes all rules top-to-bottom and stops as soon as a rule takes a moderation action. This means if a removal or filter rule matches first, any rule intended to post a sticky comment will never run.
This app runs independently of AutoModerator. It listens for new post events directly and is not affected by AutoModerator's rule ordering or actions. A post being removed by AutoModerator does not prevent this app from posting its comment. Double-Post protection also prevents the app from triggering on the same post twice.
AutoModerator requires emoji to be written as HTML entities in comment text (e.g. 😀 instead of 😀). This app accepts emoji and Unicode characters directly, both in comment bodies and in regex patterns. Unicode property escapes such as \p{Letter} also work in regex conditions, which AutoModerator does not support at all.
If you are migrating rules from your AutoModerator config, please also check out the Differences from AutoModerator section.
The app features the following settings in your Subreddit App Settings panel:
---. The app checks these rules sequentially from top to bottom (first-match-wins). As soon as a post meets all conditions of a rule, its corresponding comment is posted and all subsequent rules are skipped.Description: A fallback comment posted when an incoming post doesn't satisfy any of your active YAML rules.
Behaviour: If the YAML rules field is empty, or if a post fails to match any rule, this Default Comment is posted instead. Leave this field completely empty if you do not want any fallback comment. Supports markdown and template variables such as ({{author}}, {{subreddit}}, {{title}}, {{url}}).
Sticky Default Comment (toggle, default: on) - pins the Default Comment as a stickied mod comment.
Lock Default Comment (toggle, default: on) - disables replies to the Default Comment.
auto-sticky-rules (containing your raw YAML rules) and auto-sticky-config (containing your Default Comment, its sticky/lock toggles, and your Global Footer). Both pages are set to mods-only visibility when first created.When a user submits a new post, the app processes it through a strict step-by-step order:
onPostSubmit event more than once (or if a glitch retries the event), the app remembers the post ID and will safely ignore the duplicate, ensuring your users never see double bot comments.Two actions are available in the mod menu on any post. Both re-evaluate the post against your current YAML rules and Default Comment at the moment they are triggered, so they always reflect your latest configuration - not the configuration that was active when the post was originally submitted.
Updates an existing app comment on a post to match what the current rules require, touching only what has actually changed.
When to use it:
What it does:
All actions receive a corresponding toast notification.
Posts a brand-new comment on the post and removes any previous app comment.
When to use it:
What it does:
Existing app comments are removed before the new one is posted (if the "Remove previous app comments" setting is enabled, which it is by default), so there is at most one app comment visible at any moment.
Scan coverage note: Due to a limit on how many comments can be fetched, this may not remove all comments on very popular posts. If only one comment exists, its ID is already stored unless the post is over a year old.
All conditions within a single rule block must be true (AND semantics). If you provide multiple text values inside a single condition, matching any one of them is enough to satisfy that condition (OR semantics). A complete settings example is provided in the last section of this readme.
title (contains): keyword # single value
title (contains): [keyword, other] # inline list - any one matches
title (contains): ["need help, urgent", "getting started"]
# inline list - matches strings inside quotes
title (contains): # block list - any one matches
- keyword one
- keyword two
title (not-contains): unwanted
title (starts-with): "[Help]"
title (ends-with): "?"
title (full-exact): Exact Post Title Here
title [regex]: '^\[Discussion\]' # positive match - must match
title [not-regex]: '\bNSFW\b' # negative match - must not match
title (regex): '^\[Discussion\]' # valid positive match alternative (round brackets)
title (not-regex): '\bNSFW\b' # valid negative match alternative (round brackets)
Body checks work identically to title checks but match against the self-text body of a post. Body and title conditions can be freely combined within a single rule.
body (contains): keyword # single value
body (contains): [keyword, other] # inline list - any one matches
body (contains): ["need help, urgent", "getting started"]
# inline list - matches strings inside quotes
body (contains): # block list - any one matches
- keyword one
- keyword two
body (not-contains): unwanted
body (starts-with): "Thanks for"
body (ends-with): "?"
body (full-exact): Exact body text here
body [regex]: '^\[Context\]' # positive match - must match
body [not-regex]: '\bspam\b' # negative match - must not match
body (regex): '^\[Context\]' # valid positive match alternative (round brackets)
body (not-regex): '\bspam\b' # valid negative match alternative (round brackets)
Note: Title and body text matching is always case-insensitive.
Note: The combined title+body AutoModerator syntax is not supported. Placing a title condition and a body condition in the same rule requires both to pass (AND).
To match a value in either field, write two separate rules with the same comment.
Standard contains matches any occurrence of a phrase anywhere in the title - even when that phrase appears as part of a phrase with the opposite meaning. For example, title (contains): Spoilers would match both "Spoilers for Season 2" and "No Spoilers please", because the word "Spoilers" is present in both. Distinct matching lets you distinguish between these cases.
Three conditions work together as a group:
| Condition | Purpose |
|---|---|
title (distinct-contains) |
The requirelist - phrases you want to find |
title (distinct-block) |
The blocklist - phrases that cancel a requirelist match when the matching phrase falls entirely inside them |
title (distinct-also-contains) |
A second requirelist - an additional set of phrases that must also be present somewhere in the title |
How the blocklist works
A requirelist phrase only counts as a match if at least one of its occurrences in the title does not fall entirely within a blocklist phrase. If every occurrence of the requirelist phrase is contained inside a blocklist phrase, the condition fails.
| Title | Matches? | Reason |
|---|---|---|
Spoilers for Season 2 |
✅ Yes | "Spoilers" at the start - outside any blocklist phrase |
No Spoilers please |
❌ No | "Spoilers" appears only inside "No Spoilers" - covered by the blocklist |
Spoilers for DLC1 (No Spoilers for DLC2) |
✅ Yes | The first "Spoilers" is outside the blocklist; the one inside "No Spoilers" is ignored |
(distinct-contains: [Spoilers], distinct-block: [No Spoilers] assumed for the above examples.)
Syntax
title (distinct-contains): [Spoilers, Spoiler]
title (distinct-block): [No Spoilers, No Spoiler, Spoiler Free, Spoiler-Free]
comment: This post has been marked as containing spoilers.
Values follow the same inline and block list syntax as all other text conditions:
title (distinct-contains): # block list form
- Spoilers
- Spoiler
title (distinct-block):
- No Spoilers
- No Spoiler
- Spoiler Free
- Spoiler-Free
title (distinct-block) requires title (distinct-contains) to also be present in the same rule. Using it without a paired requirelist will produce a validation error.
The distinct-also-contains condition
distinct-also-contains requires a second set of phrases to also be present somewhere in the title. Unlike distinct-contains, it does not use the blocklist - it is a plain presence check. Its purpose is to identify titles that contain both the requirelist wording and a second type of wording at the same time.
title (distinct-contains): [Spoilers, Spoiler]
title (distinct-block): [No Spoilers, No Spoiler, Spoiler Free, Spoiler-Free]
title (distinct-also-contains): [No Spoilers, No Spoiler, Spoiler Free, Spoiler-Free]
comment: This post contains a mix of spoiler and spoiler-free content.
distinct-also-contains requires distinct-contains to be present in the same rule. Using it alone will produce a validation error; write title (contains) instead.
Note: All three conditions are equally available for body checks: body (distinct-contains), body (distinct-block), and body (distinct-also-contains) work identically to their title equivalents.
Example: routing four types of post to different sticky comments
Distinct matching is particularly useful when posts can fall into several categories based on their wording, and each category needs a different sticky comment. Using the spoiler example, posts can be sorted into four groups:
Because the app uses first-match-wins ordering, the most specific rule must come first. Rule 1 below (partial spoilers) would also satisfy the conditions of Rule 2 (full spoilers) if the ordering were reversed, since it does have distinct spoiler wording (and would thus be skipped as the full spoilers rule already tripped):
# Rule 1 - partial spoilers: has both spoiler and spoiler-free wording
# e.g. "Spoilers for DLC1 (No Spoilers for DLC2)"
title (distinct-contains): [Spoilers, Spoiler]
title (distinct-block): [No Spoilers, No Spoiler, Spoiler Free, Spoiler-Free]
title (distinct-also-contains): [No Spoilers, No Spoiler, Spoiler Free, Spoiler-Free]
comment: |
This post contains partial spoilers. Please tag your comments according to the title.
---
# Rule 2 - full spoilers: has spoiler wording with no spoiler-free wording
# e.g. "Spoilers for the entire series"
title (distinct-contains): [Spoilers, Spoiler]
title (distinct-block): [No Spoilers, No Spoiler, Spoiler Free, Spoiler-Free]
title (not-contains): [No Spoilers, No Spoiler, Spoiler Free, Spoiler-Free]
comment: |
This post contains spoilers.
---
# Rule 3 - spoiler-free: has spoiler-free wording
# Any title with distinct spoiler wording was already caught by Rules 1 or 2.
# e.g. "No Spoilers for the new series"
title (contains): [No Spoilers, No Spoiler, Spoiler Free, Spoiler-Free]
comment: |
This post is marked as spoiler-free. Any spoilers in comments must be spoiler tagged.
---
# Rule 4 - no specific wording (Default Comment)
# Set the Default Comment in App Settings to:
# "Please be mindful of spoilers when commenting."
Notice that Rule 2 also uses title (not-contains) for the spoiler-free phrases, rather than relying purely on Rule 1 to intercept mixed titles. This makes Rule 2 self-contained - it will never match a partial-spoiler title regardless of where it sits in the rule list. Relying solely on rule ordering to separate cases makes rules harder to maintain as your configuration grows.
post_flair_id: 9f2a1b3c-0000-0000-0000-aabbccddeeff # single ID
post_flair_id: [9f2a1b3c-..., deadbeef-...] # inline list
post_flair_id: # block list
- 9f2a1b3c-0000-0000-0000-aabbccddeeff
- deadbeef-0000-0000-0000-112233445566
comment: |
Your comment text here.
Markdown is supported.
Template variables: {{author}} {{subreddit}} {{title}} {{url}}
comment_stickied: true # default: true - pins as stickied mod comment
comment_locked: true # default: true - disables replies from non-mods
Note: All comments created by this app are automatically distinguished as a Moderator (green shield).
This section is specifically for moderators migrating existing AutoModerator rules. The YAML syntax is intentionally close to AutoModerator's, but there are important differences to be aware of. The app supports YAML data validation and will throw errors if your code is incompatible.
AutoModerator can match on many fields: author name, author flair, domain, URL, post type, karma thresholds, account age, and more. This app currently only supports:
title conditions (contains, not-contains, starts-with, ends-with, full-exact, [regex], [not-regex], distinct-contains, distinct-block, distinct-also-contains)body conditions (contains, not-contains, starts-with, ends-with, full-exact, [regex], [not-regex], distinct-contains, distinct-block, distinct-also-contains)post_flair_idThe combined title+body AutoModerator syntax is not supported.
Any other AutoModerator condition keys will be silently ignored. Always check that your copied rule only relies on supported fields.
AutoModerator can approve, remove, filter, report, and ban. This app only posts a comment. The action, action_reason, set_locked, and similar AutoModerator action keys are not recognised and will be ignored.
comment_stickied and comment_locked default to trueIn AutoModerator, a comment: action posts a regular, non-stickied, unlocked comment unless you explicitly configure otherwise. In this app, both default to true. If you copy a rule from AutoModerator that posts a comment without those fields set, the result here will be stickied and locked - which may or may not be what you want. Always set them explicitly if you have a preference:
comment_stickied: false
comment_locked: false
AutoModerator uses Python's re module. This app uses JavaScript's RegExp engine. The two are highly compatible for everyday patterns, but the following differences can catch you out when copying AutoModerator regex rules:
| Feature | AutoModerator (Python) | This app (JavaScript) |
|---|---|---|
| Named capture groups | (?P<name>...) |
(?<name>...) |
| End-of-string anchor | \Z |
$ |
| Possessive quantifiers | a++, a*+ |
Not supported |
| Atomic groups | (?>...) |
Not supported |
| Unicode property escapes | Not supported | \p{Letter}, \p{Emoji} etc. work |
The most likely issue when copying from AutoModerator is named capture groups: (?P<name>...) is Python-only syntax and will cause the pattern to fail to compile in JavaScript. Rewrite them as (?<name>...) or as non-capturing groups (?:...) if the name isn't needed. Error validation should make it possible to pinpoint any incompatibilities.
# Rule 1 - sticky message for help/question posts (title check)
title (contains): [help, question]
comment: |
Hi u/{{author}}, it looks like you're asking for help!
Before we dig in, please make sure you've checked our subreddit wiki.
If you have questions about this, please [contact our mods via modmail](https://www.reddit.com/message/compose?to=/r/{{subreddit}}) rather than replying here. Thank you!
comment_stickied: true
comment_locked: true
---
# Rule 2 - body text check: posts mentioning a resolved state
body (contains): [TL;DR, solved, "fixed it"]
comment: |
It looks like your post may already be resolved, u/{{author}}!
If your issue is sorted, please update your post flair to **Solved** so other members know.
comment_stickied: true
comment_locked: false
---
# Rule 3 - combined title and body check: detailed questions
title (ends-with): "?"
body (not-contains): [screenshot, image, photo]
comment: |
Thanks for your detailed question, u/{{author}}!
Our community will do its best to help. In the meantime, check the **[wiki](https://www.reddit.com/r/{{subreddit}}/wiki)** - your answer might already be there.
comment_stickied: true
comment_locked: false
---
# Rule 4 - flair-specific announcement
post_flair_id: 9f2a1b3c-xxxx-xxxx-xxxx-000000000000
comment: |
**Posts carrying this flair are subject to strict community rules.**
comment_stickied: true
comment_locked: false
---
# Rule 5 - flair-specific + title match
title (contains): ["[SPOILERS]", "[SPOILER]"]
post_flair_id: 9f2a1b3c-xxxx-xxxx-xxxx-000000000000
comment: |
**This post was tagged for spoilers.**
comment_stickied: true
comment_locked: false
Thanks for posting to r/{{subreddit}}, u/{{author}}!
Please ensure your post aligns with our general guidelines.
*I am a bot, and this action was performed automatically. Please [contact the moderators of this subreddit](/message/compose/?to=/r/{{subreddit}}) if you have any questions or concerns.*
The Global Footer is appended after a blank line to every comment the app posts. In the example above, a matched rule comment would be sent as:
Thanks for your detailed question, u/ExampleUser!
Our community will do its best to help. In the meantime, check the **wiki** - your answer might already be there.
*I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.*
distinct-contains, distinct-block, distinct-also-contains): new title and body conditions that distinguish between a phrase appearing on its own versus only appearing inside a suppressing phrase. For example, "Spoilers" in a title can be treated differently depending on whether it appears standalone or only ever inside "No Spoilers". See the Distinct Matching section for full details and examples.Author checks - username, is_contributor/ contributors_exempt, is_moderator/ moderators_exempt
Support for case-sensitive - optional case-sensitive modifier
url and domain checks for link submissions, post type checks
OR matching for multiple types of checks (e.g. body+title)
Alternate post flair checks