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.
Frequently asked questions
What does Move Members do?
It rewrites dotted property access into bracket notation, which turns every property name in the file into a string literal. On its own that is a change of spelling rather than a concealment, since one readable form has been swapped for another. The point is what happens next: those literals become eligible for the string table, and that is where the hiding actually occurs.
Why does it need the string table enabled?
Because the rewrite alone hides nothing. The concealment comes from the second step, where the literals produced by this option are collected into the generated table along with your own strings and replaced by an index. With the table disabled, the option leaves you with bracket notation containing readable names, which is no better than the dotted form and is slightly larger. Enable the pair together, which is what every preset using either one does.
Will it break code that other systems call into?
It can, in the same way any member transformation can, so the rule is to know your public surface first. Property names that something outside your file reads by name are a contract, and moving them into a table changes where they are written but not the fact that they must still resolve correctly. The risk is highest with names reached dynamically or matched from configuration. Keep those out of scope rather than discovering the problem at run time.
Does it rename the members?
No, and the distinction matters. This option changes how a member is accessed, not what it is called. The names are preserved exactly and simply moved into the table. Renaming members is a separate option with much broader consequences, because it changes the name itself and therefore every consumer of it. Reach for this one when you want the names less visible, and the renaming option when you want them different.
Which tier does it need?
It is an Enterprise-tier option, and the string table it depends on is available more widely. That combination is worth checking against your plan before you build a configuration around it, because the option silently does nothing useful without its partner and the partner is the more broadly available of the two.
What does it not protect against?
Anybody who runs the code. Member access resolves at run time to exactly the names it always did, so a debugger, a property inspection or a logged object shows the real names. What the option removes is readable text sitting next to the code that uses it, which raises the cost of reading the file statically. It does not change what the program is observably doing.