Move Members hides object member access by rewriting dotted notation into bracket notation, which turns every property name in the file into a string literal. Those literals are then swept into the generated string table, so member names stop appearing as readable text next to the code that uses them.
How it works
The rewrite itself is mechanical: obj.name becomes obj["name"]. On its own that hides nothing — you have swapped one readable form for another. The hiding comes from the second step, where Move Strings Into Array collects "name" into the table along with your own literals and replaces it with an index.
That is why this option only takes effect when MoveStrings is also enabled. Set on its own it does nothing useful. Enable the pair together, which is what every preset that uses either one does.
Example
Readable source:
o.style.color = "red";
Output after the member names and strings are indirected:
o[a[23]][a[52]] = a[145];
Three names went into the table: style, color, and the value "red". Note what survived — o is still a variable and the assignment is still an assignment. The shape of the statement is unchanged; what is gone is the vocabulary that told a reader it was about styling.
Configuration
{
"options": {
"MoveStrings": true,
"MoveMembers": true,
"EncodeStrings": true
}
}
Adding EncodeStrings is the usual third step: without it the table still lists style and color in plaintext, and a reader who opens the table gets your whole property vocabulary in one view.
What it does not do
Move Members makes member access less readable. It does not rename anything. obj["name"] reads the same property name that obj.name did, so:
- Nothing about your public API changes. Callers in other files, JSON round-trips, and framework property bindings all keep working, because the property keys are byte-identical at runtime.
- Reflection still works.
Object.keys() returns the same names it always did. If an analyst can run your code, printing the key list recovers your entire property vocabulary in one step.
- To actually rename members across files, use Protect Members (
RenameMembers), which changes the keys themselves and therefore does require you to think about every external consumer.
The two compose: Protect Members changes the names, Move Members hides the access pattern that reveals them.
Caveats worth knowing before you ship
- Private names are skipped. Members the engine identifies as private names keep dotted access rather than being rewritten, because
#field is not reachable through a computed key — obj["#count"] is a plain property, not the private field. Leaving them alone is the only correct behavior here.
- Optional chaining is preserved correctly.
obj?.member becomes obj?.["member"]. The shorter-looking obj?["member"] is invalid JavaScript — the parser reads ? as the start of a conditional and then fails looking for a colon. A customer hit exactly this in May 2026; if you see a parse error about a semicolon where a colon was required, you are running an older engine and should update.
- Run it after your bundler. Tree shaking, dead-code elimination, and most static analyzers match on dotted access. Once
console.log is a computed read, those tools stop seeing it — which means a bundler running after protection may keep code it would have dropped, or drop code it should have kept.
- Property-name minifiers conflict. If your pipeline already runs an aggressive property mangler, settle on one tool for property names rather than layering two.