Jump to content

Edit check/TextMatch

From mediawiki.org

A wiki community can create custom local suggestions using TextMatch. TextMatch is an edit check that searches for predetermined queries and, when a match is found, it displays the regular edit check message-box, along with the message, title, and potential actions for the editor to make.

For example, a TextMatch might provide an option to delete the matched term, or to replace it in a specified way, or just to encourage the editor to think about it. TextMatch checks can be very simple, or can be made more powerful with the configuration options.

Here are two examples. The first detects a potential relative-time phrase and links to the related guidance. The second detects some potential punctuation errors and provides a button to fix them.

JSON example What editors will see
{
	"textMatch": {
		"matchRules": {
			"nonspecific-time": {
				"query": [
					"in the (past|future)",
					"recently",
					"to this day"
				],
				"title": "Indefinite time",
				"message": "This phrase might contain a [[MOS:RELTIME|relative time reference]]. Absolute specifications of time are usually preferred, otherwise they may go out of date. Help readers to understand by writing the specific time that is being referred to here.",
				"isRegExp": true,
				"config": {
					"ignoreQuotedContent": true
				}
			},
			"punctuation-typos-dash": {
				"query": {
					"--": "—",
					"\\s-\\s": " — ",
					"\\s-(\\d+)": " −$1",
					"([^\\d\\s]+)(\\s—|—\\s)([^\\d\\s]+)": "$1 — $3",
					"([А-ЯЁA-Z][^\\d\\s]*)(?\u003C![XVI]+)—(?![XVI]+)([А-ЯЁA-Z][^\\d\\s]*)": "$1 — $2"
				},
				"title": "Check Punctuation",
				"message": "There may be a punctuation error here that needs to be corrected. Check to see if the [[MOS:HYPHEN|hyphen]] needs to be replaced with a specific [[MOS:DASH|dash]], [[MOS:COMMONMATH|minus]], or vice versa, and whether it needs to be surrounded by spaces.",
				"isRegExp": true,
				"mode": "replace",
				"config": {
					"caseSensitive": true
				}
			}
		}
	}
}
(mobile)

or

(desktop)

Current limitations

[edit]

As of July 2026:

  • TextMatch will not yet detect content within templates.
  • There is no way to exclude lists or other code tags from matching, nor to limit content to just lists, tables, headings, etc. See inNode below.
  • See Regex queries for more details on regex-specific limitations.
  • Open bugs and feature-requests: Phabricator search

Configuration

[edit]

TextMatch's configuration consists of the normal Edit check/Configuration, and a number of matchRules. Each matchRule consists of something to search for and a description of what should be done when that thing is found.

The matchRule definition is fairly flexible, and can accommodate anything from finding a simple word and showing a provided warning, to finding a complicated set of regular expressions and replacing them with something that varies based on what was found. See the #Example configuration section below for practical demonstrations of these cases.

Name Required? Type Description Default
unique string id Required String The short-name of the specific TextMatch
query Required Array or Object What to search for. This can be in various formats:
  • A list of terms or regex patterns
  • A mapping of terms or regex patterns, and their replacements

Note that any matches must begin and end at word boundaries. For example, if the query is "foo", a match would not be detected in "foobar". This applies to both regex and non-regex matchRules.

TextMatch uses the same JavaScript regular expressions that is used in VisualEditor's find/replace feature.

title Required String What will be displayed to users in the edit check dialog. This should only include plain text.
message Required String What will be displayed to users in the edit check dialog. This can include wiki links, and should point the user to any relevant documentation so they can learn more.
mode Optional String What actions (buttons) should be provided in the edit check dialog. Currently available modes include: "info" (just "dismiss"), "replace" ("replace" and "dismiss"), "delete" ("delete" and "dismiss")

If the mode is unknown or undefined, then it will only show the "dismiss" button.

""
isRegExp Optional Boolean If true, TextMatch will treat this query as a regex pattern. See Regex queries for more details. false
config Optional Object Various configuration options that can operate on the matchRule level. These are added on top of any configuration that is applied to the entire group of TextMatch editchecks, which they will override.
  • The generic editcheck visibility configurations (Edit check/Configuration ) such as ignoreQuotedContent, ignoreSections, ignoreDisambiguationPages, etc.
    • Using both showAsCheck:false and showAsSuggestion:false will classify the rule as "Experimental", and it will not be shown to normal editors. Experimental rules can be temporarily enabled using the #Testing instructions below. (Note: The entry will not be relocated within the Special:EditChecks page.)
  • caseSensitive - this is false by default
  • minOccurrences - an integer value for how many instances of a query need to be matched within a paragraph before the editcheck will be shown.

In the future, we'd like to support more, such as: namespace, and specific page-name.

inNode Optional String Require the match to appear inside a node of this type. Node types are from the VisualEditor data model, e.g 'mwHeading' for headings, 'paragraph' for paragraphs.
expand Optional String If a match is found, this option configures whether to expand the 'problem area' (the area that is highlighted) to surround more content, instead of just the matched text.

Available options are:

  • "word"
  • "paragraph"
  • "siblings" (all sibling nodes)
  • "parent" (the entire containing node)
preserveCase Optional Boolean If true, and if the matchRule is using mode: "replace", then the suggested replacement term will be in the same case as the matched term (limited to: title case, lowercase, and uppercase).
The case that the replacement has in the matchRule config is the lowest case that will ever be offered as a replacement. This is so that words like 'French' or 'USA' are never turned into invalid forms, even if replacing something fully lowercased. As such, consider writing TextMatch replacements in lowercase unless the term requires specific casing.
false
import Optional String You can define more complex matchRules in separate JSON pages and import them, to keep the main JSON page shorter. (example) Pages must be named MediaWiki:Editcheck-config-<name>.json e.g. "import": "MediaWiki:Editcheck-config-LLM.json"


