Tested on: Excel 365 v2509 · Excel 2021 · Excel 2019 · last verified 2026-06-08
TL;DR — On Error decides what your macro does after a runtime error: by default it stops and throws a dialog in the user's face. On Error changes that. There are exactly three forms, and using the wrong one is how macros end up silently corrupting data:
Sub ProcessData()
On Error GoTo ErrHandler ' any error below jumps to the handler
Dim ws As Worksheet
Set ws = Sheets("Report") ' if "Report" doesn't exist → jump down
ws.Range("A1").Value = 100
Exit Sub ' STOP here, or you fall into the handler
ErrHandler:
MsgBox "Step failed: " & Err.Description
End Sub
The three forms, in one block:
On Error GoTo ErrHandler ' route errors to a labelled handler — the reliable choice
On Error Resume Next ' ignore the error, run the next line — dangerous if you forget
On Error GoTo 0 ' turn trapping OFF again — back to "stop and show the dialog"
The mental model: On Error is a switch for "where do I go when I crash?"
Picture every line of your macro driving down a road. The default behaviour when a line hits a runtime error — a missing sheet, a /0, a type mismatch — is to slam on the brakes: execution halts, and Excel shows the yellow Run-time error '…' box with Debug / End. For you at design time that's fine. For a user running your tool, it's a dead end they can't read.
On Error is the switch that reroutes that crash. It doesn't prevent the error — the error still happens — it only decides where control goes next. That single reframing fixes most confusion: you're not "catching" anything in the Java/Python sense, you're setting a destination for the next crash.
The switch stays flipped for the rest of the current procedure (or until you flip it again). It is per-procedure — a handler you set in ProcessData does nothing for a Sub it calls, unless that Sub sets its own.
The one rule: On Error Resume Next hides errors, it does not handle them
This is the rule that separates a robust macro from a time bomb:
On Error Resume Nexttells VBA "whatever just broke, pretend it didn't and keep going." It silences the symptom. It does not fix or even record the problem.
Used deliberately, on one line you expect to fail, it's perfect. Used carelessly — one On Error Resume Next at the top of a Sub and then forgotten — it silences every bug in that procedure. The macro "works," writes half-blank results, and nobody finds out for a month.
The honest pattern is narrow scope + an immediate check + switch it back off:
' We WANT to delete a shape that may or may not exist.
On Error Resume Next ' arm it for exactly one risky line
ActiveSheet.Shapes("OldLogo").Delete
If Err.Number <> 0 Then Err.Clear ' acknowledge & reset — the shape simply wasn't there
On Error GoTo 0 ' DISARM immediately — stop ignoring errors
Three things make that safe: it covers a single line, it checks Err.Number right after (see the Err object), and it calls On Error GoTo 0 to disarm. Drop any one of them and you're back to hiding bugs.
The second trap: forgetting Exit Sub before the handler
A labelled handler is just code at the bottom of the procedure. Nothing stops the normal path from walking straight into it once the real work finishes:
Sub SaveReport()
On Error GoTo ErrHandler
' ... real work that succeeds ...
MsgBox "Saved!" ' success message
' ⚠ no Exit Sub — execution keeps going...
ErrHandler:
MsgBox "Save failed." ' ...and the user sees BOTH messages
End Sub
On a successful run the user gets "Saved!" and "Save failed." The fix is one line: Exit Sub (or Exit Function) immediately before the handler label, so the normal path leaves the procedure before it can fall through. This is the structural backbone covered in VBA Error Handling.
On Error GoTo 0 — the form everyone forgets
On Error GoTo 0 resets error trapping to the default "stop and show the dialog." Two reasons it matters:
- After a deliberate
Resume Next, it's how you stop ignoring errors so the next real bug isn't swallowed. - During development, it's how you let VBA break on the offending line so you can hit Debug — instead of an active handler whisking you away from the scene of the crime.
On Error Resume Next
Set wb = Workbooks("Budget.xlsx") ' maybe open, maybe not
On Error GoTo 0 ' from here on, errors stop the macro again
If wb Is Nothing Then Set wb = Workbooks.Open("C:\Data\Budget.xlsx")
The opinion: a bare On Error Resume Next with no Err check is a bug, not robustness
People reach for On Error Resume Next because it makes the red dialog go away, and "no error message" feels like "no error." It isn't. A macro that can't fail loudly is a macro you can't trust — it just fails quietly into your data instead.
My rule of thumb: every On Error Resume Next must have a matching Err.Number check and an On Error GoTo 0 within a few lines. If you can't point to those two companions, you haven't handled the error — you've buried it. For anything beyond skipping one expected failure, use On Error GoTo Label and an actual handler.
When to use which
| You want to… | Use | Watch out for |
|---|---|---|
| Route all errors to one place and react | On Error GoTo ErrHandler |
Put Exit Sub before the label |
| Skip one line you expect might fail | On Error Resume Next (narrow) |
Check Err.Number, then On Error GoTo 0 |
| Stop ignoring / debug on the real line | On Error GoTo 0 |
Without it, Resume Next stays armed |
| Retry the failed line after a fix | Resume (in a handler) |
Infinite loop if the cause isn't fixed |
Common On Error mistakes (and the fix)
| Symptom | Cause | Fix |
|---|---|---|
| Macro runs but results are half-empty | On Error Resume Next left on, swallowing real errors |
Remove it, or scope it to one line + On Error GoTo 0 |
| User sees success and failure message | No Exit Sub before the handler label |
Add Exit Sub immediately before ErrHandler: |
| "Label not defined" | On Error GoTo X but no X: label in this procedure |
Add the label, or use Resume Next/GoTo 0 |
| Handler fires but you can't tell what broke | Never read Err.Description / Err.Number |
Read the Err object in the handler |
| Error still stops the macro after a handler runs | Handler ran but didn't Resume or Exit |
Decide the exit: Resume Next, Resume, or Exit Sub |
When error handling is most of your macro — describe the job instead
Notice how much of a "simple" macro turns into plumbing: arm the handler, check Err, disarm, clean up, decide whether to resume. On a real pipeline — import three files, match on order ID, flag the mismatches — the error scaffolding can outweigh the logic you actually care about. ExcelMaster Agent lets you state that job in plain English and generates Python that already handles failures and backs up your file first — no On Error, no Resume, no quiet data corruption. Try it free →
Related guides
- VBA Error Handling — The One Pattern Every Reliable Macro Uses
- VBA Err Object — Number, Description & Raising Your Own Errors
- VBA Sub in Excel — Procedures, Calling & Why Your Macro Is Just a Sub
- VBA For Loop in Excel — 8 Real-World Examples
FAQ
What does On Error do in VBA?
On Error sets what happens when a runtime error occurs in the current procedure. Instead of the default (stop and show the Run-time error dialog), you can route the error to a handler (On Error GoTo Label), skip the failing line (On Error Resume Next), or restore the default (On Error GoTo 0).
Is On Error Resume Next bad practice?
Not inherently — but only when scoped to a single line you expect might fail, followed by an Err.Number check and On Error GoTo 0. Left on across a whole procedure it silences every error, which hides bugs and corrupts results silently. For general handling, prefer On Error GoTo Label.
What is the difference between On Error GoTo 0 and On Error Resume Next?
On Error Resume Next ignores errors and continues to the next line. On Error GoTo 0 does the opposite — it turns error trapping off so the next error stops the macro and shows the dialog again. You typically use GoTo 0 to disarm a Resume Next block.
How do I turn off error handling in VBA?
Use On Error GoTo 0. It clears any active On Error Resume Next or On Error GoTo Label setting for the current procedure and restores VBA's default break-on-error behaviour.
Does On Error work across called procedures?
No. On Error is per-procedure. If A sets a handler and calls B, an error in B is not caught by A's handler unless B has no handler of its own — in which case the error propagates up to A. Each procedure that needs handling should set its own.
