Skip to main content

Blocklist

The global blocklist holds terms Lumio never shows, whichever channel they come from: profanity, slurs, spam. Every module that checks text asks the same list. Today that is the buyer name on Shopify order events: a name the list hits is dropped silently and the order appears without a name. A channel's own word filter is separate and keeps working.

The list is never public. It is readable only in the admin app and through the admin API (/v1/admin/blocklist… and the admin* GraphQL fields); no web page, widget, extension or WebSocket channel exposes it.

Where to find it​

Admin sidebar → Moderation → Blocklist (/blocklist). The sidebar item and the page need blocklist:read; without it the page shows the no-access view.

Permissions​

ActionPermission
View the list, search and filter; check a textblocklist:read
Add an entry; import a listblocklist:create
Edit an entry; switch it on or offblocklist:edit
Delete an entryblocklist:delete

These are admin-scope permissions, seeded to system_admin; other admin roles get them on Admin Roles. They never appear in a channel's role picker. Every change needs an interactive admin session (admin 2FA applies), so no API or System key can change the list. A control you lack the permission for is hidden; without blocklist:edit a row shows an Active / Off badge instead of the switch.

The list​

Entries appear in creation order, oldest first, 50 per page; Load more appends the next page. Search matches part of the term or the internal note, case-insensitively. Filters: kind, category, language (the exact code, such as de) and active or off. Each row shows the term, kind, category, language, status and when it last changed.

Kinds​

KindHits whenExample
Part of a word (substring)the term appears anywhere, also inside a longer wordbadword hits xbadwordx
Whole word (word)the term appears as a whole word onlyass hits an ass, not Cassandra
Wildcard (wildcard)* stands for any run of characters; everything else is literalsp*m hits spam and spooom
Regex (regex)the regular expression matches, case-insensitiveb[a4]d hits bad and b4d

Every active entry checks every text, whatever its language. Language and category only sort and filter the list.

Normalisation​

Before a text is checked, Lumio normalises it: Unicode NFKC (full-width Bad becomes Bad, ligatures split), lowercase, invisible characters removed (zero-width spaces and joiners, soft hyphens, bidi controls, variation selectors), runs of whitespace collapsed. Part of a word, Whole word and Wildcard terms are normalised the same way, so capitals, full-width letters and hidden characters do not get around them. A Regex pattern is used as written (case is part of its meaning: \D is not \d) and runs on the normalised, lowercased text. There is no automatic leetspeak folding: to catch b4d, write a regex such as b[a4]d. Only the first 256 characters of a text are checked.

Adding an entry​

Add entry opens a dialog: kind (each with an explanation), term or pattern, category (optional: profanity, slur, sexual, violence, spam, other), language (optional, a code such as de, en or de-AT), internal note (optional) and Active. A saved entry takes effect on every API instance within seconds, at the latest after 60 seconds.

Lumio refuses an entry, and says why, when:

  • the term is empty or longer than 512 characters, or the note longer than 500;
  • a regex does not compile (the dialog shows the compiler's message) or is too large: each regex or wildcard may compile to at most 64 KiB, enough for one Unicode class such as \w, not for a counted repeat such as \w{20};
  • it matches too broadly: a regex or wildcard does not name at least two characters that every match must contain, or the entry hits an empty text or one of a fixed set of ordinary names. A character counts in full when the pattern fixes it (b) and half when it is one of up to four alternatives ([a4]). So b[a4]d, n[i1]gg, \bbad\w* and the wildcard f*ck save, while \p{Thai}, [ğşı], ß, ^.{11,15}$ or ^\S+ \S+ \S+$ are refused because they single out a script, a letter group, a length or a word count rather than a term. The set covers short names and initials (Anna, Max M.), accented Latin (Jürgen, José, Łukasz), other scripts (Cyrillic, Greek, Han, Arabic, Hebrew, Devanagari, Hangul, kana), long and hyphenated names (Alexandra-Sophie, a 25-character name), apostrophes, underscores and digits (O'Brien, Sam_99, Jo 2). So ., [^a-z], .{9,} or \d are refused, and a substring or word entry such as an that hits one of these names is refused too; a real offensive word still saves;
  • the same kind with the same normalised term already exists ("already on the list");
  • the list is full (max_entries, default 10,000 entries, or max_pattern_entries, default 500 regex and wildcard entries; both count entries that are switched off);
  • the list would get too large to check: Lumio compiles the whole active list before it saves, and refuses when the new or changed pattern would not fit together with the others. Nothing is saved. Switch off or delete other regex or wildcard entries first. Switching off and deleting are never refused.

Import​

Import takes one term per line, at most 1,000 lines per call. Kind, category and language apply to every line; imported entries are active and have no note. Blank lines are skipped. Each line is checked like a single entry:

  • Added: saved.
  • Already there: the term is on the list, or appears twice in the import.
  • Invalid: refused, with the same reason a single entry would get.

The result shows the totals and each line with its result; by default only the lines that were not added. Invalid lines do not stop the others. If the import would pass a size cap or make the list too large to check, it fails as a whole and saves nothing.

Edit, switch off, delete​

The switch in a row turns an entry on or off; an entry that is off stays saved but hits nothing. Edit changes kind, term, category, language, note or status; changing the kind or term is checked like a new entry, switching on is checked against the list size. Delete asks for confirmation and suggests switching off as a pause; a deleted entry stops hitting at once and frees its place in the caps.

Check a text​

Check a text shows whether the active list hits a text (up to 256 characters) and which entry hits first: the oldest active entry that matches, with its term, kind and category. It reads the list straight from the database, so it reflects a save made a moment ago. The text is neither stored nor logged. Use it to try a new pattern against real names before you rely on it.

Audit log​

Every change is in the System-scope audit log:

EventWhenMetadata
system:blocklist_entry_createdAn entry was addedentry_id, kind, category, pattern
system:blocklist_entry_updatedAn entry was changed or switched on or offas above, plus changed (the field names)
system:blocklist_entry_deletedAn entry was deletedas above
system:blocklist_entry_importedAn import rankind, category, locale and the counts created, duplicate, invalid

The pattern is recorded so the log answers "who added or removed this term?"; an import records counts only. Checking a text writes no audit row.

API​

The REST and GraphQL operations are documented in REST API → Global blocklist and GraphQL → Global blocklist. How a module uses the list: Global blocklist (developer guide).