## Summary
User search can now resolve exact `email:` and `username:` terms through
`GET /api/v2/users` instead of only supporting fuzzy free-text matches.
The database query already had exact email and username filters; this
wires the public search parser and API handler to those filters so
clients can ask for a single user by email without fetching every user
or depending on substring matching.
This is the API half of coder/terraform-provider-coderd#403: that
provider PR adds `data.coderd_user.email`, and this PR gives it an
efficient exact lookup path.
## Testing
- `go test ./coderd/searchquery -run '^TestSearchUsers$' -count=1`
- `go test ./coderd -run '^TestGetUsersFilter$' -count=1`
- Live API test:
- Built local enterprise Coder from this branch.
- Started Coder on `http://127.0.0.1:39991` against a clean Postgres
database.
- Created `lookup-target@example.com`.
- Verified `GET /api/v2/users?q=email:LOOKUP-TARGET@EXAMPLE.COM&limit=2`
returned exactly one user:
```json
{
"count": 1,
"users": [
{
"id": "efc6f909-ce0a-4731-bd2f-6e4df417aaa7",
"username": "lookup-target",
"email": "lookup-target@example.com"
}
]
}
```
---


---------
Co-authored-by: Ethan Dickson <ethanndickson@gmail.com>
Free-text member search previously matched only username and email, so
typing a person's display name returned no results even though the UI
shows the display name as the primary label. This broadens the free-text
`@search` filter to also match `users.name`.
The change is in three queries: `GetUsers`,
`PaginatedOrganizationMembers`, and `GetGroupMembersByGroupIDPaginated`.
This covers every server-filtered surface: the Users page, the
Organization Members page, the Group Members page, and the
`UserAutocomplete` / `WorkspaceUserAutocomplete` pickers (which query
`GetUsers` with `q`). The org member picker (`MemberAutocomplete`)
filters client-side via cmdk, so display name is added to its
`keywords`.
Explicit filters (`name:`, `username`/`email`) and pagination counts are
unchanged; the group members count still comes from the filtered
`COUNT(*) OVER()` in the same query.
Refs DEVEX-484
Refs DEVEX-565
<details>
<summary>Implementation plan</summary>
## Problem
Member search (both the global Users page and the Organization Members
page) matches only on `username` and `email`. It does not match on the
user's display name (`users.name`), even though the Organization Members
table shows `name` as the primary title. So typing a person's full name
in the search box returns nothing.
Today a bare search term (`alice`) is routed to the SQL `@search`
filter, which only checks `email`/`username`. Display name is only
matched if the user explicitly types `name:alice`, which is
undiscoverable.
## Design decision
Include `name` in the free-text `@search` condition in the affected SQL
queries. A bare term then matches `email OR username OR name`, using the
same case-insensitive substring `ILIKE` already in place. This keeps the
existing explicit `name:` filter working.
Tradeoff: this broadens the meaning of free-text `search` globally
(anything using these queries now also matches display name). This is
the intended behavior, confirmed against DEVEX-565 (display name search
in the user picker).
## Affected files
Backend:
- `coderd/database/queries/users.sql` (`GetUsers`)
- `coderd/database/queries/organizationmembers.sql`
(`PaginatedOrganizationMembers`)
- `coderd/database/queries/groupmembers.sql`
(`GetGroupMembersByGroupIDPaginated`)
- `coderd/database/queries.sql.go` regenerated via `make gen`
Frontend:
- `site/src/components/UserAutocomplete/UserAutocomplete.tsx` (add
`name` to client-side cmdk keywords)
Tests:
- `coderd/coderdtest/users.go` (shared `UsersFilter` helper): added a
`DisplayNameSearch` case and extended search-based expectations to
include `name`. Exercised by `TestGetUsersFilter`,
`TestGetOrgMembersFilter`, and `TestGetGroupMembersFilter`.
Docs:
- `docs/admin/users/index.md`: documented that free-text search matches
username, email, and display name.
## Frontend surface coverage
| Surface | Sends | Backend | Query |
|---|---|---|---|
| Users page | `q` | `GET /users` | `GetUsers` |
| Organization Members page | `q` | paginated members |
`PaginatedOrganizationMembers` |
| Group Members page | `q` | `groupMembers` |
`GetGroupMembersByGroupIDPaginated` |
| User pickers (server-filtered) | `q` | `GET /users` | `GetUsers` |
| Org member picker (client-filtered) | local cmdk | n/a | keyword
change |
## Out of scope
- Trigram/similarity (fuzzy) matching; keeps `ILIKE` substring
semantics.
- Sort/pagination ordering (still `LOWER(username)`).
</details>
---
_Created by Coder Agents on behalf of @aqandrew._
The username suffix could put the name past the 32 character limit,
causing the test to flake. Instead of using a suffix, match on the
expected ID instead.
Partially addresses #21813 (still need to make changes to the "add user"
button to be complete)
Since there are a lot of user tests already, I moved them into
`coderdtest` to be shared.