GET /users supports six filter parameters that cover most real-world scenarios. This page walks through the practical patterns: when to use which filter, how to combine them, and how to pair them with pagination and throttling for production-grade reads.
If you haven’t yet, skim the Users concept page first — this workflow page builds on the field shapes and endpoint structure documented there.
When to use this workflow
| Scenario | This workflow fits |
|---|---|
| Build a “recently active volunteers” dashboard | ✓ Use updated_after |
| Pull monthly new-volunteer reports | ✓ Combine created_before and created_after |
| Search users by partial name in a UI | ✓ Use name_like |
| Filter to users with specific email domains | ✓ Use email_like (with care — see below) |
| Sync the full user dataset to an external system | ✓ No filter + pagination |
| Find a single user by exact email | Use the Find a User by Email workflow instead |
| Find a user when you have their ID | Use GET /users/{id} instead — single-request, full detail |
The six filter parameters
| Parameter | What it filters by |
|---|---|
name_like | Substring match against first OR last name (case-insensitive) |
email_like | Substring match against email (case-insensitive) |
created_before | Users created on or before a date (ISO 8601) |
created_after | Users created on or after a date |
updated_before | Users updated on or before a date |
updated_after | Users updated on or after a date |
page | Page number for pagination (default 1) |
Scenario 1: Recently active volunteers
Goal: find users whose record has been updated in the last 30 days.JavaScript
What “updated” captures
Theupdated_at timestamp changes when the User record is modified — including:
- Profile updates (name, email, phone, address, etc.)
- Membership status changes
- Form completions (likely — confirm against live behavior)
- Profile field value updates
updated_at does NOT necessarily change when the user participates in a Project Date. Participation creates a Participation record, not a direct User modification. To detect “users who participated recently,” query Project Dates and follow back to users, not users?updated_after.
Use this filter for change detection
This is the primary filter for incremental sync patterns:JavaScript
updated_at actually seen — not to new Date() — so an interrupted sync resumes correctly. See Pagination: Advancing the checkpoint correctly.
Scenario 2: Users created in a time window
Goal: users created within a specific calendar month.JavaScript
The before vs after semantics
| Filter | Boundary inclusion |
|---|---|
created_after: "2025-03-01T00:00:00Z" | Users created on or after March 1 |
created_before: "2025-04-01T00:00:00Z" | Users created on or before April 1 |
after, ≤ vs < on before) isn’t strictly specified. Using exclusive next-month-start for created_before (e.g., April 1 for “March users”) prevents accidentally including April records.
Combining with name or email filters
Filter parameters AND together — adding more narrows the result:JavaScript
Scenario 3: Search users by name
Goal: find users matching a name fragment, typically for an interactive search box.JavaScript
name_like matches first OR last name
name_like=wayne matches:
first_name: "Wayne", last_name: "Smith"✓first_name: "Bruce", last_name: "Wayne"✓first_name: "Wayland", last_name: "Smith"✓ (substring match)
Handling ambiguous results
For interactive UIs, present multiple matches for user selection:JavaScript
Scenario 4: Filter by email domain
Goal: find users with emails from a specific domain (often for organizational segmentation).JavaScript
Why the secondary filter
email_like=@wayne.example is a substring match — it would also match @wayne.example.com if such an address exists. The client-side endsWith filter ensures the match is actually at the end of the email.
For most domains this isn’t an issue, but for short or common domain prefixes (e.g., @vol.com could match @volunteer-orgs.com), the secondary filter prevents false positives.
Scenario 5: Full dataset read
Goal: pull every user — typically for a one-time backfill or daily reconciliation.JavaScript
Page size considerations
Volunteer’s page size is platform-controlled (default 15) — not partner-controllable viaper_page. A customer with 10,000 users requires ~667 page requests for a full read.
For large customers:
| Strategy | Description |
|---|---|
| Throttle aggressively | 2–3 req/sec during full backfills, not 10+ |
| Cache the result | Daily backfills can re-use yesterday’s cache as a starting point |
Combine with updated_after | After the initial backfill, switch to incremental |
| Run during off-peak hours | Reduces collision with the customer’s interactive use |
Scenario 6: Filters that don’t exist
A few useful filters that the API does not expose:| Desired filter | What to do instead |
|---|---|
phone_like | Not supported — paginate all users and filter client-side |
birthday_before / birthday_after | Not supported — same |
user_status (filter by status enum) | Not supported — fetch and filter client-side |
membership_role (filter by role) | Not supported — same |
organization_id / org_slug | Not supported on /users — User scope is determined by the token |
participated_in_project | Not supported — fetch participations through the User detail endpoint |
belongs_to_group | Not supported — use GET /groups/{id}/members instead |
JavaScript
Putting it together
A reference function that handles the common cases robustly:JavaScript
list({...}) for cases the named methods don’t cover.
Performance and rate-limit considerations
A few practical notes for production-scale workloads:| Concern | Practice |
|---|---|
| Page size is small (default 15) | Plan for many requests on large datasets |
| Each request consumes rate-limit budget | Throttle to a conservative rate (3-5 req/sec for backfills) |
| Filters reduce total pages — use them | updated_after for incremental beats reading everything every time |
| Cache where appropriate | Daily reads of the same data hint at over-fetching |
| Process pages as they arrive | Don’t accumulate 100,000+ users in memory before processing |
Common bugs to avoid
A few patterns that look right but produce subtle issues:Treating meta.total as a constant
meta.total is the count at the time of the request — if users are being added during your iteration, the count can change. For “did I get them all?” checks, use links.next === null as the truth, not “I read meta.total records.”
Ignoring null in prev/next
Use simple null-checks (if (page.links.next)), not string comparison. See Pagination: the "null" vs null warning.
Date format inconsistency
All datetime filter parameters expect ISO 8601 strings. Sending a Unix timestamp, aDate.toString() value, or a non-UTC ISO string can produce silent failures (returning everything because the date didn’t parse) or wrong results.
JavaScript
Forgetting URL encoding
URLSearchParams handles encoding for you. Manual concatenation breaks on spaces, ampersands, and other special characters:
JavaScript
Where to go next
Create or Update a User
Once you’ve found (or haven’t found) the user, the upsert workflow.
Find a User by Email
The focused workflow for email-based lookups.
Pagination
The full pagination pattern these workflows depend on.
Rate Limits
The throttling pattern for bulk reads.