TL;DR —
Worksheet_Changeis an event Excel fires every time a user (or your code) edits a cell on that sheet. Excel hands you the changed cell asTarget, so you can react — timestamp the row, validate the entry, log the edit. The one thing you must know: if your handler writes to a cell, that write firesWorksheet_Changeagain — and again — until Excel freezes. The fix is a single pair of lines,Application.EnableEvents = False … = True, around every write. Get that reflex and everything else is detail.
' Lives in the sheet's own object (e.g. Sheet1), NOT a Module or ThisWorkbook.
Private Sub Worksheet_Change(ByVal Target As Range)
If Intersect(Target, Range("B:B")) Is Nothing Then Exit Sub ' only react to column B
On Error GoTo Done
Application.EnableEvents = False ' stop our own write from re-firing this event
Target.Offset(0, 1).Value = Now ' stamp the time next to the edit
Done:
Application.EnableEvents = True ' ALWAYS turn events back on
End Sub
Reacting to edits is where VBA stops being "buttons that run macros" and starts
feeling alive: a cell changes and the sheet responds on its own. Worksheet_Change
is the event that powers audit trails, auto-timestamps, live validation, and
dependent dropdowns. It's also the event that produces the most panicked "Excel
froze and I lost my work" moments — because the obvious code is one small step from
an infinite loop.
What you'll learn
- The mental model — an edit sensor that hands you the changed cell as
Target - The infinite-loop trap — and the
Application.EnableEventsfix that defines event VBA - Why a crash can turn all your events off until you restart Excel
- How to scope the handler with
Intersectso it doesn't run on every edit - Why it ignores formula recalcs — and when you need
Worksheet_Calculateinstead
The mental model: an edit sensor, not a button
Worksheet_Change is a sensor wired to the sheet. You don't call it; the moment a
cell's content changes, Excel calls you, passing the changed cell(s) as a
Range named Target. Your job is to read Target and decide what to do about it.
That framing settles two things. First, Target is your entire input — it's where
the edit happened, and it can be one cell or many (a paste, a fill, a
delete-across-a-selection all arrive as a multi-cell Target). Second, the handler
lives in the specific sheet's code object — Sheet1 in the Project Explorer,
not ThisWorkbook and not a Module. The signature is fixed:
Private Sub Worksheet_Change(ByVal Target As Range). (The workbook-wide version,
for all sheets at once, is Workbook_SheetChange in ThisWorkbook.)
The trap that freezes Excel: your write re-fires the event
Here is the bug every VBA developer writes exactly once. You want to react to an edit by writing something back to the sheet:
Private Sub Worksheet_Change(ByVal Target As Range)
Target.Offset(0, 1).Value = Now ' write next to the edit... which is ITSELF an edit
End Sub
Writing to Target.Offset(0, 1) changes a cell — which fires Worksheet_Change
again — whose write fires it again — forever. Excel recurses until it runs out of
stack and throws, or simply appears frozen. The change event has no built-in guard
against reacting to its own reaction.
The fix is the most important idiom in event VBA: switch events off around any write, then switch them back on.
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
Target.Offset(0, 1).Value = Now
Application.EnableEvents = True
End Sub
With EnableEvents = False, your write doesn't trigger a new Worksheet_Change, so
there's no recursion. Then you restore it. Burn in the rule: inside any event that
writes to cells, wrap the write in Application.EnableEvents = False … = True.
The rule that hides the trap's second half: a crash leaves events off
Application.EnableEvents is a single, application-global switch, and Excel does
not restore it automatically. That creates a nasty second failure mode: if your
handler errors after setting it to False but before setting it back, events
stay off — for the whole Excel session, across every sheet and workbook.
The symptom is bewildering: "my macros just stopped working." Your Worksheet_Change
didn't break — events are globally suppressed because an earlier run died mid-handler.
That's why the safe pattern always re-enables events in an error handler:
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo CleanExit
Application.EnableEvents = False
Target.Offset(0, 1).Value = Now
' ... more logic that might error ...
CleanExit:
Application.EnableEvents = True ' runs whether we succeeded or errored
End Sub
This is exactly the "one exit that always cleans up" discipline from
VBA On Error, and it's non-negotiable here. (If events are
already stuck off, run one line in the Immediate window — Application.EnableEvents = True — or just restart Excel.)
The rule that keeps it targeted: scope with Intersect
A bare Worksheet_Change fires for any cell on the sheet. If you only care
about edits in one column or one table, you must say so — otherwise the handler runs
(and maybe writes) on every unrelated edit. The tool is Intersect, which returns
the overlap between Target and the range you care about, or Nothing if there's
none:
Private Sub Worksheet_Change(ByVal Target As Range)
' Ignore edits outside B2:B1000
If Intersect(Target, Range("B2:B1000")) Is Nothing Then Exit Sub
' ... react only to the part that matters ...
End Sub
Two related cautions. Target can be many cells — a paste or a column-fill
hands you a multi-cell range, so code that assumes Target.Value is a single value
will error or misbehave; either loop over Target or restrict with
If Target.Count > 1 Then Exit Sub when you genuinely want single edits. And react
to the intersection, not all of Target, when a paste spans your watched range
and cells outside it.
The distinction that trips people: it ignores formula recalcs
Worksheet_Change fires on a change to a cell's content — a typed value, a
pasted value, a deletion, a VBA write. It does not fire when a cell merely shows
a new number because a formula recalculated. If C1 is =A1+B1 and A1 changes,
the event fires for A1 (the edited cell), not for C1 (the recalculated one).
When you need to react to a result changing, that's a different event —
Worksheet_Calculate — which fires on recalculation but gives you no Target
(you have to inspect the cells yourself). Choosing the wrong one is a quiet failure:
your handler simply never runs. The rule: Change = someone edited the cell;
Calculate = a formula's output moved.
Worksheet_Change is one half of the "react to the user" pair. Its sibling,
Worksheet_SelectionChange, fires when the
cursor moves rather than when a value changes — and both belong to the same event
family as Workbook_Open, the event that runs when the file
first opens.
How ExcelMaster helps
An edit-reaction macro is deceptively fiddly: right object, Intersect to scope it,
EnableEvents to avoid the loop, an error handler so a crash doesn't kill every
event in Excel. Miss the EnableEvents pair and you freeze the file; miss the error
handler and you break events silently.
ExcelMaster
lets you state the behaviour instead. Say "when someone edits column B, put the
current time in column C next to it," and it writes a Worksheet_Change in the right
sheet object — scoped with Intersect, guarded with EnableEvents, wrapped so an
error can't leave events stuck off. You keep the sheet and the code; you skip the
one-time ritual of freezing your own workbook to learn the rule.
Frequently asked questions
Why does my Worksheet_Change cause an infinite loop or freeze?
Because your handler writes to a cell, and that write is itself a change that fires
Worksheet_Change again — endlessly. Wrap every write in
Application.EnableEvents = False before it and Application.EnableEvents = True
after it, and always re-enable in an error handler so a crash can't leave events off.
How do I make Worksheet_Change run only when a specific column changes?
Use Intersect at the top of the handler:
If Intersect(Target, Range("B:B")) Is Nothing Then Exit Sub. That exits
immediately unless the edit touched column B. Intersect returns the overlap
between the edited range and the range you care about, or Nothing when there's no
overlap.
Does Worksheet_Change fire when a formula recalculates?
No. It fires only when a cell's content is edited — typed, pasted, deleted, or
written by VBA. A cell that shows a new value because a formula recalculated does not
trigger it. For that, use the Worksheet_Calculate event, which fires on
recalculation but does not give you a Target.
Where does Worksheet_Change code go?
In the code module of the specific worksheet — double-click the sheet (e.g.
Sheet1) under "Microsoft Excel Objects" in the Project Explorer and put
Private Sub Worksheet_Change(ByVal Target As Range) there. It does not work from a
standard Module. For all sheets at once, use Workbook_SheetChange in
ThisWorkbook.
My VBA events stopped working — how do I fix it?
An earlier handler probably set Application.EnableEvents = False and errored before
restoring it, leaving events globally off. Type Application.EnableEvents = True in
the Immediate window (Ctrl+G) and press Enter, or restart Excel. Prevent it by
always re-enabling events in an error handler.
Tested in
Tested in: Excel 365 (Windows 11), VBA 7.1 — last verified 2026-08-02.
Related guides: VBA Worksheet_SelectionChange · VBA Workbook_Open · VBA On Error · VBA Range · VBA For Loop
