Configuration#
Configuration is an important topic in r3ply, as it's the primary way most people would interact with the system. This page will first cover fundamentals of r3ply configuration, before specifying the configuration options for site configs and r3ply app configs.
Table of Contents#
Fundamentals#
Your website's config is how you will control most of r3ply's behavior. Here are some details that will help you understand how r3ply expects configs to work in general.
Versioning of r3ply#
r3ply uses semantic versioning and this is enforced by the version config key, which is required. All the components of r3ply – the server, config, CLI, etc... – are designed to work with their corresponding major version.
- Small changes such as bug fixes change the patch version number, i.e.
0.0.Z - Backwards compatible features change the minor version number, i.e.
0.Y.0 - Breaking changes update the major version and, i.e.
X.0.0
(Note: Your version of r3ply can be specified at the top-level of your site config, e.g. version = "0.0.1".)
In other words, if you're using a config at version 1.0.1, and a r3ply server is using 1.0.5 or 1.6.2, your config should still work. The same also applies for the CLI tool.
However, while r3ply is in version 0.y.z semantic versioning can be broken (although we will try not to do it too much). This is so we can get to a stable version as quickly as possible.
Config File Types & Locations#
r3ply configs can be written as either TOML or JSON files. The r3ply servers will choose the first file that exists at the following locations, with precedence high to low:
# Priority from highest to lowest:
<PROJECT_ROOT>/.well-known/r3ply/config.toml
<PROJECT_ROOT>/.well-known/r3ply/config.json
<PROJECT_ROOT>/.well-known/r3ply.config.toml
<PROJECT_ROOT>/.well-known/r3ply.config.json
<PROJECT_ROOT>/r3ply.config.toml
<PROJECT_ROOT>/r3ply.config.json
<PROJECT_ROOT>/r3ply.toml
<PROJECT_ROOT>/r3ply.json
<PROJECT_ROOT> can, for example, be a domain, or it could also be your project's top-level directory, in the context of using r3ply locally with the CLI.
Config Schemas#
r3ply's configs are written as a JSON Schemas ↗. They can be browsed here.
One of the benefits of this is you can reference the schema in your configuration, enabling editor support like validation, hints/examples, and auto-complete.
Here's how you do it with JSON configs:
{
"$schema": "https://r3ply.com/schemas/v0.0.1/config/site.v0.0.1.json",
"version": "0.0.1",
"site": [{
"domain": "spenc.es",
"r3ply": "r3ply.com",
"signet": "qhQ6YSUvQNLb1lCdw3kDR",
"issued": "2025-08-22"
}]
/* ... */
}
And now VSCode will provide detailed editor support:
The same can be done for TOML configs – albeit not yet supported as well1 – using tombi, and then adding a schema comment directive at the top.
#:schema https://r3ply.com/schemas/v0.0.1/config/site.v0.0.1.json
version = "0.0.1"
[[site]]
domain = "example.com"
r3ply = "r3ply.com"
signet = "iSQIIBcF7ka2UURJpFDkYw"
issued = 2025-08-26
Config Variables & Types#
A r3ply config variable consists of a name and a value, e.g. version = "0.0.1".
The value assigned to a config name can have many types. To help communicate this information r3ply uses variable names that follow a convention.
Skip to subsection: foo, foo_{}, &foo, foo*, $foo
Variables named foo#
These are just normal variables. The have the same types that you expect in TOML or JSON, e.g. string, number, date, list, etc...
# string
version = "0.0.1"
# boolean
enabled = true
# date (in JSON these have to be quoted)
issued = 2025-10-24
Variables named foo_{}#
The _{} syntax means a template string is expected.
# here the path of a file is
"file_path_{}" = "content/comments/{{ comment.ts_rcvd }}.md"
"head_branch_{}" = "comment-{{ comment.ts_rcvd }}"
"commit_msg_{}" = """
Comment submitted:
Sender: {{ author.pseudonym }}
Timestamp: {{ comment.ts_rcvd }}
Subject: {{ comment.subject.url }}"""
See the string templating docs for more info.
Variables named &foo#
The &foo syntax means the value within is referencing a file that holds the real value. Paths are relative to the location of the site's r3ply config.
These variables are often combined with _{} (see above) so that complex templates can have their own file. For example &foo_{} would read as
a reference to a file, that within contains a string template.
Here are some examples with comments.
# `..` and `.` are relative to config path
"&relative_example" = "../foo.bar.baz.txt"
# read like, "the comment template string is in this file"
"&comment_{}" = "./viaEmail/comment.template.html"
Each r3ply app knows how to deference these config variables in a way that's specific to their domain. For example, the public internet version of r3ply will interpret these as a URL path and perform a fetch request, while the r3ply CLI app re will interpret this as a path on the local file system.
In either case the same variable value works in both without having to be changed.
Variables named foo*#
The foo* syntax means glob patterns can be used. These variables are usually used with lists.
# i.e. only allow `"production"`
"filter*" = ["production"]
# i.e. allow all paths except those begining with "private"
"paths*" = ["**", "!/private/**"]
Variables named $foo#
The $foo syntax means this variable is some kind of meta variable reserved by r3ply and can usually not be changed.
# These can be safely ignored
"$comment_sources" = ["email"]
Site Config#
Below is an example of the full site config, using every default and with every value set. For convenience there are also separate sections with more discussion for site, comments, and moderation.
1 # ALL SITE CONFIG VARIABLES SET + DEFAULTS
2
3 # r3ply site config: Used to configure the r3ply commenting system.
4 # See https://r3ply.com/docs for more info.
5
6 # Should be accessible from a known location
7 # E.g. `/.well-known/r3ply/config.toml`
8 # See #file-types-and-locations for more
9
10 # r3ply version: Declares what version of r3ply this config conforms to.
11 version = "0.0.1"
12 # Enable r3ply: False completely turns off r3ply, including any downstream processes.
13 # Default: true
14 enabled = true
15
16 # Site information: Requires a separate entry for each `site` x `r3ply` (and `issued`) combination.
17 [[site]]
18 # Site domain: The domain that this configuration applies to. Hostname only.
19 domain = "spenc.es"
20 # r3ply domain: The r3ply app to receive comments from. Hostname only.
21 r3ply = "r3ply.com"
22 # Signet data: The r3ply-issued signet key.
23 # Examples: ["qhQ6YSUvQNLb1lCdw3kDRg"]
24 # See docs/overview#sites-signets.
25 signet = "wXyyym86v0pKerq41HiSCA"
26 # Issue date (of signet): Used as a key identifier, e.g. for signet rotations and versioning of signet key.
27 # Examples: ["2025-08-22"]
28 issued = 2025-10-24
29 # Site label: A human readable label of a site. Useful for filtering further downstream.
30 # Examples: ["test","test #1","production","website"]
31 # See also `filter*`.
32 label = "prod"
33
34 # A "staging" example using test domains
35 [[site]]
36 domain = "test.spenc.es"
37 r3ply = "test.r3ply.com"
38 signet = "mwXjhb543US3KrSkYtHfnQ"
39 issued = 2025-10-24
40 label = "staging"
41
42 # The CLI also requires its own site entry
43 [[site]]
44 domain = "site.local.test"
45 r3ply = "cli.r3ply.test"
46 signet = "cmq0jqG3c2JxKKzDJ6qpXQ"
47 issued = "2025-10-24"
48 label = "CLI"
49
50 # Comments config: Control top-level commenting parameters.
51 # See also `comments.email`.
52 [comments]
53 # Enable comments: False completely turns off commenting.
54 # Default: true
55 enabled = true
56 # Cache pending comments: Can be temporarily fetched, e.g. via front end javascript.
57 # Default: false
58 # the pending comments cache is very unstable still. TODO: some kind of basic, automatic moderation to flag for spam. TODO: document better exactly how much time the cache makes comments available (72 hours is reasonable).
59 cache = false
60 # Markdown to HTML conversion: Converts markdown syntax to HTML tags.
61 # Default: true
62 # See also `sanitize_html`. TODO: remove this config variable. If people don't want MD -> HTML conversion they can just not use the converted HTML.
63 md_to_html = true
64 # Sanitize HTML: Nothing from the outside world should be trusted, especially HTML in comments. Only disable this if you reall know what you're doing.
65 # Default: true
66 # See also `allow_tags`.
67 sanitize_html = true
68 # HTML Tags to allow: Only tags listed here will be allowed by the HTML sanitizer.
69 # Default: (same as what's shown below)
70 allow_tags = [ "a", "br", "p", "span", "strong", "s", "del", "em", "u", "ul", "ol", "li", "blockquote", "hr", "code", "pre", "table", "tr", "td", "th", "caption", "thead", "tbody", "tfoot", "kbd", "mark", "sub", "small"]
71 # TODO: remove this. There are better ways to derive this.
72 "$comment_sources" = [ "email" ]
73
74 # Emailed comments config: Control parameters unique to email.
75 [comments.email]
76 # Enable email comments: False disables email comments only.
77 # Default: true
78 # See `comments.enabled`.
79 enabled = true
80 # Filter site: Specifies which sites (by label) will be processed.
81 # Default: ["**"]
82 # Examples: ["*","!local"]
83 # See `label` property on `site` config variable.
84 "filter*" = [ "**" ]
85 # Email signature separator: Text boundary that appears before email signature.
86 # Default: "\n"
87 # Examples: ["﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍\nWrite your comment above 👆\n\nDON'T alter the subject line ⚠️\n\nEverything below this line 👇 will be ignored\n﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍"]
88 # This should be the same string you use in the `body` field of your mailto links. It's a good idea to use some recognizable text. You can also put instructions to the commenter in here.
89 email_signature_separator = """
90
91 """
92 # Allow attachments: Attachments are currently disabled but support will be added in the future.
93 # Default: false
94 attachments = false
95 # Max size (in bytes): If an email comment exceeds either this amount or the limit set upstream by the r3ply server it will be rejected.
96 # Default: 1048576
97 # i.e. 1 MB.
98 max_size_bytes = 1_048_576
99 # Block list: Specifies which pseudyonym/email address to block.
100 # Default: []
101 # Examples: ["e8a20d6*","mallory@evil.com","*@spam.com"]
102 "block*" = [ ]
103 # Comment mime type: It can be at times useful to specify the mime type of a comment file.
104 # Default: "text/plain"
105 comment_mime = "text/plain"
106
107 # Moderation config: Control the various moderation channels
108 # i.e. what should happen to comment after they've been processed.
109 [moderation]
110 # Enable moderation: False completely disables moderation.
111 # Default: true
112 enabled = true
113
114 # Local moderation: Specifies a moderation channel used locally.
115 # This is usually used by `re` the r3ply CLI tool.
116 [[moderation.local]]
117 # File path template (string): Specifies the file path of the new comment.
118 # Examples: ["content/comments/{{ comment.id | slice(end=8) }}.md"]
119 # Can never begin with a `/`.
120 "file_path_{}" = "comment_{{ comment.id[:8] }}.json"
121 # Enable moderation: False completely turns off moderation for this channel.
122 # Default: true
123 # See `moderation.enabled`.
124 enabled = true
125 # Allow list: Plain text email or pseudonym bypassing moderation.
126 # Default: []
127 # Examples: ["*@alice.com","bob@example.com"]
128 "allow*" = [ ]
129
130 # Github moderation: Specify how comments should be sent to GitHub for moderation.
131 [[moderation.github]]
132 # Repo owner: This should be the user or org name.
133 # Examples: ["asimpletune","r3ply"]
134 owner = "<YOUR_GITHUB_USERNAME>"
135 # Repo name: Name of GitHub repository.
136 # Examples: ["yoursite"]
137 repo = "<YOUR_PROJECT>"
138 # File path template (string): Specifies the file path of the new comment.
139 # Examples: ["content/comments/{{ comment.id | slice(end=8) }}.md"]
140 # Can never begin with a `/`.
141 "file_path_{}" = "comment_{{ comment.id[:8] }}.json"
142 # Base branch template (string): Specifies the base branch of the new comment, e.g. `main`.
143 # Default: "main"
144 "base_branch_{}" = "main"
145 # Head branch template (string): Specifies the head branch of the new comment, e.g. `comment-123`.
146 # Default: "comment-{{ comment.ts_rcvd }}-{{ comment.id[:8] }}.md"
147 # Examples: ["{{ comment.id[:8] }}-{{ comment.ts_rcvd }}"]
148 "head_branch_{}" = "comment-{{ comment.ts_rcvd }}-{{ comment.id[:8] }}.md"
149 # Commit message template (string): Specifies the commit message of the new comment.
150 # Default: (same as what's shown below)
151 "commit_msg_{}" = """
152 Comment submitted:
153 Sender: {{ author.pseudonym }}
154 Timestamp: {{ comment.ts_rcvd }}
155 Subject: {{ comment.subject.url }}
156 Comment: > {{ comment.txt | split(pat="
157 ") | join(sep="> ") }}"""
158 # PR title template (string): Specifies the PR title of the new comment.
159 # Default: (same as what's shown below)
160 "pr_title_{}" = "New comment ({{ comment.id[:8] }}) on {{ comment.subject.url }} by author `{{ author.pseudonym[:7] }}`"
161 # PR body template (string): Specifies the PR body of the new comment.
162 # Default: (same as what's shown below)
163 "pr_body_{}" = ""
164 # Github host: Only useful for enterprise instances of GitHub.
165 # Default: "github.com"
166 # You probable don't want to change this.
167 github_host = "github.com"
168 # Enable moderation: False completely turns off moderation for this channel.
169 # Default: true
170 # See `moderation.enabled`.
171 enabled = true
172 # Allow list: Plain text email or pseudonym bypassing moderation.
173 # Default: []
174 # Examples: ["*@alice.com","bob@example.com"]
175 "allow*" = [ ]
176
177 # Webhook moderation: Specifies how comments should be sent to a webhook.
178 # Note: webhook moderation is useful taking custom actions.
179 [[moderation.webhook]]
180 # Webhook URL: The comment will be in the request body.
181 # TODO: when r3ply supports secrets allow secrets to be stored here.
182 url = "https://TODO"
183 # Webhook method: The method the comment will be sent with.
184 # Default: "POST"
185 method = "POST"
186 # Enable moderation: False completely turns off moderation for this channel.
187 # Default: true
188 # See `moderation.enabled`.
189 enabled = true
190 # Allow list: Plain text email or pseudonym bypassing moderation.
191 # Default: []
192 # Examples: ["*@alice.com","bob@example.com"]
193 "allow*" = [ ]
Site#
The site config key expects an array of objects. Each site entry object is as follows:
# Site information: Requires a separate entry for each `site` x `r3ply` (and `issued`) combination.
[[site]]
# Site domain: The domain that this configuration applies to. Hostname only.
domain = "spenc.es"
# r3ply domain: The r3ply app to receive comments from. Hostname only.
r3ply = "r3ply.com"
# Signet data: The r3ply-issued signet key.
# Examples: ["qhQ6YSUvQNLb1lCdw3kDRg"]
# See docs/overview#sites-signets.
signet = "wXyyym86v0pKerq41HiSCA"
# Issue date (of signet): Used as a key identifier, e.g. for signet rotations and versioning of signet key.
# Examples: ["2025-08-22"]
issued = 2025-10-24
# Site label: A human readable label of a site. Useful for filtering further downstream.
# Examples: ["test","test #1","production","website"]
# See also `filter*`.
label = "prod"
One config file can be used by many sites. This is a fairly common scenario when you want to stage changes. You may have one site deployed at one domain, while having another site deployed to a staging domain for testing.
[[site]]
# In this example I would be deploying some new changes to a staged domain.
domain = "test.spenc.es"
# I might also be testing changes with a new version of r3ply.
r3ply = "test.r3ply.com"
signet = "mwXjhb543US3KrSkYtHfnQ"
issued = 2025-10-24
# Giving a `site` entry a label allows you to filter them downstream. See the `filter*` config variable.
label = "staging"
Additional filtering can be done further downstream in the r3ply pipeline by using the site entry's label. See filter* for more details.
Comments#
The comments key is where the behavior for comments is adjusted. Here are the top-level comment config variables.
# Comments config: Control top-level commenting parameters.
# See also `comments.email`.
[comments]
# Enable comments: False completely turns off commenting.
# Default: true
enabled = true
# Cache pending comments: Can be temporarily fetched, e.g. via front end javascript.
# Default: false
# the pending comments cache is very unstable still. TODO: some kind of basic, automatic moderation to flag for spam. TODO: document better exactly how much time the cache makes comments available (72 hours is reasonable).
cache = false
# Markdown to HTML conversion: Converts markdown syntax to HTML tags.
# Default: true
# See also `sanitize_html`. TODO: remove this config variable. If people don't want MD -> HTML conversion they can just not use the converted HTML.
md_to_html = true
# Sanitize HTML: Nothing from the outside world should be trusted, especially HTML in comments. Only disable this if you reall know what you're doing.
# Default: true
# See also `allow_tags`.
sanitize_html = true
# HTML Tags to allow: Only tags listed here will be allowed by the HTML sanitizer.
# Default: (same as what's shown below)
allow_tags = [ "a", "br", "p", "span", "strong", "s", "del", "em", "u", "ul", "ol", "li", "blockquote", "hr", "code", "pre", "table", "tr", "td", "th", "caption", "thead", "tbody", "tfoot", "kbd", "mark", "sub", "small"]
# TODO: remove this. There are better ways to derive this.
"$comment_sources" = [ "email" ]
It is strongly advised NOT to disable sanitize_html.
There are also individual comment sources that have their own config key, albeit with comments.email currently being the first and only one.
Email Comments#
# Emailed comments config: Control parameters unique to email.
[comments.email]
# Enable email comments: False disables email comments only.
# Default: true
# See `comments.enabled`.
enabled = true
# Filter site: Specifies which sites (by label) will be processed.
# Default: ["**"]
# Examples: ["*","!local"]
# See `label` property on `site` config variable.
"filter*" = [ "**" ]
# Email signature separator: Text boundary that appears before email signature.
# Default: "\n"
# Examples: ["﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍\nWrite your comment above 👆\n\nDON'T alter the subject line ⚠️\n\nEverything below this line 👇 will be ignored\n﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍﹍"]
# This should be the same string you use in the `body` field of your mailto links. It's a good idea to use some recognizable text. You can also put instructions to the commenter in here.
email_signature_separator = """
"""
# Allow attachments: Attachments are currently disabled but support will be added in the future.
# Default: false
attachments = false
# Max size (in bytes): If an email comment exceeds either this amount or the limit set upstream by the r3ply server it will be rejected.
# Default: 1048576
# i.e. 1 MB.
max_size_bytes = 1_048_576
# Block list: Specifies which pseudyonym/email address to block.
# Default: []
# Examples: ["e8a20d6*","mallory@evil.com","*@spam.com"]
"block*" = [ ]
# Comment mime type: It can be at times useful to specify the mime type of a comment file.
# Default: "text/plain"
comment_mime = "text/plain"
Moderation#
Moderation is what happens to a comment after it has been received and processed according to a site's config. What follows are the top-level moderation config options.
# Moderation config: Control the various moderation channels
# i.e. what should happen to comment after they've been processed.
[moderation]
# Enable moderation: False completely disables moderation.
# Default: true
enabled = true
Additionally, there are individual moderation channels that have their own sub-configs.
Local Moderation#
# Local moderation: Specifies a moderation channel used locally.
# This is usually used by `re` the r3ply CLI tool.
[[moderation.local]]
# File path template (string): Specifies the file path of the new comment.
# Examples: ["content/comments/{{ comment.id | slice(end=8) }}.md"]
# Can never begin with a `/`.
"file_path_{}" = "comment_{{ comment.id[:8] }}.json"
# Enable moderation: False completely turns off moderation for this channel.
# Default: true
# See `moderation.enabled`.
enabled = true
# Allow list: Plain text email or pseudonym bypassing moderation.
# Default: []
# Examples: ["*@alice.com","bob@example.com"]
"allow*" = [ ]
GitHub Moderation#
# Github moderation: Specify how comments should be sent to GitHub for moderation.
[[moderation.github]]
# Repo owner: This should be the user or org name.
# Examples: ["asimpletune","r3ply"]
owner = "<YOUR_GITHUB_USERNAME>"
# Repo name: Name of GitHub repository.
# Examples: ["yoursite"]
repo = "<YOUR_PROJECT>"
# File path template (string): Specifies the file path of the new comment.
# Examples: ["content/comments/{{ comment.id | slice(end=8) }}.md"]
# Can never begin with a `/`.
"file_path_{}" = "comment_{{ comment.id[:8] }}.json"
# Base branch template (string): Specifies the base branch of the new comment, e.g. `main`.
# Default: "main"
"base_branch_{}" = "main"
# Head branch template (string): Specifies the head branch of the new comment, e.g. `comment-123`.
# Default: "comment-{{ comment.ts_rcvd }}-{{ comment.id[:8] }}.md"
# Examples: ["{{ comment.id[:8] }}-{{ comment.ts_rcvd }}"]
"head_branch_{}" = "comment-{{ comment.ts_rcvd }}-{{ comment.id[:8] }}.md"
# Commit message template (string): Specifies the commit message of the new comment.
# Default: (same as what's shown below)
"commit_msg_{}" = """
Comment submitted:
Sender: {{ author.pseudonym }}
Timestamp: {{ comment.ts_rcvd }}
Subject: {{ comment.subject.url }}
Comment: > {{ comment.txt | split(pat="
") | join(sep="> ") }}"""
# PR title template (string): Specifies the PR title of the new comment.
# Default: (same as what's shown below)
"pr_title_{}" = "New comment ({{ comment.id[:8] }}) on {{ comment.subject.url }} by author `{{ author.pseudonym[:7] }}`"
# PR body template (string): Specifies the PR body of the new comment.
# Default: (same as what's shown below)
"pr_body_{}" = ""
# Github host: Only useful for enterprise instances of GitHub.
# Default: "github.com"
# You probable don't want to change this.
github_host = "github.com"
# Enable moderation: False completely turns off moderation for this channel.
# Default: true
# See `moderation.enabled`.
enabled = true
# Allow list: Plain text email or pseudonym bypassing moderation.
# Default: []
# Examples: ["*@alice.com","bob@example.com"]
"allow*" = [ ]
Webhook Moderation#
# Webhook moderation: Specifies how comments should be sent to a webhook.
# Note: webhook moderation is useful taking custom actions.
[[moderation.webhook]]
# Webhook URL: The comment will be in the request body.
# TODO: when r3ply supports secrets allow secrets to be stored here.
url = "https://TODO"
# Webhook method: The method the comment will be sent with.
# Default: "POST"
method = "POST"
# Enable moderation: False completely turns off moderation for this channel.
# Default: true
# See `moderation.enabled`.
enabled = true
# Allow list: Plain text email or pseudonym bypassing moderation.
# Default: []
# Examples: ["*@alice.com","bob@example.com"]
"allow*" = [ ]
System Config#
Below is an example of the full r3ply system config, using every default and with every value set.
# r3ply system config: Used to configure a system that provides r3ply commenting functionality.
# See https://r3ply.com/docs for more info.
# r3ply version: Declares what version of r3ply this config conforms to.
# Default: "0.0.1"
version = "0.0.1"
# r3ply domains: The domains that configuration applies to. Hostname only.
# Examples: [["r3ply.com","test.r3ply.com"],["your-r3ply-app.com"]]
# must match the domain that serves the config
domains = [ "r3ply.com" ]
# Enable r3ply: False completely turns off r3ply, including any downstream processes.
# Default: true
# ⚠️: if disabled, downstream sites will be affected.
enabled = true
# Allowed sites: Specifies what sites to accepts comments on behalf of.
# If undefined then all sites are allowed.
# Default: undefined
# "sites*" = undefined
# Admin: Contact list for r3ply system admins.
[[admin]]
name = "Spence"
email = "hello@spenc.es"
# Email processing
[email]
# Enable email comments: If false, all emails are ignored.
# Default: true
# ⚠️: if disabled, downstream sites will be affected.
enabled = true
# Email attachments: Attachments are currently disabled but support will be added in the future.
attachments = false
# Max size (bytes): Emails are ignored if their size (in bytes) exceed the min(system, site) configs
# Default: 5242880
# Note: default is 5 MB
max_size_bytes = 5_242_880
-
If you're interested in helping to develop better tooling see contributing. ↩