In Excel: Set hit = ws.Cells.Find(What:="text", LookIn:=xlValues, LookAt:=xlPart) locates the first match, and ws.Cells.Replace What:="old", Replacement:="new" changes them all in one call. Excel for the web cannot create, run, or edit VBA macros. Open the workbook in the Excel desktop app to work with macros.
On this page8
VBA macro: Find and Replace with VBA
Sub FindAndReplaceInSheet()
Dim ws As Worksheet
Dim hit As Range
Set ws = ActiveSheet
Set hit = ws.Cells.Find(What:="draft", LookIn:=xlValues, _
LookAt:=xlPart, MatchCase:=False)
If hit Is Nothing Then
MsgBox "Nothing to replace.", vbExclamation
Exit Sub
End If
MsgBox "First match: " & hit.Address, vbInformation
ws.Cells.Replace What:="draft", Replacement:="final", _
LookAt:=xlPart, MatchCase:=False
End SubRange.Find returns Nothing rather than raising an error when there is no match, so the If hit Is Nothing guard is required — using .Address on Nothing is error 91. Always name LookIn, LookAt and MatchCase explicitly; unset arguments inherit whatever the user last chose in the Find dialog.
How to run this macro
- Press Alt + F11 to open the VBA editor.
- Insert > Module.
- Paste the code above.
- Press
F5, or close the editor and run it from Developer > Macros. - Save the file as .xlsm so the macro is kept.
Decide whether you need the location of matches (Find) or just to change them (Replace).
For Find, assign the result with Set and name What, LookIn, LookAt and MatchCase explicitly.
Guard immediately with If hit Is Nothing Then — a miss returns Nothing, not an error.
To visit every match, save the first hit's Address and loop with FindNext until the address comes round again.
For a bulk change, call Range.Replace once on the whole range instead of looping — it is dramatically faster.
What this does
VBA offers three related tools and they solve different problems. Range.Find locates a value and hands back the cell as a Range object, or Nothing when there is no match — it tells you where something is. Range.Replace changes every match in the target range in a single call and returns only True or False; it is far faster than looping when you do not need to know which cells changed. FindNext continues a search from a previous hit, and the correct way to iterate is to remember the address of the first result and stop when the search wraps back round to it, since Find loops the range endlessly otherwise. Separately, VBA's own Replace() function works on a string rather than a sheet: Replace(text, "a", "b") returns a modified copy and never touches a cell, which is what you want inside a loop over values you have already read. The single biggest trap is that Find's unspecified arguments persist from the last search anyone performed, including one a user did through the dialog — so a macro that omits LookIn behaves differently depending on the session. A macro runs in the desktop app's VBA environment, not in the cell grid: the workbook has to be saved in a macro-enabled format such as .xlsm to keep the code, and your macro security settings — or a file that arrived from the internet — can block it until you trust it. Excel for the web cannot run VBA at all. The difference between a quick fix and a sheet you can trust is the extra minute you spend validating “excel vba substitute”. Start on a copy or a tiny sample, keep the affected cells visible, and compare the result with the outcome you expected before you touch the real workbook. When this is a ribbon command, the selection matters more than the button: confirm the range, apply the command, then spot-check the output before saving. The point is a text operation that turns messy entries into clean, usable data, but the practical win is that someone else can open the file and understand what happened without asking you.
A worked example
A sheet full of records is marked "DRAFT" in mixed positions and casing, and every one should read "FINAL". Sub FindAndReplaceInSheet() first calls Find to confirm at least one match exists and reports its address, then calls Replace once to change them all. The If hit Is Nothing guard is what stops the macro dying on the .Address line when the sheet is already clean. When you need to act on each match rather than just rewrite it — say, colouring every cell containing "draft" — use the FindNext pattern: store firstAddress = hit.Address, do the work, then Set hit = ws.Cells.FindNext(hit) and loop while Not hit Is Nothing And hit.Address <> firstAddress. Without that address comparison the loop never ends, because Find wraps around to the beginning of the range. Find and Replace is where macros meet messy real data — inconsistent labels, stray markers, values that need normalising before anything else can run. The two behaviours worth internalising are that a miss returns Nothing rather than erroring, and that omitted arguments are inherited rather than defaulted; between them they account for most of the bug reports this pair of methods generates. Run a new macro on a copy of the workbook first. Do not count on Undo to reverse what a macro changed; the saved copy is your way back.
In Google Sheets
Google Sheets does not run VBA. A macro written for Excel has to be rewritten in Google Apps Script (Extensions ▸ Apps Script), and an .xlsm file converted to Sheets keeps its cells but drops the code. The aim was to get you unstuck fast and leave you a little more capable than a copy-paste would. The answer is at the top and the detail above shows why it holds — so the next time a colleague asks, you can answer without reaching for search. Treat “excel vba substitute” as code you are responsible for: know what it changes, keep a copy of the workbook before running it and keep the file in a macro-enabled format. In Google Sheets and Excel for the web this code does not run; those need a different mechanism.
Common mistakes
- Using the result of Find without checking for Nothing. Any property access on Nothing raises error 91, and a sheet that simply has no match is the normal case, not an exceptional one.
- Leaving LookIn and LookAt unspecified. Those arguments persist between searches — including from the user's last use of Ctrl + F — so the same macro can match whole cells one day and partial text the next.
- Looping FindNext without tracking the first address. The search wraps around at the end of the range, so the loop runs forever; comparing against the stored first address is the standard termination test.
- Searching with LookIn:=xlValues when you meant formulas. xlValues sees displayed results, xlFormulas sees the underlying text, so a macro hunting for a cell reference must use the latter.
- Confusing Range.Replace with VBA's Replace() function. The first edits cells on a sheet, the second returns a modified string and changes nothing — using the wrong one produces code that appears to do nothing at all.
Frequently asked questions
Why does my Find raise error 91?
Because it found nothing and returned Nothing, and the next line asked it for a property. Test If hit Is Nothing Then before touching the result — that branch is not optional.
How do I loop through every match?
Save the first hit's Address, act on it, then Set hit = rng.FindNext(hit) and repeat while the address differs from the saved one. Find wraps at the end of the range, so that comparison is what ends the loop.
Is Replace faster than looping over cells?
Considerably. Range.Replace performs the whole operation inside Excel in one call, while a loop crosses the VBA-to-Excel boundary once per cell. Loop only when each match needs different handling.
How do I search only part of a cell rather than the whole value?
Pass LookAt:=xlPart. LookAt:=xlWhole requires the entire cell to equal the search text. Always state it explicitly, because the unset value carries over from the previous search.