BookingWP - #1 WordPress Appointment Booking Plugin Version History
19 versions available
2 Credits
per install
Subscribe to connect and install
v5.3.9
LatestVerified Safe
Released 2.8 MB
Improved
Improved admin panel styling with bigger input selection.
Admin panel create/edit: datetime row shows a loading state while product details, first-available auto-pick, day slots, or availability AJAX run; success/error only after the check completes.
Unified `day_data`: storefront calendar is `day_data`-only — per-day `padding` / `restricted`, full per-staff occupancy fields, redundant `display_day_summaries` removed; `to_day_data()` optional fold-in; availability widget/block use `get_day_data()` with `selectable`; `resolve_schedule_bounds()` maps Unix `0` to the booking horizon.
Padding and fully-booked month aggregation call `get_padding_days_for_product()`, `get_padding_days_for_fully_booked_dates()`, and `get_scheduled_month_slots()` (`get_day_data()`-based).
Storefront `date-picker.js`: removed legacy four-array branch and `is_slot_appointable`; removed unused `getTzOffsetFromString` / `getCustomerTzOffsetMinutes` (offset still server-supplied on AJAX).
**WPML / WCML:** admin appointment panel **create** — independent multicurrency selector (`integrationCurrency` on `wc_appointments_admin_panel_config`); appointment-currency cookie + `wcml_client_currency` on panel cost, add-on, and set-currency AJAX; WCML loads in admin for those actions; rate conversion via pricing-rule filters, `wc_price_args`, and WCML `get_product_addons_fields` (add-ons refetch on currency change); footer total from server `calculate_costs` HTML; `_order_currency` on `woocommerce_new_appointment_order`; legacy add-appointment page unchanged.
**WooCommerce Payments Multi-Currency:** admin appointment panel **create** — independent multicurrency selector (same `integrationCurrency` pattern as WCML); appointment-currency cookie + `wcpay_multi_currency_override_selected_currency` filter for exchange rate conversion on panel cost AJAX; addon prices converted via `woocommerce_product_addons_*_price_raw` (WC Payments skips admin); `_order_currency` persisted on `woocommerce_new_appointment_order`. WCML takes priority when both plugins are active.
Admin appointment panel **create**: **WooCommerce Deposits** — deposit / pay-in-full / plan UI (storefront POST keys), line totals and meta on `wc_appointments_create_appointment`, `_wc_deposits_version` when applicable; product AJAX exposes `wc_deposits`; compact amounts in `wca-slots-header` / `wca-slots-count`.
Admin panel browse: time slot chips use a four-column equal-width grid (`.wca-slots-grid` / `.wca-slot-chip` in `appointment-admin-panel.scss`).
Google Calendar OAuth (settings + staff profile): [Google Identity branding](https://developers.google.com/identity/branding-guidelines) — **Continue with Google**, `assets/images/google-g-mark.svg`, shared markup via `WC_Appointments_GCal::get_google_oauth_connect_button_html()`.
Admin panel: `woocommerce_appointments_add_appointment_page_redirect` gains a 5th string `$context` (`legacy_post`, `admin_ajax_standalone`, `admin_ajax_calendar`); full admin calendar never returns `post_create_redirect` (callbacks may still read context).
Update translations (+7 new strings, -2 strings removed).
Settings → Indexing: during manual re-index, an admin notice explains the storefront uses dynamic availability checks until rebuild finishes (availability stays correct; performance may be slower during rebuild).
Other
Performance - Admin panel create: first-available auto-pick scans the bookable horizon in 14-day chunks and stops at the first slot instead of loading the full range up front.
2026.05.20 - version 5.3.9-beta
Breaking change - Removed `WC_Appointments_Controller::find_padding_day_slots()`, `get_padding_day_slots_for_scheduled_days()`, and `find_scheduled_month_slots()`. Use `WC_Appointment_Slots_Provider::get_padding_days_for_product()`, `get_padding_days_for_fully_booked_dates()`, and `get_scheduled_month_slots()`.
Breaking change - Removed `WC_Appointments_Controller::find_scheduled_day_slots()`, `WC_Appointment_Slots_Provider::get_scheduled_day_slots()`, and `WC_Appointment_Slots_Collection::legacy_scheduled_arrays_from_day_data()`. Use `get_day_data()` / `to_day_data()`; padding anchors from fully booked `day_data` (`get_padding_days_for_fully_booked_dates()`, `Y-n-j` keys); months via `get_day_data(..., 'Y-n')`. `woocommerce_appointments_scheduled_day_slots` runs inside `get_day_data()` with the `to_day_data()` payload (not legacy four-array shapes).
Breaking change - Calendar AJAX `wc_appointments_find_scheduled_day_slots` no longer returns legacy keys (`partially_scheduled_days`, `remaining_scheduled_days`, `fully_scheduled_days`, `unavailable_days`, `padding_days`, `restricted_days`) or invokes that filter at the AJAX layer. Read `day_data` / `meta`, filter the JSON response, or use `get_day_data()` in PHP where the filter still applies.
Fixed
Admin panel create: availability no longer flashes loading twice or clears to blank before first-available prefill; date-only calendar prefill waits for day slots before checking; browse does not re-open after the user closes it during the same unavailable episode.
Admin panel: datetime loading no longer sticks when day-slot AJAX fails or returns empty, when a superseded availability request aborts, or while bootstrap waits only on an in-flight slots request (not missing cached data).
Slots / day & month products: timed global RRULE blocks (e.g. weekly Tue 4–6pm) block the whole civil day again — date-level checks use RRULE occurrence expansion, not midnight-only overlap, so higher-priority global rules override product day rules (`RuleMinutesRruleCivilDayTest`).
Admin panel browse: slot chips show remaining capacity (e.g. **1 available**) on multi-capacity products when only one place is left, not only when two or more remain.
Slots: dynamic availability (indexing off) paints full half-open unappointable ranges on the minute mask — priority blocks no longer leave appointable boundary minutes (e.g. 10:00–11:00 block showing 10:59 slots); matches indexed mask painting.
Slots / calendars: minute/hour bookings that start at site midnight (including long durations such as 12 hours) block capacity again — timed overlap day-keys use Fake UTC site civil buckets for both occupancy indexing and slot lookup instead of mixing wp_date() site midnight with the civil UTC-midnight fast path.
Slots / calendars: multi-day day-unit occupancy for admin-created (site-local midnight) bookings — civil overlap keys use the site calendar date (Fake UTC) so every booked day blocks correctly (e.g. June 4–5 for a 2-day booking starting June 4); `wc_appointments_get_posted_data()` stores matching Fake UTC start/end for new day/month appointments.
WooCommerce block email editor support for appointment transactional emails (synced block templates under templates/emails/block/, hook-driven parts under templates/emails/parts/, personalization tags, preview context, admin styles).
Improved
Date picker default date uses `WC_Appointment_Slots_Provider::get_first_available_slot()` which leverages Collection day summaries to skip unavailable days. Complexity reduced from O(all_slots) to O(days + slots_in_first_available_day).
`WC_Product_Appointment::get_first_available_slot()` and `get_slots_in_range_lazy()` (and private `get_slots_lazy_by_duration` / `get_slots_in_range_for_*_lazy` helpers) removed; use `WC_Appointment_Slots_Provider::get_first_available_slot()`.
Removed deprecated dead code with no external calls: 11 methods from `WC_Appointments_Controller` (deprecated 2.4.9–4.7.0), 5 functions from `wc-appointments-functions.php` (deprecated 1.15.0–5.1.0), and 3 methods from `WC_Appointment` (deprecated 4.2.0–5.3.8). Deprecated code still in use internally was retained.
v5.3.7
Verified Safe
Released 2.8 MB
Improved
Admin calendar: on expired nonce/session (REST 401/403), show an expiry banner with reload link instead of failing silently after long idle periods.
Admin appointment panel: create/edit success step uses a light success-tinted circular badge for the checkmark (aligned with other success affordances), not a plain large icon.
Dev - Removed deprecated per-product `customer_timezones`; timezone support is site-wide. `has_timezones()` now reads `wc_appointments_timezone_support_enabled` (default `true`). Removed `get_customer_timezones` / `set_customer_timezones`, related data store mapping, admin and REST fields, and WCML/Polylang sync for that setting.
Fixed
v5.3.6.1
Verified Safe
Released 2.8 MB
Improved
What's New admin screen (`html-wc-appointments-whats-new.php`): **admin appointment panel** behavior is now described and illustrated clearly—by default the panel **anchors beside the selected calendar appointment** (prefers right, then left, centers only when neither side fits), with backdrop and roadmap/changelog wording aligned. This replaces misleading “side panel” / generic sidebar copy and SVGs that did not show the panel next to the trigger event.
Fixed
Admin appointment save (`WC_Appointment_Meta_Box_Data::meta_box_save`): blank or invalid start/end date (or missing time when not all-day) no longer calls `mktime()` with non-integer parts (PHP 8+ TypeError / fatal). Invalid input shows a WooCommerce admin error and the appointment object is not updated on that request.
Unified Slots Standardization (part 1): `WC_Appointment_Slot` embeds projected business/display TZ metadata, wall times, formatted labels, `status`/`bookable`, and selection helpers so storefronts render slots without extra timezone math.
Slots provider/engine accept optional `display_timezone`; `WC_Appointment_Slot::project_display_fields()` centralizes projection; `resolve_display_timezone()` pins day/month and non-timezone products to business TZ and validates IANA zones for timed products.
`WC_Appointment_Slots_Collection::get_day_summaries_by_display()` groups by `display_day`; calendar AJAX includes `display_day_summaries` while legacy `scheduled_days` remains for compatibility.
`WC_Appointment_Day_Summary` JSON adds `display_day`, `display_date_formatted`, `selectable`, and first/last slot hints sourced from projected slots.
Fixed
v5.3.4
File unavailableVerified Safe
Released 2.7 MB
Fixed
Day-duration availability and checkout validation are now timezone-agnostic across both negative and positive UTC offsets by aligning day-key generation and matching in slot display, indexed capacity lookup, and multi-day fallback validation.
Rule capacity evaluation now correctly returns zero for non-appointable matches in capacity mode, preventing disabled all-day dates from appearing available when global and product-level rules overlap.
Availability rule sorting now applies time-rule specificity only when comparing two time-family rules, so date-specific unavailability (custom/date-range) no longer gets overridden by weekday time rules on the same day.
Indexed availability: availability rules saved while the indexed cache is disabled are queued and processed when indexing is turned back on (bounded synchronous pass for the first batch, Action Scheduler for overflow), avoiding empty or stale cache after long periods with indexing off.
v5.3.3.1
File unavailableVerified Safe
Released 2.7 MB
Fixed
REST API v1: Fixed critical bug in Products controller where appointment-specific fields (duration, interval, staff assignments, etc.) could not be updated via PATCH/PUT requests due to incorrect use of array_keys() in the update loop.
Timezone display setting now only controls timezone UI/selection by context (frontend/admin/all); when disabled, timezone picker behavior is suppressed and booking/slot/cost calculations consistently fall back to site timezone.
Google Calendar: Importing or restoring synced availability rules (including backup restores) no longer triggers rule sync to Google Calendar.
Google Calendar: manual sync from a staff profile now pulls that staff’s calendar; `sync_from_gcal()` no longer ignored `set_user_id()` when called without a user ID argument.
Google Calendar: staff calendars eligible for automatic multi-calendar sync now include users with the `wc_appointment_staff` role as well as `shop_staff`; role list is filterable via `wc_appointments_gcal_staff_roles_for_sync`.
Sold out snippet: add_filter( 'woocommerce_appointments_include_sold_out', '__return_true' ); not rendering fine on frontend.
End slots might be cut off due to wrong timezone calculations.
Sorting time slots sometimes puts time slot from end to the middle of the array.
v5.3.1.2
File unavailableVerified Safe
Released 2.7 MB
Fixed
Admin panel: resolved "Call to unknown method: WC_Product::get_staff_ids()" by ensuring it is only called on valid appointment products with the missing method check.
Product Add-Ons duration + padding: storefront cost/availability checks for time-based products now include padding in extended-range slot coverage, preventing false "Sorry, the selected slot is not available." when add-ons extend duration across slot boundaries.
Staff availability rules are now indexed to the availability cache during save (synchronously), same as global rules, so availability is correct immediately after saving.
Staff rule indexing was previously deferred to async jobs and could result in corrupted or missing cache entries; indexing now runs in the same request after the transaction commit.
Slots / calendars: multi-day day-unit products block every booked civil day again — occupancy uses live appointments; overlap day-keys use timezone-agnostic civil dates (gmdate on stored tokens), matching slot tokens, not wp_date() site-local midnight.
Admin panel **create**: first available date/time via one AJAX call (`WC_Appointment_Slots_Provider::get_first_available_slot`, same as storefront), including when the first slot is outside the current month; calendar day click/drag date-only prefill auto-picks the first time or opens browse once with `noSlotsOnDateSelect` when that day has no slots; browse auto-opens only on unavailable validation (not during silent auto-pick) and stays dismissible (no immediate re-open after close until date or product changes); live preview placeholder while slots load; panel expands when the product form is ready.
Slots / calendars: minute/hour day availability (storefront `day_data`, admin mini-calendar, time-slot lists) counts only bookable slot starts via shared `WC_Appointment_Slots_Collection::slots_for_day_summary()` on the same `get_collection()` engine — fully booked days no longer stay selectable when only sub-duration engine segments remain.
Slots / time picker: **Availability check → The starting slot only** no longer lists grid starts in appointable fragments shorter than the booking duration (phantom evening slots such as 19:00 on 12h/12h products when the index leaves a tail after a gap); full-day ranges still allow starts when duration exceeds the window (e.g. 25h in 24h).
Checkout validation (`WC_Appointment_Cart_Manager::validate_appointment_order`): re-validates cart slots via `WC_Appointments_Controller::get_indexed_total_available_for_range()` (same `slot_matches_requested_range` tolerance as add-to-cart) instead of strict `start_ts` equality; **all staff together** uses staff id `0` fan-out like `is_appointable()`; in-cart holds are excluded from overlap during the check.
**Staff all together:** cart booking no longer collapses multi-staff `_staff_ids` to a single `_staff_id` during `WC_Appointment_Booking_Handler::create_appointment()` availability merge.
Admin panel: **WooCommerce Deposits** on create — `calculate_costs` / `create_appointment` honour deposit choice and tax display (incl./excl. catalog); deposit footer quote and order item meta (`_deposit_full_amount`, etc.) match storefront; pay-deposit hides footer price edit (product deposit rules); pay-in-full edits full service price via `raw_full_price`; mistaken deposit-only overrides corrected on save.
Storefront auto-select with `woocommerce_appointments_include_sold_out`: slots AJAX picks the first bookable time from the availability collection (marks it `selected` in HTML); the time picker applies that server selection instead of the first listed slot when sold-out rows are shown.
Auto-select slot highlighting: match engine `start_ts` directly and compare unifi
Removed deprecated dead code from `WC_Appointment_Slots_Legacy_Adapter`: get_time_slots(), get_available_slots()
Admin appointment panel (`wcaPanel`): localized incl./excl. tax short labels from WooCommerce; footer cost hint and add-on section respect `wc_prices_include_tax()` and whether tax is enabled; AJAX posts (`wc_appointments_panel_cost_excl_tax`, add-on incl./excl. flags, override excl. flag) only when the catalog is exclusive so net/gross semantics stay consistent.
Product Add-Ons integration: panel AJAX uses filters so admin add-on price display and suffix follow the same inclusive/exclusive catalog basis as the appointment panel.
Admin appointment block templates: admin edit link uses the same `printf( esc_html__( … ), '<!--[tag]-->' )` pattern as WooCommerce core block emails (e.g. store email / customer name in default content).
Update translations (+19 new strings).
Fixed
Admin product **Custom availability** rows (`html-appointment-availability-fields.php`): date/time display no longer converts using `get_staff_timezone( get_current_user_id() )`, which applied the **viewing user’s** saved appointment timezone to everyone (including administrators). Uses `WC_Appointments_Timezone_Helper::get_admin_user_timezone()` so **store admins and shop managers** see **site** wall times; **staff-only** users still see their profile timezone. Google/RRule summary only treats `kind_id` as a staff user when the rule `kind` is `availability#staff` (not product/global `kind_id`).
Admin user profiles now show a single appointment timezone selector for all users. Staff profiles use the shared field while keeping the staff profile navigation section and enhanced searchable timezone picker.
Indexed availability: manual **Re-index** no longer raises per-rule limits so high (e.g. thousands of RRULE occurrences or a full year of days in one pass) that a single AJAX chunk could hit browser/proxy timeouts despite batching. `WC_Appointments_Cache_Availability::run_rule_index_pass()` uses modest multiples of the normal per-pass caps; rules that still need more work are re-scheduled as before.
Unified slots when a **specific staff** is selected: `WC_Appointment_Slots_Rule_Resolver::build_minute_mask` (dynamic rules) and `WC_Appointment_Slots_Data_Normalizer::build_minute_mask_from_index_rows` (indexed cache) now apply matching **staff**-scope rows **inline** on the main minute mask in sorted override order, instead of replacing the mask with a staff snapshot that could resurrect minutes a **higher-priority** global/product **not available** row had already cleared. Rule priority vs. staff overrides now match between indexing on and off.
Indexed full-day row types **`days` / `months` / `weeks`**: minute-mask painting clamps the day span at **1440** minutes (end of civil day) so the mask tail past midnight is not left appointable from index timestamps; avoids rule-blocked calendar days appearing clickable when availability indexing is enabled.
Legacy storefront calendar payload (`WC_Appointment_Slots_Legacy_Adapter::collection_to_scheduled_days`): days with **`unavailable`** status are also copied into **`fully_scheduled_days`** for the same date keys, because `date-picker.js` treats **`fully
Admin timezone display: `appointments_time_zone` cookie no longer applies in admin/REST. Only staff (providers) may use profile timezone in admin; admins and shop managers always see site timezone. Remote staff can still set profile timezone.
Admin create flow, existing order: order number no longer appears twice in Select2 and the selected chip—both prefixed `#` + internal ID onto labels that already included the display order number.
Legacy calendar staff view: staffed appointments no longer also list under Unassigned—`$is_creator` matched `creator_id === 0` because `(int) 'unassigned' === 0`; creator shortcut now requires non-zero `creator_id`.
Manual appointment on existing order (admin AJAX): line meta used public `appointment_id`, so emails/item meta showed an extra row. New rows use `_appointment_id` only (checkout parity); formatted order item meta strips legacy public `appointment_id` for display.
Admin browse day slots: discounted `price.display` (`<del>` / `<ins>`) was React-escaped; `.wca-slot-price` now renders with `dangerouslySetInnerHTML` (same as panel footer).
Recurring `time:range`: same-day windows ending at **`00:00`** were misclassified as overnight (**string** order: **`00:00` < `08:00`**, **`24:00` < `08:00`**). `WC_Product_Appointment_Rule_Manager` / `WC_Appointment_Slots_Rule_Minutes` now use minute-based wrap helpers (`is_wrap_midnight_hhmm_pair` / `is_wrap_midnight_time_pair`); `evaluate_rule_for_timestamp` shares the same bounds. Availability cache indexing aligns with slot rules for **`00:00`** end-of-day, maps **`24:00` → 1440**, and sets same-day **`eff_to_min = 1440`** when needed. **`time:range` overnight** at midnight merges evening (**from–24:00**) with morning (**00:00–to**) when the prior day is in range (**`RuleMinutesMidnightWrapTest`**).
Unified slots + `woocommerce_appointments_slots_display_pricing`: `WC_Appointment_Slots_Pricing::compute_slot_price_raw()` returns the full filtered slot price when the filter runs (not only when below default). `WC_Appointment_Slots_Collection::serialize_slots()` writes `price_data.display` / `price_formatted` HTML; the lazy formatter had read a non-array `price` from `jsonSerialize()`, so strikethrough/sale never reached unified JSON.
Availability rules: changing `range_type` (e.g. Date range with time → Days) clears unused `from_date` / `to_date` via `apply_json_rule_to_availability()` (`time:range`, `custom:daterange`, `store_availability` still keep dates), so weekly/monthly rules are not skewed by stale columns.
Indexed availability cache: indexer ignores stray `from_date` / `to_date` on **`days`**, **`months`**, **`weeks`** (same as recurring `time` / `time:N`), so corrupt rows no longer shrink the cache window until re-saved.
Admin appointment save (`WC_Appointment_Meta_Box_Data::meta_box_save`): failed start/end validation now sets `self::$saved_meta_box` before return (matches success path), avoiding duplicate admin errors or extra `save_post` work in the same request.
Day-unit products when add-ons (or `appointment_form_posted_total_duration`) extend past base duration: `WC_Appointments_Controller::get_exact_available_capacity_for_slot()` no longer uses the multi-day “start row only” shortcut (it skipped later days) or `get_extended_range_available_capacity()` for that span (UTC civil-day mismatch caused false unavailability). Added `get_addon_extended_day_span_capacity()` (same UTC keys as `get_multi_day_available_capacity()`, min capacity across span days). Minute/hour add-on extension unchanged.
Bundled Product Add-Ons integration JS: strip esbuild's top `"use strict"` in `build-js-addons`; initialize `window.WC_PAO`; declare quickview `product_price`; broaden `PaoValidation` minify post-replace for renamed locals.
Slots engine: time-slot start step again uses the product **interval** (base) when it differs from **duration** (e.g. 90 min service every 2 hours). 5.3.4.1’s `min(duration, interval)` grid for all minute/hour products had caused starts every 90m instead of every 120m in that case; duration only defines block length, not the cadence between start times.
Indexed availability: `custom` rules that only store `YYYY-MM-DD` bounds in `from_range`/`to_range` (with empty `from_date`/`to_date`) now anchor the indexer to that window so far-future custom blocks are not skipped on the first bounded pass.
Indexed availability: open-ended recurring non-RRULE rules (weekday `time:*`, `days`, `months`) prime through the cache horizon on a cold first pass instead of only the per-pass day chunk, so recurring weekdays do not disappear until background continuation runs.
Admin create success message now uses canonical server-provided schedule text to prevent date shifts caused by client-side timezone reformatting.
Multiple `appointment_form` shortcodes/blocks on the same page now keep datepicker state isolated per form; with auto-select enabled, one form no longer shifts another form's calendar month/year.
PHP 7.4 compatibility: removed PHP 8.0+ union return types from several methods (e.g. `string|false`, `WP_REST_Response|WP_Error`) that caused parse errors on PHP 7.4; `@return` PHPDoc preserved so behavior and tooling contracts stay documented.
PHP 7.4 compatibility: slots subsystem (value objects, collection, engine, provider, legacy bridge, REST v2 slots) no longer uses PHP 8.0+ syntax that failed to parse on PHP 7.4 — `readonly` typed properties, named arguments in `WC_Appointment_Slot` construction, trailing commas in function parameter lists, and `match` (replaced with equivalent `switch` / conditionals). Runtime slot math and responses are unchanged.
PHP 7.4 compatibility: Google Calendar (`WC_Appointments_GCal`) and the appointment product model (`WC_Product_Appointment`) no longer declare PHP 8.0+ union types on properties (`int|false`, `string|false`, `int|string|false`); types are documented in PHPDoc and values/behavior stay the same.