Staff IDs Architecture
This document explains how staff assignment works throughout WooCommerce Appointments—from data models to REST APIs to database storage.
Appointments support multiple staff members. The canonical storage is always staff_ids (plural, array). The singular staff_id is a convenience accessor or filter parameter.
Quick Reference
| Context | Singular (staff_id) | Plural (staff_ids) |
|---|---|---|
| WC_Appointment class | get_staff_id() returns first staff (int) | get_staff_ids() returns all staff (array) |
| REST API query params | Filter by single staff | Filter by multiple staff (comma-separated) |
| REST API response | Per-slot staff assignment | Appointment's full staff array |
| Database | N/A | _appointment_staff_id meta (multiple entries) |
WC_Appointment Class
Getting Staff
// Get ALL assigned staff as an array (preferred)
$staff_ids = $appointment->get_staff_ids();
// Returns: array( 123, 456 ) or array() if no staff
// Get the first/primary staff ID (convenience method)
$staff_id = $appointment->get_staff_id();
// Returns: int (first staff ID, or 0 if no staff)
// Get staff member objects with names
$staff_members = $appointment->get_staff_members(); // Array of WC_Product_Appointment_Staff
$staff_names = $appointment->get_staff_members( true ); // Comma-separated names string
$staff_links = $appointment->get_staff_members( false, true ); // HTML links
Setting Staff
// Set single staff member
$appointment->set_staff_id( 123 );
// Internally stores: staff_ids = [123]
// Set multiple staff members
$appointment->set_staff_ids( array( 123, 456 ) );
// Internally stores: staff_ids = [123, 456]
// IMPORTANT: Both methods write to the 'staff_ids' property
// There is NO separate 'staff_id' storage in the data model
Common Patterns
// Check if any staff assigned
if ( $appointment->get_staff_ids() ) {
// Has staff
}
// Check for specific staff
if ( in_array( 5, $appointment->get_staff_ids() ) ) {
// Staff ID 5 is assigned
}
// Get staff count
$staff_count = count( $appointment->get_staff_ids() );
REST API
Appointments Endpoint
Query Parameters:
staff_id— Filter appointments by a single staff member
Response includes:
staff_ids— Array of all assigned staff IDsstaff_details— Array of staff objects withid,display_name,avatar_url
# Filter appointments by staff member
curl -u admin:APP_PASSWORD \
"https://example.com/wp-json/wc-appointments/v2/appointments?staff_id=5"
Response:
{
"id": 123,
"staff_ids": [5, 8],
"staff_details": [
{
"id": 5,
"display_name": "John Smith",
"avatar_url": "..."
},
{
"id": 8,
"display_name": "Jane Doe",
"avatar_url": "..."
}
]
}
Slots Endpoint
The slots endpoint returns availability per staff member. Each slot record has a single staff_id representing which staff member that slot is for.
Query Parameters (both accepted, converted to array internally):
staff_id— Single staff ID (e.g.,staff_id=5)staff_ids— Comma-separated staff IDs (e.g.,staff_ids=5,8,12)
Response:
- Each slot record has
staff_id(int) — the staff member for that specific slot
# Get slots for specific staff members
curl -u admin:APP_PASSWORD \
"https://example.com/wp-json/wc-appointments/v2/slots?product_id=123&staff_ids=5,8&min_date=2025-12-01&max_date=2025-12-31"
Response:
{
"records": [
{
"product_id": 123,
"product_name": "Consultation",
"date": "2025-12-01T10:00",
"timestamp": 1764547200,
"duration": 60,
"duration_unit": "minute",
"available": 1,
"scheduled": 0,
"staff_id": 5
},
{
"product_id": 123,
"product_name": "Consultation",
"date": "2025-12-01T10:00",
"timestamp": 1764547200,
"duration": 60,
"duration_unit": "minute",
"available": 1,
"scheduled": 0,
"staff_id": 8
}
],
"count": 2
}
Combined Staff Mode
Use combine_staff=true to get a single slot per time with summed availability across staff:
curl "https://example.com/wp-json/wc-appointments/v2/slots?product_id=123&combine_staff=true&min_date=2025-12-01"
Response:
{
"records": [
{
"product_id": 123,
"product_name": "Consultation",
"date": "2025-12-01T10:00",
"timestamp": 1764547200,
"duration": 60,
"duration_unit": "minute",
"available": 2,
"scheduled": 0,
"staff_id": 0
}
],
"count": 1
}
When combine_staff=true, staff_id is 0 because availability is summed across multiple staff.
Index Endpoint
Query Parameters:
staff_id— Filter indexed appointments/availability by staff
curl -u admin:APP_PASSWORD \
"https://example.com/wp-json/wc-appointments/v2/index?start_ts=1764547200&end_ts=1764719999&staff_id=5"
Availabilities Endpoint
Query Parameters:
staff_id— Filter availability rules by staff
Database Storage
Meta Key
Staff IDs are stored in WordPress post meta with key _appointment_staff_id.
Single staff appointment:
INSERT INTO wp_postmeta (post_id, meta_key, meta_value)
VALUES (100, '_appointment_staff_id', 5);
Multi-staff appointment (multiple entries with same key):
INSERT INTO wp_postmeta (post_id, meta_key, meta_value) VALUES (101, '_appointment_staff_id', 5);
INSERT INTO wp_postmeta (post_id, meta_key, meta_value) VALUES (101, '_appointment_staff_id', 8);
Data Store Mapping
In WC_Appointment_Data_Store:
private array $appointment_meta_key_to_props = [
'_appointment_staff_id' => 'staff_ids', // Note: meta key is singular, prop is plural
// ...
];
The data store reads all values with get_post_meta($id, '_appointment_staff_id', false) (the false returns an array of all values).
Querying by Staff
// Get appointments for a specific staff member in a date range
$appointments = WC_Appointment_Data_Store::get_appointments_in_date_range(
$start_timestamp,
$end_timestamp,
$product_id, // 0 for all products
$staff_id // Staff ID to filter by
);
Product Staff Configuration
Products can have different staff assignment modes:
| Mode | Value | Description | staff_ids Behavior |
|---|---|---|---|
| Customer selects | customer | Customer picks staff at booking | Single staff ID chosen by customer |
| Automatic | automatic | System auto-assigns available staff | Single staff ID assigned by system |
| All staff required | all | All assigned staff are booked together | Multiple staff IDs (all assigned staff) |
// Check product's staff assignment mode
$mode = $product->get_staff_assignment();
// Get product's available staff
$product_staff_ids = $product->get_staff_ids();
if ( 'all' === $mode ) {
// All staff will be assigned to each appointment
}
Availability Cache
The availability cache (WC_Appointments_Availability_Cache) uses a separate staff_id property for filtering cached entries. This is distinct from the appointment's staff_ids array.
// Cache entry has single staff_id for filtering
$cache_entry->get_staff_id(); // Returns int
$cache_entry->set_staff_id( 5 );
Best Practices
DO
- ✅ Use
get_staff_ids()to get all assigned staff - ✅ Use
get_staff_id()only when you know there's a single staff - ✅ Use
set_staff_ids()with an array for multi-staff - ✅ Check for empty array when verifying no staff assigned
- ✅ Use
staff_ids(comma-separated) in API calls when filtering multiple staff
DON'T
- ❌ Don't assume only one staff member is assigned
- ❌ Don't access
$appointment->data['staff_id']directly (doesn't exist) - ❌ Don't store staff in a separate
staff_idproperty
Test Fixture Example
// Single staff
$appointment = $this->create_test_appointment([
'product_id' => $product_id,
'staff_id' => 5, // Convenience - stored in staff_ids
'start' => $start,
'end' => $end,
]);
// Multiple staff (preferred for multi-staff scenarios)
$appointment = $this->create_test_appointment([
'product_id' => $product_id,
'staff_ids' => [5, 8, 12], // Explicit array
'start' => $start,
'end' => $end,
]);
Troubleshooting
"Staff not saved" Issues
// WRONG - this won't persist
$appointment->staff_ids = array( 5, 8 );
// CORRECT - use the setter
$appointment->set_staff_ids( array( 5, 8 ) );
$appointment->save();
"get_staff_id() returns 0"
This happens when:
- No staff is assigned
- Staff IDs array is empty
$staff_ids = $appointment->get_staff_ids();
if ( empty( $staff_ids ) ) {
// No staff assigned - this is expected for some products
}
API Returns Empty Staff
If the slots API returns staff_id: 0:
- Product may not have staff assigned
- Using
combine_staff=truemode (summed availability) - Staff filter doesn't match product's available staff
Related Documentation
- REST API Reference
- Hooks Reference - Staff-related hooks
- Performance Guide - Indexed queries