Scrub for Craft CMS

Finding and replacing

Everything happens on Scrub → Replace. You fill in what to find, what to replace it with, where to look, and press Preview.

The preview is the run

This is the part worth understanding, because it's the reason to use Scrub at all.

The preview and the run are the same method with one boolean between them. Scrub scans, matches, and computes every replacement exactly as it would for real, and then either writes the result or throws it away. What you are shown is not a prediction of what would happen. It is what happened, with the last step skipped.

That matters because the usual failure of a find-and-replace is not "it didn't match". It's "it matched somewhere I forgot existed". A preview built from a separate, cheaper code path can't tell you about those places, because it isn't looking the way the real run looks.

The preview lists up to 200 places by default, and up to 10 individual matches inside each one. Past that it stops listing and starts counting, so a run across four thousand entries still tells you the total.

The three modes

Plain matches the characters you typed, anywhere they appear. cat matches cat, catalogue and concatenate.

Whole word matches only where the text stands on its own. cat matches cat and cat, but not catalogue. This is the one you want for renaming a product or a company, and it's the one people forget exists until after they've turned every form on the site into a formie.

Regular expression (Pro) matches a pattern, with capture groups available in the replacement as $1, $2 and so on. Scrub picks a delimiter that doesn't appear in your pattern, so you never escape slashes. multiline and dotAll are separate switches rather than flags you have to remember.

Find:     (\d{4})-(\d{2})-(\d{2})
Replace:  $3/$2/$1

2026-08-18  →  18/08/2026

A malformed pattern is rejected at the preview, with the error, before anything runs.

Case

Case sensitive is on by default, and is the ordinary meaning of it: Acme does not match acme.

Keep the original capitalisation (Pro) is the interesting one. Turn it off and a single rule handles every casing at once:

Find: widget   Replace: gadget

widget  →  gadget
Widget  →  Gadget
WIDGET  →  GADGET

It works word by word, not by copying the case of the whole string, which is what makes multi-word replacements come out right. Acme LtdAcme Inc, not acme inc.

Running it

Press Replace and one of two things happens.

Under 100 matching places, it runs in the request and you get the result immediately. Over that, it's handed to the queue, because a request that saves eight hundred elements is a request that times out. The threshold is queueThreshold and you can set it to 0 to always run in the request.

Either way the run is recorded, every changed value is stored before and after, and the run appears in History where it can be undone.

The ceiling

By default no single run may change more than 5,000 places. If the preview says more, the run refuses.

This is a guardrail against the mis-scoped rule, which is the way this goes wrong in practice. You meant to rename a product inside one section and you left the scope empty. Failing loudly at the preview is better than succeeding quietly across eighty thousand entries. Raise maxUnitsPerRun when you genuinely mean it, or set it to 0 to remove the ceiling.