Skip to main content
The most common Volunteer integration workflow is reading Users — for reporting, sync to external systems, or driving partner-built features. 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


The six filter parameters

All parameters are optional. Combining multiple parameters narrows the result set (AND logic — a user must match all filters).

Scenario 1: Recently active volunteers

Goal: find users whose record has been updated in the last 30 days.
JavaScript

What “updated” captures

The updated_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
Note: 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
Advance the checkpoint to the latest 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

The exact inclusion of the boundary (≥ vs > on 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
Useful for narrow targeted reports — “new Wayne family members onboarded in Q1.”

Scenario 3: Search users by name

Goal: find users matching a name fragment, typically for an interactive search box.
JavaScript
For interactive search, returning only the first page is usually right — users searching for “Bruce” want top matches, not all 12 pages.

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)
The match is case-insensitive substring search. There’s no way to restrict to first-only or last-only matching via the API.

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 via per_page. A customer with 10,000 users requires ~667 page requests for a full read. For large customers: See Rate Limits for the throttling pattern and Sync Architecture Patterns for the broader backfill design.

Scenario 6: Filters that don’t exist

A few useful filters that the API does not expose: For any of these, the pattern is:
JavaScript
The cost is a full dataset read. For small accounts this is fine; for large accounts, it’s better to capture the data once into an external store and query there.

Putting it together

A reference function that handles the common cases robustly:
JavaScript
This encapsulates the common patterns while keeping the underlying flexibility — you can pass arbitrary filters via list({...}) for cases the named methods don’t cover.

Performance and rate-limit considerations

A few practical notes for production-scale workloads: See API Performance Tips for the broader patterns.

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, a Date.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.
Last modified on May 22, 2026