Hooks and Filters
You want Core Bookings to price by time of day, block slots from another calendar, push bookings into your CRM or add a column to the export, without editing the plugin. Every one of those is a hook. The built-in modules use the same hooks you do, so anything a module can do, your own plugin can do too.
All hooks use the core_bookings/ prefix. Amounts are integers in minor currency units (2500 is 25.00), and times are Unix seconds unless a row says otherwise.
Before You Start
- Rejecting a request. Validation hooks reject by throwing
Gatilab\CoreBookings\Problem:throw new Problem('slot_taken', __('That time is taken.'), 409);. The slug becomes thecb_<slug>error code customers and the REST API see. The status defaults to 422. - Module hooks. Some hooks below fire only while their module is switched on. Those rows name the module.
- Transactions. Hooks that run inside the reservation or import transaction roll the whole request back when they throw. Never call a slow external API from them.
- Events are at least once. Lifecycle actions can repeat after a retry. Deduplicate with
event_id.
Booking Engine and Availability
| Hook | Type | Arguments | Use |
|---|---|---|---|
core_bookings/request_service | filter | array $service, array $input | Apply customer choices ($input['choices']), such as a longer duration. Keep id. Used for availability, quotes, reservations and waitlist joins; rescheduling keeps the purchased snapshot. |
core_bookings/staff_service | filter | array $service, int $staffId | The service as one host delivers it (per-staff length). Never applied to rescheduling. |
core_bookings/validate_request | action | array $service, array $input, string $intent | Throw a Problem to reject. $intent is booking, offer or admin. A slug of answer_<key> marks a question field. |
core_bookings/request_fingerprint | filter | array $key, array $input, array $service | Add your own input that must match when a request key is retried. |
core_bookings/price | filter | array $price, array $service, array $input, array $context | $price is ['total','due','lines']; lines are ['label','amount']. $context has seats, index, count, start and staff. |
core_bookings/candidates | filter | int[] $starts, array $service, array $staff, string $date | Remove start times for one staff date (opening hours). It can only remove. |
core_bookings/slots | filter | array $result, array $service, array $staff, int $from, int $until, array $context | Reorder or remove offered starts. $context has ignore, seats and any. |
core_bookings/remaining | filter | int $left, array $service, array $staff, int $start, string $ignore | Lower the places left at a start (shared resources). It can never raise the count. |
core_bookings/staff_order | filter | int[] $ids, array $service, array $input | Order the staff tried for “any available” bookings. |
core_bookings/series_starts | filter | int[] $starts, array $service, array $input, array $staff | Group services: every start of a fixed series (a course), reserved together. At most 52, including the requested start. |
core_bookings/reserved | action | array $row, array $service, array $input, int $index, Store $store | Inside the reservation transaction after each row is saved. Throwing rolls back the request. |
core_bookings/validate_reschedule | action | array $service, array $booking, int $start, bool $operator | Before a reschedule, outside the transaction. Throw to refuse. |
core_bookings/rescheduled | action | array $new, array $old, Store $store | Inside the reschedule transaction. |
core_bookings/validate_confirmation | action | array $service, array $booking, string $context | Before a held booking is confirmed later (approval, claim, payment or review). Throw slot_taken to refuse. |
core_bookings/booking_hosts | filter | int[] $ids, array $booking | Staff who co-host a booking and may open and change it. |
Payments and Checkout
| Hook | Type | Arguments | Use |
|---|---|---|---|
core_bookings/payment_gateways | filter | Gateway[] $gateways | Register a hosted checkout provider for service payments. PayPal, Polar and Paddle use it. |
core_bookings/booking_credit | filter | int $credit, array $booking | Amount already settled outside money payments (a gift card). Lowers the balance due. |
core_bookings/cancellation_retained | filter | int $retained, array $booking, int $credit, Store $store | The part of a credit kept when a cancelled booking returns it (Promotions). |
core_bookings/refund_suggestion | filter | int $amount, array $booking, Store $store | Amount prefilled in the appointment panel’s refund form. |
core_bookings/balance_tip_limit | filter | int $limit, array $booking | Largest tip allowed on a Stripe balance payment. 0 turns tips off. |
core_bookings/stripe_event_types | filter | string[] $types | Extra Stripe event types the verified webhook stores. |
core_bookings/stripe_event | filter | false, array $event, Payments $payments | Handle a stored, signature-verified Stripe event. Return true once handled; throw to retry. |
core_bookings/fluentcart_cart | filter | mixed $record, string $kind, string $ref | Describe a module’s one-item FluentCart cart. |
core_bookings/fluentcart_linked | action | string $kind, string $ref, object $order | The checkout created an order for a module cart. |
core_bookings/fluentcart_order | action | int $order, int $version | A FluentCart order was paid, refunded or changed. |
core_bookings/fluentcart_subscription | filter | array $record, object $subscription | Correct a normalized FluentCart subscription (Memberships). |
core_bookings/fluentcart_order_url | filter | string $url, array $booking | The customer’s FluentCart order page (Taxes). |
A gateway extends Gatilab\CoreBookings\Gateway and implements id(), label(), ready(), offered(), currencies(), hosts(), start(), state(), webhook(), history() and refund(). IDs are 2 to 32 lower-case letters, digits or underscores, starting with a letter; free, offline, stripe, stripe_embedded and fluentcart are reserved. Its webhook address is /wp-json/core-bookings/v1/webhooks/<id>. Register the filter even while your settings are off, so payments already started still settle.
add_filter('core_bookings/payment_gateways', function (array $gateways): array {
$gateways[] = new My_Gateway(); // extends \Gatilab\CoreBookings\Gateway
return $gateways;
});
Lifecycle Events
These fire from the background worker after the change is committed. Tokens, provider keys and private manage links are never included.
| Hook | Type | Arguments | Use |
|---|---|---|---|
core_bookings/lifecycle | action | array $event | Every booking event: event_id, event, booking (snapshot), occurred_at. |
core_bookings/booking_<event> | action | array $event | One booking event, such as booking_created, booking_confirmed, booking_cancel, booking_reschedule, booking_paid or booking_refunded. |
core_bookings/<subject>_<event> | action | array $event | Other subjects: customer_created, pack_activated, waitlist_offered and so on. The payload sits under the subject key. |
core_bookings/event | action | array $event | Every event for every subject: event_id, event, subject_type, subject_id, booking_id, payload, occurred_at. |
core_bookings/booking_erased | action | string $id, Store $store | A booking was anonymized. Remove your copy of its personal data. |
core_bookings/checked_in | action | array $booking, array $state, Store $store | A check-in was saved or undone (Classes, Staff portal). |
core_bookings/webhook_events | filter | array $types | List your module’s events on the Webhooks screen. |
core_bookings/webhook_booking | filter | array $data, array $booking, Store $store | Add fields to the booking object of webhook payloads. Core fields always win. |
add_action('core_bookings/booking_confirmed', function (array $event): void {
if (get_transient('my_crm_' . $event['event_id'])) { return; } // already sent
my_crm_push($event['booking']['email'], $event['booking']['start_at']);
set_transient('my_crm_' . $event['event_id'], 1, DAY_IN_SECONDS);
});
Throwing an exception retries the event a limited number of times. For HTTP delivery without code, use outgoing webhooks.
Services, Staff, Customers and the Workspace
| Hook | Type | Arguments | Use |
|---|---|---|---|
core_bookings/service_config | filter | array $config, array $input | Validate and add your keys to a saved service. Throw to reject. Prefix your keys. |
core_bookings/service_editor | action | array $service | Print fields inside the service form. |
core_bookings/service_saved | action | int $id, array $service, array $old | After a service is saved (editor, block editor, WP-CLI). Imports don’t fire it. |
core_bookings/staff_editor | action | int $userId, array $row | Print fields inside the staff form. |
core_bookings/staff_saved | action | int $userId, array $input | After a staff profile, time block or portal hours edit is saved. |
core_bookings/staff_scope | filter | string $sql, int $userId, Store $store | The bookings a staff member works on (prepared SQL, table alias b). |
core_bookings/customer_meta_fields | filter | array $fields | Register customer fields: key => ['schema', 'customer_read', 'customer_write', 'internal']. Only registered fields can be saved. |
core_bookings/customer_profile | action | array $customer, Store $store | Print sections on a customer’s admin profile. |
core_bookings/customer_merge | action | array $from, array $to, Store $store | Inside the duplicate-merge transaction. Move your rows keyed by the old customer. |
core_bookings/customer_booking | filter | array $payload, array $booking, Store $store | Add details and links rows to the customer’s booking page and dashboard. |
core_bookings/customer_dashboard | filter | array $sections, string $email, Store $store | Add sections to the customer dashboard. |
core_bookings/appointment_panel | filter | string $html, array $booking, Store $store | Escaped HTML sections for the appointment panel. |
core_bookings/admin_views | filter | array $views | Add a workspace screen: id => ['label', 'description', 'capability', 'render', 'parent']. |
core_bookings/export_columns | filter | array $columns | key => label columns added to the appointments CSV. |
core_bookings/export_row | filter | array $values, array $booking, Store $store | key => value for those columns, one booking at a time. |
core_bookings/email_placeholders | filter | array $placeholders | name => label placeholders listed under Emails. |
core_bookings/email_values | filter | array $values, array $booking, array $service, Store $store | Fill those placeholders. |
core_bookings/deliver_email | filter | mixed $handled, array $message | Send Core Bookings email through your own provider. Return true once your provider accepts it, null to use the configured delivery, or throw to retry. Deduplicate by $message['event_id']. |
core_bookings/staff_recipients | filter | array $to, array $booking, int $actor | email => name recipients of a staff booking email. |
core_bookings/job | filter | false, array $job, Store $store | Handle your own background job kind. Return true when done. Name kinds with core_bookings/job_labels and list ones without a booking in core_bookings/independent_jobs. |
core_bookings/dismissible_jobs | filter | string[] $kinds | Job kinds a manager may dismiss in Operations after a failure. |
core_bookings/transfer_export | filter | array $data, bool $withBookings, Store $store | Add your portable records to a JSON export. |
core_bookings/transfer_imported | action | array $data, array $serviceIds, array $bookingIds, Store $store, array $staff | Inside the import transaction. Map your records to this site. |
core_bookings/module_enabled | action | string $id | A module was switched on. |
Calendars, Meetings and Integrations
| Hook | Type | Arguments | Use |
|---|---|---|---|
core_bookings/busy | filter | array $busy, array $staff, int $start, int $end | Add busy intervals [[start, end], …] from another calendar. Throw calendar_unavailable (503) to pause that person’s online availability. |
core_bookings/calendar_event | filter | array $event, array $booking, string $kind | The Google Calendar event body before it’s written (booking or meeting). |
core_bookings/calendar_job | action | array $booking, Store $store | A booking’s calendar job is running. Queue your own sync. |
core_bookings/calendar_worker | action | CalendarSync $sync, float $deadline | Bounded maintenance inside the calendar worker, every minute. |
core_bookings/calendar_connections | action | int $userId, array $staff | Print sections on My calendar. |
core_bookings/meeting_providers | filter | array $providers | Add a video meeting choice (id => label). none, meet and zoom are built in. |
core_bookings/meeting_ready | action | string $provider, array $service, array $staff | Throw when this host can’t create links. |
core_bookings/meeting_link | filter | string $url, string $provider, array $meeting, array $booking, array $staff, array $snapshot | Create or update the remote meeting and return its HTTPS join URL. |
core_bookings/meeting_cleanup | filter | ?int $status, string $provider, array $meeting | Delete the remote meeting and return the provider’s HTTP status. |
core_bookings/automation_actions | filter | array $actions | Add actions to the Automations module. |
core_bookings/automation_values | filter | array $values, array $booking, Store $store | Fill automation placeholders. |
core_bookings/crm_contact | filter | array $contact, array $booking, string $tool | Change the contact sent to FluentCRM or MailerPress (Integrations). |
core_bookings/review_approved | action | array $review, Store $store | A review was approved (Reviews). |
core_bookings/review_display | filter | bool $show, array $args | Return false to turn off the built-in reviews block and shortcode. |
core_bookings/insights_locations | filter | array $locations | id => label locations for the Reports filter. |
core_bookings/whatsapp_graph_version | filter | string $version | Graph API version for WhatsApp (default v21.0). |
Filters Core Bookings Answers
With the Integrations module on, other plugins can ask Core Bookings for data through these filters:
apply_filters('core_bookings/portal_bookings', [], $userId, $args): bookings of the customer linked to a WordPress user, for a client portal.apply_filters('core_bookings/support_context', [], $email, $args): a customer’s upcoming and recent bookings for a support ticket.apply_filters('core_bookings/quote_booking_url', '', $quote)anddo_action('core_bookings/open_booking', $quote): a private booking link with the customer prefilled, for a quote.core_bookings/quote_bookedfires when it’s used.
Booking Form Extensions
| Hook | Type | Arguments | Use |
|---|---|---|---|
core_bookings/public_service | filter | array $payload, array $service | Set choices, questions, coupon, quote or hidden for the booking form. |
core_bookings/public_services | filter | array $list | Remove or reorder services the form and dashboard list. |
core_bookings/widget_assets | action | int $service, array $options | Enqueue scripts that extend the form. |
core_bookings/checkin_url | filter | string $url, string $code, array $booking | The URL a class ticket’s QR code opens (Classes, Staff portal). |
In the browser, register scripts with core-bookings as a dependency and push functions onto window.coreBookingsHooks.appointment, details, payload or manage. Custom question types render through window.coreBookingsQuestionTypes[type].
add_filter('core_bookings/price', function (array $price, array $service, array $input, array $context): array {
$hour = (int) wp_date('G', $context['start'] ?: time());
if ($context['start'] && $hour >= 18) {
$price['lines'][] = ['label' => 'Evening rate', 'amount' => 1000];
$price['total'] += 1000;
$price['due'] += 1000;
}
return $price;
}, 10, 4);
Need a hook that isn’t here? Ask on the roadmap.