Linear forward-path diagram (invitation -> Active), with transitions out of Active as a list instead of cramped back-arrows.
SimpleX Directory Service
Chat bot for registering and searching groups. Superusers and admins are configured via CLI flags.
Prerequisites
- GHC 9.6.3 — install via GHCup
- Cabal 3.10.2+
Building
git clone https://github.com/simplex-chat/simplex-chat
cd simplex-chat
# OpenSSL build configuration (required) — copy the file for your OS:
cp scripts/cabal.project.local.linux cabal.project.local # on macOS: scripts/cabal.project.local.mac
cabal build simplex-directory-service
See docs/CONTRIBUTING.md for the full build setup (toolchain, OpenSSL headers, branch compatibility).
Find the compiled binary with:
cabal list-bin simplex-directory-service
Or run directly without installing:
cabal run simplex-directory-service -- <flags>
Running the Bot
Getting your contact ID (bootstrap)
--super-users is required and takes one or more CONTACT_ID:DISPLAY_NAME pairs. On a fresh database you don't yet know your contact ID, so start the bot with --run-cli and a placeholder, connect to it, then look up your real ID:
simplex-directory-service --run-cli --super-users 999:nobody --database /path/to/db
# on startup the bot prints (and, unless --no-address, creates) its contact address:
# "Bot's contact address is: simplex:/contact#..."
# connect to it from your SimpleX Chat app, then in the bot terminal:
/contacts # lists connected contacts by name
/i alice # shows full info for contact "alice", including their contact ID
# note the ID, then restart without --run-cli:
simplex-directory-service --super-users 2:alice --database /path/to/db
In each --super-users / --admin-users pair both contactId and localDisplayName must match the contact exactly. (A mismatched name doesn't stop the bot from sending admin notifications to that contact ID, but commands from that contact will be rejected.)
Minimal run
simplex-directory-service \
--super-users 2:alice \
--database /path/to/db
Flags
simplex-directory-service also accepts the standard SimpleX Chat core options (custom SMP/XFTP servers, --socks-proxy, network settings, etc.) — run simplex-directory-service --help for the complete list. The directory-specific options are:
| Flag | Default | Description |
|---|---|---|
--super-users ID:NAME[,...] |
(required) | Super-user contacts (comma-separated) |
--admin-users ID:NAME[,...] |
none | Admin-only contacts (comma-separated) |
--owners-group ID:NAME |
none | Group (by group ID) that owners of listed groups are invited into — automatically on listing, or via /invite |
-d / --database PATH |
~/.simplex/simplex_directory_service |
Database file path prefix |
--directory-file PATH |
none | Append-only log of directory state (see Directory state log) |
--migrate-directory-file check|import|export|listing |
— | Check, import (log → DB), export (DB → log), or regenerate listing files, then exit |
--web-folder PATH |
none | Write static listing JSON + group images here (see Hosting the directory page) |
--no-address |
off | Skip checking/creating the bot's contact address |
--service-name NAME |
SimpleX Directory |
Bot display name (without * characters) |
--profile-name-limit N |
unlimited | Max display-name length allowed to connect / join groups (used by the name join filter) |
--blocked-words-file PATH |
none | Words not allowed in profiles (used by the name join filter and profile review) |
--blocked-fragments-file PATH |
none | Word fragments not allowed in profiles |
--blocked-extenstion-rules PATH |
none | Substitution rules that expand the blocked-words list (the flag is spelled this way in the binary) |
--name-spelling-file PATH |
none | Character-substitution rules for matching disguised names |
--captcha-generator PATH |
none | Executable that renders a captcha image; without it captchas are sent as plain text |
--voice-captcha-generator PATH |
none | Executable that renders a voice captcha |
--run-cli |
off | Run an interactive CLI alongside the bot (useful for bootstrap) |
-v / --version |
— | Print version and exit |
Directory State Log
--directory-file keeps an append-only log of every change to directory state (registrations, status changes, promotions) alongside the SQLite database. It is optional but recommended for operability: it is human-inspectable and can be checked, exported, or re-imported with --migrate-directory-file. Without it, directory state lives only in the database.
Hosting the Directory Page
The web/ folder contains a ready-to-use page (directory.html) that renders a bot's directory as a searchable, paginated list — no build step. Dark mode follows the system preference.
A few things to know before deploying it:
- Copy the files out of the repo.
web/directory.jsis a symlink towebsite/src/js/directory.js(kept in sync with the main website), so don't edit it in place — copydirectory.htmland the contents ofdirectory.jsto your web root and edit the copy. The data URL can't be overridden fromdirectory.htmlbecausedirectory.jsdeclares it as a top-levelconst. - Serve it under a
/directorypath.directory.jsonly initialises when the page path starts with/directory(e.g.https://example.com/directory.htmlorhttps://example.com/directory/). - Provide the fallback image. Groups without a profile image (and any image that fails to load) fall back to
/img/group.svg, resolved from the site root — copywebsite/src/img/group.svgto<web-root>/img/group.svg. - Point it at your bot's data. Near the bottom of
directory.js, change:(the default is the official directory,const simplexDirectoryDataURL = 'https://your-domain.example/data/';https://directory.simplex.chat/data/). The page fetcheslisting.jsonfrom that URL and loads group images relative to it. - Optional: the "Also available as a SimpleX chat bot" link in
directory.htmlpoints at the official directory bot — update it to your bot's address.
Then run the bot with --web-folder pointing at the folder served at that URL. The bot writes listing.json (used by the page), promoted.json (the promoted subset — written but not rendered by the bundled page), and group images, refreshing every 5 minutes and immediately when a group is approved or its listing/promotion status changes:
simplex-directory-service \
--super-users 2:alice \
--database /path/to/db \
--web-folder /var/www/your-domain.example/data
Command Reference
This is a reference for all commands accepted by the SimpleX Directory bot. For a guided walkthrough of group submission see DIRECTORY.md.
1. Searching
The bot sends a welcome message automatically when you connect.
| Action | Syntax | Effect |
|---|---|---|
| Search | <text> |
Returns up to 10 groups whose name or welcome message matches; sorted by member count |
| Next page | /next or . |
Next page of the most recent search (after ~5 min of inactivity, or with no recent search, falls back to listing all groups) |
| Recent groups | /new |
Groups added most recently |
| All groups | /all |
All listed groups, sorted by member count |
| Help | /help [registration|r|commands|c], or /h |
Show registration or commands help (default: registration) |
2. Registering a Group
Registration is a three-step process — see DIRECTORY.md for full details:
- Invite the directory bot to your group as
admin. - Add the link the bot sends you to the group's welcome message.
- Wait for admin approval (usually within a day, except holidays).
If a group with the same display name is already registered (but not yet listed or suspended), the bot asks you to confirm with /confirm. If the name is already listed or suspended in the directory, registration is blocked.
3. Managing Your Groups (user commands)
Note on
<ID>vs<ID>:<name>: Commands shown with<ID>[:<name>](/role,/filter,/link) accept just the ID — the name is optional./confirm,/delete, and all admin/super-user commands require both, written as<ID>:<name>. For user commands the ID is the registration ID shown by/list; for admin and super-user commands it's the group ID included in the bot's admin notifications. When the bot expects an<ID>:<name>argument it normally quotes the whole command for you (e.g. for/confirmand/approve), so you can copy it directly; for/deleteyou build it from the ID and name shown by/list.
List groups
/list
/ls
Shows all groups you have registered, with their current status, member count, and the /role and /filter commands for each.
Confirm duplicate name
/confirm <ID>:<name>
When you invite the bot to a group whose display name matches an already-registered group, the bot pauses registration and asks for explicit confirmation. This prevents accidental registration of another group that shares a name. /confirm acknowledges the duplicate and proceeds with registration. ID and name are provided in the bot's prompt.
If a group with the same name is already listed or suspended in the directory, registration is blocked entirely and /confirm is not offered.
View or set default join role
/role <ID>[:<name>] [member|observer]
Omit the role argument to view the current setting. member (default) lets new joiners post immediately; observer makes them read-only until promoted.
View or configure anti-spam filter
/filter <ID>[:<name>] [preset | flags]
Omit the argument to view the current filter. Filters apply to people joining via the directory-managed link.
Presets (mutually exclusive):
| Preset | Effect |
|---|---|
off |
No filter |
basic |
For profiles without an image: reject long or inappropriate names |
moderate (or mod) |
Reject long/inappropriate names from all profiles; require captcha from profiles without an image |
strong |
Reject long/inappropriate names from all profiles; require captcha from all profiles |
Flags (combine freely):
/filter <ID>[:<name>] [name[=all|=noimage]] [captcha[=all|=noimage]] [observer[=all|=noimage]]
| Flag | Condition | Effect |
|---|---|---|
name |
=all (default) or =noimage |
Reject joins from profiles whose name is too long (--profile-name-limit) or contains blocked words/fragments (--blocked-words-file / --blocked-fragments-file) |
captcha |
=all (default) or =noimage |
Require the joiner to solve a captcha |
observer |
=all (default) or =noimage |
Make new members observers instead of members |
=noimage means the condition applies only to profiles that have no profile image (=no_image and =no-image are also accepted).
The
namefilter only has an effect when the bot was started with--profile-name-limitand/or--blocked-words-file/--blocked-fragments-file; otherwise it does nothing. Thecaptchafilter sends a much stronger challenge when--captcha-generator(or--voice-captcha-generator) is configured — without it the captcha text is sent as a plain message.
View or upgrade group link
/link <ID>[:<name>]
Shows the current directory-managed join link. If the link is outdated the bot upgrades it.
Remove group from directory
/delete <ID>:<name>
Permanently removes the group from the directory. The group can be re-registered later.
4. Admin Commands
Admins receive a notification whenever a group enters the PendingApproval state. The <approval-id> in /approve is the integer from that notification (or from /pending); it increments each time a group re-enters the PendingApproval state so that stale approvals are rejected.
| Command | Syntax | Effect |
|---|---|---|
| Approve | /approve <ID>:<name> <approval-id> [promote=on|off] |
List group in directory; notifies owner |
| Suspend | /suspend <ID>:<name> |
Hide group from directory; notifies owner |
| Resume | /resume <ID>:<name> |
Re-list a suspended group; notifies owner |
| List recent | /last [N] |
Show last N registered groups (default: 10) |
| List pending | /pending [N] |
Show N groups awaiting approval (default: 10) |
| Message owner | /owner <ID>:<name> <message> |
Forward a message to the group owner |
| Reject | /reject <ID>:<name> |
(Reserved, currently a no-op) |
| Invite owner | /invite <ID>:<name> |
Invite the group owner to the owners' group (requires --owners-group) |
5. Super-User Commands
| Command | Syntax | Effect |
|---|---|---|
| Feature group | /promote <ID>:<name> on|off |
Add or remove group from the promoted listing (promoted.json) |
| Execute API command | /exec <command> or /x <command> |
Run a raw SimpleX Chat API command |
6. Group Lifecycle
Forward path, from invitation to being listed:
Invited by owner
│
┌────────────┴────────────┐
unique name duplicate name*
│ │
│ ▼
│ PendingConfirmation
│ │ owner runs /confirm
└────────────┬─────────────┘
▼
Proposed
│ bot joins the group and creates the link
▼
PendingUpdate
│ owner adds the link to the group welcome
▼
PendingApproval
│ admin runs /approve
▼
Active (listed; visible in search)
Transitions out of Active:
- → PendingUpdate — the directory bot link is removed from the welcome message.
- → PendingApproval — most other profile changes (see ** below); the
approval-idshown to admins is bumped each time, so stale/approvecommands are rejected. - → Suspended — an admin runs
/suspend;/resumere-lists the group. - → SuspendedBadRoles — the directory bot loses its
adminrole, or the registering owner loses theirownerrole, in the group; automatically restored to Active once the roles are corrected. - → Removed — the owner runs
/delete, the owner is removed from or leaves the group, the bot is removed from the group, or the group is deleted. The group can be re-registered afterwards.
* Only when the duplicate is registered but not yet listed or suspended. If the name is already listed or suspended, registration is blocked entirely.
** Profile changes only trigger re-approval when fields other than the directory bot link are modified. If the only change is swapping the old bot link for the new one, or changing only whitespace in the description, the group stays Active.
State notes:
- PendingConfirmation — the bot was invited but a group with the same display name is already registered (in a pending state); the owner must run
/confirmto proceed. - Proposed — the name is unique (or the duplicate was confirmed via
/confirm); the bot is joining the group. - PendingUpdate — the bot has joined the group and created the join link; the owner must add it to the group's welcome message.
- PendingApproval — submitted for admin review. The join link works even before approval.
- Active — listed in the directory and visible in search results.