Segmenting users is always messier than it should be
You need to target a specific slice of your users: the customers who bought a particular product, the subscribers who upgraded last quarter, the beta testers you invited manually. WordPress gives you roles and nothing else. Roles are global. You can't add a role called "vip" without it appearing in the role dropdown everywhere, and you certainly can't assign three different "vip"-style labels to the same user without a plugin that ships an entire membership system.
So most segmentation ends up living in spreadsheets, in WooCommerce order notes, or nowhere at all. When you need to act on that segment, you start from scratch every time.
What most people do
LIKE '%vip%' scans the whole table and returns false positives. It does not scale past a few hundred users.A better way: indexed tags stored right in user_meta
TrueCommander stores each tag as its own user_meta row with the key trc_user_tag. WordPress indexes that column. A query for "all users tagged vip" is a single indexed lookup, not a table scan. Type assign user tag to attach a tag, and remove user tag to detach it. Both commands are idempotent: running them twice changes nothing the second time, which makes them safe inside scheduled macros.
trc_user_tagTags are fast to query. Because each tag is its own user_meta row, the WordPress query engine can use the existing index on meta_key and meta_value. Retrieving every user tagged vip is a single indexed query no matter how many users you have.
What each command does
Two commands, one job: attach and detach slug labels from user records cleanly and safely.
| Command | What it does |
|---|---|
assign user tag | Attaches a slug tag to a user by adding an individual trc_user_tag user_meta row. Idempotent: if the tag already exists, the command returns success without duplicating the row. |
remove user tag | Detaches a slug tag from a user. Pass -all=true to remove every tag from the user in one call. Idempotent: removing a tag the user does not have returns not_tagged without an error. |
How it works
assign user tag Supply a slug and a user ID. The slug is normalized to lowercase, spaces become hyphens, and characters outside [a-z0-9_-] are dropped. Each tag becomes its own indexed user_meta row.filter users Pass the tag as a meta filter. Because the storage is indexed, the result comes back fast even across large user tables. Chain into a Repeat step to iterate every matched user.remove user tag Pass -tag to detach one label, or pass -all=true to wipe every tag from the user in a single call. Safe to run on a schedule: missing tags are skipped, not errored.| Detail | Value |
|---|---|
| Command names | assign user tag, remove user tag |
| Tag format | Slug: lowercase, hyphens for spaces, [a-z0-9_-] only, 1 to 100 characters |
| Storage | Individual user_meta rows, meta_key trc_user_tag, one row per tag per user |
| Idempotency | Assign is a no-op if the tag exists. Remove returns not_tagged if the tag is absent. Neither throws an error. |
| Bulk usage | Chain from filter users with a Repeat step and {{item.id}} to tag or untag an entire segment in one macro run |
| Can be used in |
Real example
You run a WooCommerce store and you want to tag every customer who bought your premium course as "vip" so you can email them a bonus and target them in future campaigns.
You build a macro: filter users with a meta filter that matches buyers of that product, then a Repeat step that calls tp assign user tag -tag=vip -user_id={{item.id}} for each one. The macro runs in under a second for 200 users. You schedule it nightly so anyone who buys overnight gets tagged automatically by morning.
Three weeks later you run a promotion. You call tp filter users -tag=vip to pull the segment, then send a targeted email via send template email in the same macro. No export, no spreadsheet, no re-importing. The tag was always there.
Goes further with TrueCommander
filter users into a Repeat step and call assign user tag with {{item.id}}. Tag hundreds of users from one click without writing any code.send template email. Your segment is always fresh because it reads live from the database.