Regex queries

[edit]

There are some specific complexities for matchRules that use regex queries.

  • Plain backreferences are not supported, due to the way that all regex patterns in a query are bundled together for performance reasons. The suggested workaround is to use named backreferences.
  • When using a regex pattern with alternation, order the alternatives from longest to shortest (e.g., use "enhanc(ed|es|e|ing)" rather than "enhanc(e|ed|es|ing)") . This way the regex matches the full word before any shorter alternative can match a partial word that then fails the word-boundary check.[1]

Regex queries can always be tested using VisualEditor's "find and replace " tool (with the appropriate modifiers enabled). This is helpful to understand what it will and will not support.

  1. This is due to all TextMatch rules using a wholeWord flag that restricts matches to those surrounded by word boundaries. As of June 2026 we are considering making this property configurable.

Testing instructions

[edit]

TextMatch is part of the current Suggestion Mode Beta Feature which is available on all Wikipedias (see Deployment status).

To show the Experimental types of checks and suggestions, you can add the &ecenable=experimental URL parameter to an existing VisualEditor edit session.

To test a new type of TextMatch:

  1. Visit a wiki where TextMatchEditCheck is available and you have admin role. Login.
  2. On said wiki, visit MediaWiki:Editcheck-config.json
  3. Tap edit
  4. Insert JSON from the Example configuration or some custom configs that follow the same format.
    1. If there’s already JSON present, only copy from "textMatch"; not the outermost curly braces
  5. Visit an article on that same wiki that is likely to contain one of the matches listed in the JSON
  6. Open a VisualEditor edit session using the &ecenable=suggestions,experimental URL parameter

Example configuration

[edit]
See Help:Suggestion mode for more examples that have been created at various wikis.

Here is an example of what could be configured:

{
	"textMatch": {
		"enabled": true,
		"matchRules": {
		    "british-english": {
				"query": {
					"chiseled": "chiselled",
					"favor(ed|ite|ing)": "favour$1",
					"somberly": "sombrely"
				},
				"title": "American English",
				"mode": "replace",
				"isRegExp": true,
				"preserveCase": true,
				"message": "This article requires British English. Fix?",
				"config": {
					"maximumEditCount": 500,
					"account": "loggedin",
					"hasTemplate": [
						"Use British English",
						"EngvarB"
					]
				}
			},
			"potential-LLM": {
				"query": [
					"(stands as|serves as|is) a (testament|reminder)",
					"indelible mark",
					"important to note",
					"observers have cited"
				],
				"title": "Potential LLM content",
				"message": "Is this LLM-generated content? Every edit that incorporates LLM output should be marked as LLM-assisted by identifying the name and, if possible, version of the AI in the edit summary.",
				"isRegExp": true,
				"config": {
					"caseSensitive": false,
					"minOccurrences": 3
				},
				"expand": "paragraph"
			},
			"cliches": {
				"query": [
					"tip of the iceberg",
					"white elephant",
					"gild the lily",
					"take the plunge",
					"took the plunge",
					"ace up the sleeve",
					"bird in the hand",
					"twist of fate",
					"at the end of the day"
				],
				"title": "Cliches and idioms",
				"message": "Clichés and idioms should generally be avoided in favor of direct, literal expressions.",
				"config": {
					"account": false
				}
			},
			"relative-time-ref": {
				"query": [
					"recently",
					"lately",
					"currently",
					"today",
					"presently",
					"to date",
					"years ago",
					"formerly",
					"in the past",
					"traditionally",
					"yesterday",
					"tomorrow",
					"in the future",
					"to this day"
				],
				"title": "Relative time references",
				"message": "Absolute specifications of time are preferred to relative constructions such as this, because the latter may go out of date.",
				"config": {
					"account": false
				}
			},
			"editorializing": {
				"query": [
					"notably",
					"it should be noted",
					"arguably",
					"interestingly",
					"essentially",
					"utterly",
					"actually",
					"clearly",
					"absolutely",
					"of course",
					"without a doubt",
					"indeed",
					"happily",
					"sadly",
					"tragically",
					"aptly",
					"unfortunately",
					"untimely"
				],
				"title": "Editorializing",
				"mode": "delete",
				"message": "Use of adverbs such as these, to highlight something as particularly significant or certain without attributing that opinion, should usually be avoided so as to maintain an impartial tone.",
				"config": {
					"account": false
				}
			},
			"puffery": {
				"query": [
					"legendary",
					"greatest",
					"acclaimed",
					"iconic",
					"visionary",
					"outstanding",
					"leading",
					"celebrated",
					"popular",
					"landmark",
					"cutting-edge",
					"innovative",
					"revolutionary",
					"extraordinary",
					"brilliant",
					"famous",
					"renowned",
					"remarkable",
					"prestigious",
					"world-class",
					"respected",
					"notable",
					"virtuoso",
					"honorable",
					"awesome",
					"unique",
					"pioneering",
					"phenomenal",
					"prominent"
				],
				"title": "Puffery",
				"mode": "delete",
				"message": "Words such as these are often used without attribution to promote the subject of an article and should usually be avoided.",
				"config": {
					"account": false
				}
			}
		}
	}
}