Learn how to wire VBA macros to Excel's built-in workbook events so your spreadsheets automatically respond when users open, save, or change data. This hands-on lesson covers Workbook_Open, BeforeSave, BeforeClose, and Worksheet_Change with real-world examples you can use immediately.

Imagine you manage a weekly sales report that gets opened every Monday morning by a dozen colleagues. Every time someone opens it, they need to refresh the data, check that they're viewing the current week's tab, and make sure their name gets logged in the audit trail. Right now, you've sent a three-step instruction email that half the team ignores. What if the workbook just did all of that automatically the moment someone opened it?
That's the promise of workbook events in VBA. An event is simply something that happens in Excel — a workbook opens, a cell changes, a sheet gets activated, someone tries to save. When you connect a macro to an event, you're telling Excel: "When this happens, run that code automatically." No button to click, no macro to remember. The automation just fires.
By the end of this lesson, you'll be able to wire macros to Excel's most useful workbook events so your spreadsheets respond intelligently to what users do. You'll understand where this code lives, why it works differently from regular macros, and how to avoid the common traps that trip people up.
What you'll learn:
ThisWorkbook module) and why it mattersWorkbook_Open)Workbook_BeforeSave, Workbook_BeforeClose)Worksheet_Change, Workbook_SheetChange)You should be comfortable with the basics of the VBA editor — opening it, writing simple Sub procedures, and running them. If you haven't done that yet, start with Getting Started with VBA Macros in Excel and then Introduction to VBA: Write Your First Excel Macro and Automate Repetitive Tasks before continuing here. You don't need to be an expert, but you should know what a variable is and how an If statement works.
Before writing any code, let's build a mental model of what's actually happening under the hood.
Excel's object model is a hierarchy of objects — the Application sits at the top, Workbooks live inside it, Worksheets live inside Workbooks, and Ranges live inside Worksheets. You can read more about this hierarchy in Understanding Excel's Object Model: Workbooks, Worksheets, Ranges, and Cells as the Foundation for VBA Automation. Each of these objects can raise events — they broadcast signals when interesting things happen to them.
Think of it like a smart home system. Your front door (the object) can raise events: DoorOpened, DoorLocked, DoorLeftOpenTooLong. You can write instructions (event handlers) that say "when DoorOpened fires, turn on the hallway lights." The door doesn't care what you do with the event — it just fires it. What happens next is entirely your code's business.
In Excel, the Workbook object raises events like Open, BeforeSave, BeforeClose, SheetChange, and about a dozen others. The Worksheet object raises events like Change, SelectionChange, and Activate. To wire your code to these events, you write event handler procedures — specially named Sub routines — inside the correct code module.
Key insight
The name of the event procedure is not arbitrary. Excel looks for procedures with exact names like Workbook_Open or Worksheet_Change. If you misspell the name or put the code in the wrong module, nothing will happen and Excel won't warn you. It just won't fire.
Open the VBA editor with Alt + F11. In the Project Explorer on the left side, you'll see a tree structure for your workbook. Under your workbook's name, you'll see a folder called "Microsoft Excel Objects." Inside that folder, you'll find entries for each worksheet (like Sheet1, Sheet2) and one called ThisWorkbook.
Double-click ThisWorkbook to open its code pane. This is the dedicated home for all workbook-level event procedures. Code written here has direct access to the workbook's events.
Notice the two dropdowns at the top of the code pane. The left dropdown lets you select an object ("Workbook" is the one you want). The right dropdown lists every available event for that object. When you select an event from the right dropdown, VBA automatically generates the correct procedure stub for you — the right name, the right parameters, everything. This is the fastest and safest way to start an event handler.
Tip
Always use the dropdowns to generate event stubs rather than typing the procedure name by hand. One typo in the procedure name means your event will silently never fire.
The Workbook_Open event is the most commonly used workbook event. It fires automatically every time the workbook is opened, before the user can interact with anything.
In the ThisWorkbook module, select "Workbook" in the left dropdown and "Open" in the right dropdown. VBA generates this stub:
Private Sub Workbook_Open()
End Sub
Now let's build something realistic. Say your workbook is a quarterly budget tracker. When it opens, you want to:
Private Sub Workbook_Open()
Dim wsLog As Worksheet
Dim wsTarget As Worksheet
Dim currentMonth As String
Dim nextRow As Long
' Navigate to current month's sheet
currentMonth = Format(Date, "MMMM")
On Error Resume Next
Set wsTarget = Me.Worksheets(currentMonth)
On Error GoTo 0
If Not wsTarget Is Nothing Then
wsTarget.Activate
wsTarget.Range("A1").Select
Else
MsgBox "Could not find a sheet named '" & currentMonth & "'.", vbExclamation
End If
' Log who opened the file and when
Set wsLog = Me.Worksheets("AuditLog")
If Not wsLog Is Nothing Then
nextRow = wsLog.Cells(wsLog.Rows.Count, "A").End(xlUp).Row + 1
wsLog.Cells(nextRow, "A").Value = Environ("USERNAME")
wsLog.Cells(nextRow, "B").Value = Now()
wsLog.Cells(nextRow, "C").Value = "Workbook Opened"
End If
' Greet the user
MsgBox "Welcome to the Budget Tracker." & vbCrLf & _
"Today is " & Format(Date, "Long Date") & ".", vbInformation
End Sub
A few things worth noting about this code. Me refers to the workbook the code lives in — it's cleaner and safer than referring to the workbook by name, especially if the file ever gets renamed. Environ("USERNAME") pulls the Windows username of whoever opened the file without asking them anything. The On Error Resume Next block around the sheet lookup prevents a crash if the sheet doesn't exist — we check afterward with If Not wsTarget Is Nothing. You can learn more about robust error handling patterns in Error Handling and Debugging VBA Code Like a Pro.
Warning
Workbook_Open requires macros to be enabled to run. If a user opens the file with macros disabled, the event won't fire at all. Consider adding instructions in your workbook for enabling macros, or use a "splash sheet" that explains the requirement.
The BeforeSave and BeforeClose events are powerful because they let you intercept an action before it completes — and optionally cancel it. Both events receive a Cancel parameter: if you set Cancel = True inside the handler, Excel abandons the operation entirely.
Imagine your budget tracker has a required "Approved By" field in cell B2 of the Summary sheet. You want to prevent anyone from saving unless that field is filled in.
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Dim wsSum As Worksheet
Dim approver As String
Set wsSum = Me.Worksheets("Summary")
approver = Trim(wsSum.Range("B2").Value)
If approver = "" Then
MsgBox "You must enter an approver name in Summary!B2 before saving.", _
vbExclamation, "Save Blocked"
Cancel = True ' Abort the save
wsSum.Activate
wsSum.Range("B2").Select
End If
End Sub
The SaveAsUI parameter is True when the user is doing a Save As (which opens the file dialog), and False for a regular save. This lets you handle the two scenarios differently if needed — for example, you might want to allow Save As but block regular saves in certain conditions.
Tip
When you set Cancel = True, always show the user a message explaining why the save was blocked. Silently preventing a save with no feedback is a terrible user experience and will generate confused support requests.
The BeforeClose event fires when a user tries to close the workbook. This is a great place to clean up temporary data, reset states, or warn users about unsaved work.
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Dim wsLog As Worksheet
Dim nextRow As Long
Dim response As Integer
' If there are unsaved changes, ask the user what they want to do
If Me.Saved = False Then
response = MsgBox("You have unsaved changes. Save before closing?", _
vbYesNoCancel + vbQuestion, "Unsaved Changes")
Select Case response
Case vbYes
Me.Save
Case vbNo
' Let it close without saving — do nothing
Case vbCancel
Cancel = True ' Abort the close
Exit Sub
End Select
End If
' Log the close event
Set wsLog = Me.Worksheets("AuditLog")
nextRow = wsLog.Cells(wsLog.Rows.Count, "A").End(xlUp).Row + 1
wsLog.Cells(nextRow, "A").Value = Environ("USERNAME")
wsLog.Cells(nextRow, "B").Value = Now()
wsLog.Cells(nextRow, "C").Value = "Workbook Closed"
End Sub
Warning
Be careful about calling Me.Save inside BeforeClose. If you save the workbook programmatically and then the user cancels something else, you may have already committed changes they didn't intend to keep. Always confirm intent before auto-saving.
Sheet-level events open up a whole new category of automation: reacting to what users type or do on a worksheet. There are two main places to handle these events.
If you want to react to changes on a specific sheet, double-click that sheet's module in the Project Explorer (e.g., Sheet1) and write the handler there.
Here's a practical example: you have a data entry sheet where column D should always contain a status of "Pending," "Approved," or "Rejected." When someone types anything else, you want to highlight the cell in red and show a warning. This pairs well with Excel Data Validation Techniques: Drop-Down Lists, Custom Rules, and Input Controls for Reliable Data Entry, which covers the formula-based approach — but VBA lets you respond dynamically after the fact.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim validStatuses As Variant
Dim cell As Range
Dim isValid As Boolean
Dim i As Integer
' Only care about changes in column D
If Intersect(Target, Me.Columns("D")) Is Nothing Then Exit Sub
validStatuses = Array("Pending", "Approved", "Rejected")
' Loop through each changed cell (handles paste operations)
For Each cell In Intersect(Target, Me.Columns("D"))
If cell.Value = "" Then
cell.Interior.ColorIndex = xlNone
Else
isValid = False
For i = 0 To UBound(validStatuses)
If cell.Value = validStatuses(i) Then
isValid = True
Exit For
End If
Next i
If isValid Then
cell.Interior.ColorIndex = xlNone
Else
cell.Interior.Color = RGB(255, 180, 180) ' Light red
MsgBox "'" & cell.Value & "' is not a valid status." & vbCrLf & _
"Please enter: Pending, Approved, or Rejected.", _
vbExclamation, "Invalid Entry"
End If
End If
Next cell
End Sub
The Target parameter is a Range object representing the cell or cells that just changed. The Intersect function is crucial here — it returns Nothing if Target and your watch range don't overlap, letting you exit immediately when changes happen in other columns. Without this check, your code would run on every single change anywhere in the sheet.
Warning
The most notorious pitfall with Worksheet_Change is infinite loops. If your handler writes to a cell, that triggers another Change event, which triggers your handler again, and so on until Excel crashes. The fix is to disable events before writing, then re-enable them: Application.EnableEvents = False before your write, and Application.EnableEvents = True after it.
Here's how to safely update a cell inside a Worksheet_Change handler:
Private Sub Worksheet_Change(ByVal Target As Range)
' Only watch column B
If Intersect(Target, Me.Columns("B")) Is Nothing Then Exit Sub
Application.EnableEvents = False ' Prevent re-triggering
' Stamp the modification time in column C
Intersect(Target, Me.Columns("B")).Offset(0, 1).Value = Now()
Application.EnableEvents = True ' Re-enable events
End Sub
If you want to monitor changes across every sheet in the workbook, use Workbook_SheetChange in the ThisWorkbook module. It receives two parameters: Sh (the worksheet where the change happened) and Target (the changed range).
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim wsLog As Worksheet
Dim nextRow As Long
' Ignore changes on the AuditLog sheet itself
If Sh.Name = "AuditLog" Then Exit Sub
Application.EnableEvents = False
Set wsLog = Me.Worksheets("AuditLog")
nextRow = wsLog.Cells(wsLog.Rows.Count, "A").End(xlUp).Row + 1
wsLog.Cells(nextRow, "A").Value = Environ("USERNAME")
wsLog.Cells(nextRow, "B").Value = Now()
wsLog.Cells(nextRow, "C").Value = Sh.Name
wsLog.Cells(nextRow, "D").Value = Target.Address
wsLog.Cells(nextRow, "E").Value = Target.Value
Application.EnableEvents = True
End Sub
This creates a complete audit trail of every cell change across the entire workbook — who changed what, where, and when. For production workbooks where data integrity matters, this kind of logging is invaluable.
While Open, BeforeSave, BeforeClose, and SheetChange cover most use cases, a few others are worth having in your toolkit:
Workbook_AfterSave — Fires after a successful save. Useful for logging save events or sending automatic backups. Note this event was added in Excel 2010.
Workbook_SheetActivate — Fires whenever the user switches to a different sheet. Useful for updating a header or refreshing data specific to that sheet.
Workbook_NewSheet — Fires when a new sheet is added. You can automatically apply formatting templates or naming conventions.
Private Sub Workbook_NewSheet(ByVal Sh As Object)
' Apply standard formatting whenever a new sheet is created
Sh.Tab.Color = RGB(68, 114, 196) ' Blue tab
Sh.Range("A1").Value = "Created by: " & Environ("USERNAME")
Sh.Range("A2").Value = "Created on: " & Format(Date, "DD-MMM-YYYY")
End Sub
Let's build a small but complete event-driven workbook from scratch. This exercise ties together everything in the lesson.
Setup: Create a new workbook with three sheets: rename them Dashboard, DataEntry, and AuditLog. In AuditLog, add headers in row 1: User, Timestamp, Sheet, Cell, NewValue, Event.
Your tasks:
In ThisWorkbook, write a Workbook_Open handler that activates the Dashboard sheet and logs the open event to AuditLog.
In ThisWorkbook, write a Workbook_BeforeSave handler that checks whether cell B1 on Dashboard contains a project name (it should not be empty). If it's empty, block the save and navigate the user to that cell.
In the DataEntry sheet module, write a Worksheet_Change handler that watches column A. Whenever a value is entered in column A, it automatically timestamps column B in the same row with the current date and time. Remember to use Application.EnableEvents = False/True to prevent loops.
In ThisWorkbook, write a Workbook_SheetChange handler that logs every change to the AuditLog sheet (excluding changes to AuditLog itself).
Test each piece individually before combining them. When you're confident each works, open the workbook fresh to confirm the Open event fires, try entering values in DataEntry, and try saving with an empty Dashboard!B1.
My event handler never fires.
First, verify macros are enabled. Second, confirm the code is in the right module — Workbook_Open must be in ThisWorkbook, not in a regular module. Third, check the spelling of the procedure name character by character. Fourth, make sure Application.EnableEvents hasn't been left as False somewhere — this is a common residue from debugging. Type Application.EnableEvents = True in the Immediate Window and press Enter to reset it.
My Worksheet_Change handler causes a stack overflow or freezes Excel.
This is the infinite loop problem. You wrote to a cell without disabling events first. Add Application.EnableEvents = False before any write operations and Application.EnableEvents = True immediately after. Wrap the whole thing in error handling so that if your code crashes mid-execution, events get re-enabled:
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo Cleanup
Application.EnableEvents = False
' ... your code here ...
Cleanup:
Application.EnableEvents = True
End Sub
BeforeSave fires twice when I do Save As.
This is expected behavior. BeforeSave fires once when the dialog is triggered and once when the user confirms the new filename. Use the SaveAsUI parameter to detect which scenario you're in and handle them separately.
My audit log grows enormous after a paste operation.
When a user pastes a range of cells, Target in Worksheet_Change will be a multi-cell range. Looping through each cell individually (For Each cell In Target) and logging each one can generate thousands of rows from a single paste. Consider logging at the range level rather than the cell level, or add a check for Target.Count > 1 and handle bulk operations differently.
You've just learned one of the most powerful patterns in Excel VBA: event-driven automation. Instead of requiring users to run macros manually, you've connected your code directly to things that happen naturally — opening, saving, changing. The workbook now behaves, not just sits there.
Here's what you covered:
ThisWorkbook is home to workbook-level events; individual sheet modules handle sheet-specific eventsWorkbook_Open automates setup tasks on launch; BeforeSave and BeforeClose let you validate and intercept user actionsWorksheet_Change and Workbook_SheetChange react to data entry in real timeApplication.EnableEvents = False/True around any cell writes inside a Change handler, and always wrap that pattern in error handlingFrom here, you have a lot of territory to explore. If you want to build richer interfaces that respond to users without requiring them to edit cells directly, Building UserForms for Custom Data Entry Interfaces is a natural next step. For a deeper look at the full range of events available across the Application, Workbook, and Worksheet objects — including mouse clicks and keyboard events — see Building a Custom VBA Event-Driven Framework: Respond to Workbook, Worksheet, and Application Events for Real-Time Automation. And when you're ready to package your event-driven tools so they work across multiple workbooks, Building Excel Add-Ins with VBA: Package and Deploy Custom Tools Across Your Organization will show you how.
Events are the foundation of workbooks that feel like real applications. Keep building.