TL;DR — A
Propertyturns a field of a class into a gate. From the caller's sideemp.Salary = 65000looks like a plain variable, but behind that assignment your code runs — so you can validate, compute, or refuse the write.Property Getis the read gate,Property Letreceives a value, andProperty Setreceives an object. Mixing upLetandSetis the number-one error; leaving outLeton purpose is how you make a field read-only.
' --- Inside a class module (clsEmployee) ---
Private mSalary As Currency ' the backing field, hidden from the outside
Public Property Get Salary() As Currency ' READ gate
Salary = mSalary
End Property
Public Property Let Salary(ByVal value As Currency) ' WRITE-a-value gate
If value < 0 Then Err.Raise 5, , "Salary cannot be negative"
mSalary = value ' validated before storing
End Property
' --- Usage reads exactly like a plain field ---
' emp.Salary = 65000 -> runs Property Let (validates, stores)
' Debug.Print emp.Salary -> runs Property Get (returns mSalary)
' emp.Salary = -100 -> raises "Salary cannot be negative"
A class module can expose its data as plain Public
variables — and sometimes that's the right call. But the reason classes feel more solid
than a bag of public fields is Property procedures: they are the hook where you decide
what the outside world is allowed to do with each field.
What you'll learn
- The mental model — a gate on a field, not a raw variable
- Why a Property beats a plain Public variable
- Get, Let and Set — and the one distinction everyone trips on
- The backing-field pattern that makes it all work
- How to make a read-only property (and why that's a feature)
- When a plain Public variable is the honest choice
The mental model: a gate on a field
A Property is a gate in front of a field. To anyone using your object it looks
identical to a normal variable — they write emp.Salary = 65000 and read
x = emp.Salary — but each of those touches runs a small procedure you wrote. The read
runs Property Get; the write runs Property Let (for values) or Property Set (for
objects).
That indirection is the entire point. Because your code sits in the gate, you can check the value before storing it, compute a value that isn't stored at all, or simply not provide a write gate so the field becomes read-only. The caller never has to know — the syntax is the same either way.
Why a Property beats a plain Public variable
A Public field is a hole straight into your object: anyone can write anything, any
time, with no checks. emp.Age = -5 succeeds. emp.Id = 999 overwrites an identity
that was supposed to be permanent. There is no place to put a rule, because there is no
code between the caller and the field.
A Property gives you that place. The failure it prevents is the silent bad write — the
negative age, the salary set to a string that later blows up a calculation, the "unique"
id quietly reassigned. With a Property Let, the illegal write is caught at the point
it happens, with a clear message, instead of surfacing three procedures later as a
mysterious wrong result.
Get, Let and Set: the one distinction everyone trips on
There are three property procedures, and the split between the two write ones is the classic VBA stumbling block:
| Procedure | Direction | Receives / returns | Use for |
|---|---|---|---|
Property Get |
Read | Returns a value or object | Reading any property |
Property Let |
Write | Receives a value (Long, String, Date, Currency…) | Writing a value property |
Property Set |
Write | Receives an object (Range, class instance…) | Writing an object property |
The rule is the same one that governs ordinary assignment in VBA:
values use Let, objects use Set. If a property holds a Range and you write a
Property Let for it, assigning emp.HomeCell = Range("A1") fails, because assigning an
object needs Set — and VBA looks for a Property Set it can't find. Match the
procedure to the kind of thing the field holds:
Private mRange As Range
Public Property Get HomeCell() As Range
Set HomeCell = mRange ' note: Set, because we return an object
End Property
Public Property Set HomeCell(ByVal r As Range) ' Set, not Let — it's an object
Set mRange = r
End Property
' Usage: Set emp.HomeCell = Range("A1")
Notice that even inside Property Get, returning an object uses Set HomeCell = ....
The Let/Set distinction follows the kind of data, everywhere.
The backing-field pattern
Properties almost always come in a pair wrapped around a hidden field. The field is
Private (the m prefix is convention for "member"), and the world reaches it only
through the gates:
Private mName As String ' hidden backing field
Public Property Get Name() As String
Name = mName
End Property
Public Property Let Name(ByVal value As String)
mName = Trim$(value) ' normalise on the way in
End Property
This is the shape that makes encapsulation real: the data lives in one Private
variable, and every read and write goes through code you control. Change how a name is
stored later — trimmed, upper-cased, validated against a list — and you change one
Property Let, not every caller.
How to make a read-only property (and why that's a feature)
Leave out the Property Let and the field becomes read-only from the outside: the
class can set the backing field internally, but callers can only read it.
Private mId As Long
Public Property Get Id() As Long ' Get only — no Let
Id = mId
End Property
Friend Sub AssignId(ByVal newId As Long) ' class sets it internally
mId = newId
End Sub
' emp.Id -> works (read)
' emp.Id = 42 -> compile error: Can't assign to read-only property
That compile error is not an obstacle — it's the guarantee. A read-only property is how you express "this value is set once and never edited," and the compiler enforces it for you. The same technique gives you computed properties that have no backing field at all:
Public Property Get FullName() As String
FullName = mFirst & " " & mLast ' derived, nothing stored
End Property
When a plain Public variable is the honest choice
Here's the judgement that separates good VBA from cargo-culted boilerplate: not every
field needs a Property. If a field never needs validation, never needs to be
computed, and is fine to read and write freely, then a Public variable says exactly
that — and wrapping it in a pass-through Get/Let pair that does nothing but copy
mX back and forth adds noise, not safety.
So: reach for a Property when you have a reason — validation, a read-only id, a
computed value, an object field that needs Set. For a plain, unconstrained value, a
Public variable is the honest, readable choice. Add the gate when you need the gate;
don't build gates around open fields.
How ExcelMaster helps
The property rules are small but unforgiving: Let for values, Set for objects, a
missing Let for read-only, a Private backing field behind each pair. Get the
Let/Set pairing wrong and it won't compile; get the encapsulation wrong and your
"protected" field is wide open.
ExcelMaster writes
the whole shape for you. Describe the object — "an Employee whose salary can't be
negative, with a read-only id and a computed full name" — and it generates the
Private backing fields, the validating Property Let, the object-aware Property Set
where needed, and the Get-only read-only properties, all paired correctly. You get
proper encapsulation without memorising which keyword goes with which kind of field.
Frequently asked questions
What is a Property in VBA?
A Property is a procedure in a class module that controls
access to a field. Property Get runs when the field is read, and Property Let (for
values) or Property Set (for objects) runs when it's written. To the caller it looks
like a normal variable — emp.Salary = 65000 — but your code runs behind the
assignment, so you can validate, compute, or make the field read-only.
What is the difference between Property Let and Property Set?
Property Let receives a value — a Long, String, Date, Currency, and so on.
Property Set receives an object — a Range, a class instance, or any reference type.
It's the same rule as ordinary assignment: values use = (via Let), objects use Set
(via Set). Using Let for an object property, or Set for a value, is a compile or
run-time error.
How do I make a read-only property in VBA?
Write a Property Get but no Property Let (or Set). Callers can then read the value
but not assign to it — an attempted write is the compile error "Can't assign to
read-only property." The class itself can still change the backing field internally. This
is ideal for identity values that are set once, or for computed properties like a full
name derived from other fields.
Why use a Property instead of a Public variable?
A Public variable can be written with any value, any time, with no checks. A
Property gives you a place to validate input, compute a value, make a field read-only,
or change how the data is stored later without touching callers. Use a Property when
you have such a reason; for a plain unconstrained value, a Public variable is simpler
and perfectly honest.
What does Property Get do in VBA?
Property Get is the read side of a property: it runs whenever code reads the
property, and it returns the value (or, for an object property, returns it with Set).
It's typically paired with a Property Let or Set for writing, wrapped around a
Private backing field — but a Property Get on its own creates a read-only or
computed property.
Tested in
Tested in: Excel 365 (Windows 11), VBA 7.1 — last verified 2026-08-04.
Related guides: VBA Class Module · VBA Type · VBA Dim · VBA Function · VBA ByRef vs ByVal
