Ready to Transform Your Online Presence? Let's Get Started!

Add a buffer period to WooCommerce Bookable products only if there are existing bookings in Woocommerc Bookings Plugin

Goal: I’m using WooCommerce Bookings and want to implement conditional buffer times that only apply when there’s an existing booking.

The problem:
Currently, when I set a buffer (e.g., 1 hour) via the WooCommerce Bookings admin UI, it always applies, even when there are no bookings for that product on a given day. This unnecessarily limits available start times — for example, instead of allowing bookings at every hour (7am, 8am, 9am…), the buffer enforces 2-hours gaps (7am, 9am, 11am…), even if the day is wide open.

What I want instead:
I want the buffer time (e.g., 1 hour before booking start time and 1 hour after booking end time) to be added only after an existing booking.
For example:

  • If a booking exists from 9am to 12pm on June 2nd, I want to enforce a 1-hour buffer before it starts, 1-hour buffer after it ends, so the next available booking would be like 7am, 1pm, 2pm ….
  • However, if there are no bookings at all on June 2nd, I want the full day to be open with no buffer restrictions — allowing start times like 7am, 8am, 9am, etc., at hourly intervals.

Solution

1. Creating two buffer child bookings with the same order to block 1 hour before booked session starts and 1 hour after booked session ends, these bookings we can call follow up booking or bookings to add padding time or buffer bookings

    function auto_create_followup_booking( $booking_id ) {
    	
    	// Get the previous booking from the ID
    	$prev_booking = get_wc_booking( $booking_id );
    	
    	// Don't want follow ups for follow ups
    	if ( $prev_booking->get_parent_id() <= 0 ) {
    		// Set the follow up data
    		$new_booking_data = array(
    			'start_date'  => strtotime( '-1 hour', $prev_booking->get_start() ), // same time, 1 week on
    			'end_date'    => strtotime( '+0 hour', $prev_booking->get_start() ),   // same time, 1 week on
    			'resource_id' => $prev_booking->get_resource_id(),                   // same resource
    			'parent_id'   => $booking_id,                                        // set the parent
    		);
    		// Did the previous booking have persons?
    		$persons = $prev_booking->get_persons();
    		if ( is_array( $persons ) && 0 < count( $persons ) ) {
    			$new_booking_data['persons'] = $persons;
    		}
    		// Was the previous booking all day?
    		if ( $prev_booking->is_all_day() ) {
    			$new_booking_data['all_day'] = true;
    		}
    		create_wc_booking( 
    			$prev_booking->get_product_id(), // Creating a booking for the previous bookings product
    			$new_booking_data,               // Use the data pulled above
    			$prev_booking->get_status(),     // Match previous bookings status
    			false                            // Not exact, look for a slot
    		);
    		
    		
    		// Set the follow up data
    		$new_booking_data = array(
    			'start_date'  => strtotime( '+0 hour', $prev_booking->get_end() ), // same time, 1 week on
    			'end_date'    => strtotime( '+1 hour', $prev_booking->get_end() ),   // same time, 1 week on
    			'resource_id' => $prev_booking->get_resource_id(),                   // same resource
    			'parent_id'   => $booking_id,                                        // set the parent
    		);
    		// Did the previous booking have persons?
    		$persons = $prev_booking->get_persons();
    		if ( is_array( $persons ) && 0 < count( $persons ) ) {
    			$new_booking_data['persons'] = $persons;
    		}
    		// Was the previous booking all day?
    		if ( $prev_booking->is_all_day() ) {
    			$new_booking_data['all_day'] = true;
    		}
    		create_wc_booking( 
    			$prev_booking->get_product_id(), // Creating a booking for the previous bookings product
    			$new_booking_data,               // Use the data pulled above
    			$prev_booking->get_status(),     // Match previous bookings status
    			false                            // Not exact, look for a slot
    		);
    	}
    }
    add_action( 'woocommerce_booking_in-cart_to_paid', 'auto_create_followup_booking' );
    add_action( 'woocommerce_booking_unpaid_to_paid', 'auto_create_followup_booking' );
    add_action( 'woocommerce_booking_confirmed_to_paid', 'auto_create_followup_booking' );
    

    2. Step 1 will send new bookings emails to admin for these 2 buffer bookings, if you want to disable these email you have to follow this step other wise you can skip, this is an optional step

    function filter_woocommerce_email_recipient_new_booking_custom( $recipient, $booking ) {   
        // Make sure $booking is a WC_Booking object
        if (!is_a($booking, 'WC_Booking')) {
            return $recipient;
        }
    
        if ( $booking->get_parent_id() > 0 ) {
    		return '';
    	}
    
        return $recipient;
    }
    add_filter( 'woocommerce_email_recipient_new_booking', 'filter_woocommerce_email_recipient_new_booking_custom', 10, 2 );

    3. This step you needed if you are using Google Calendar integration. These 2 buffer booking will also be synced with google calender as normal booking, so if you want to modify the google event title to indentify that these 2 booking are for buffer or follow up us the code given below

    add_filter('woocommerce_bookings_gcalendar_sync', 'woocommerce_bookings_gcalendar_sync_custom_update', 10, 2);
    function woocommerce_bookings_gcalendar_sync_custom_update( $event, $booking ){
    	if( $booking->get_parent_id() > 0 ){
    		$product_id      = $booking->get_product_id();
    		$product         = wc_get_product( $product_id );
    		// Set the event data.
    		$product_title = $product ? html_entity_decode( $product->get_title(), ENT_COMPAT ) : __( 'Booking', 'woocommerce-bookings' );
    		$event->setSummary( wp_kses_post( sprintf( '%s, %s - #%s', 'Buffer Duration: 1 Hour', $product_title, $booking->get_id() ) ) );
    	}
    	return $event;
    }

    4. This step you have to follow if you are using Google Calendar integration and if you are also booking manuall sessions from google calender and also want to block buffer hours in website for session booked on google calendar

    add_filter('woocommerce_booking_get_availability_rules', 'ag_filter_rules', 99, 3);
    function ag_filter_rules($availability_rules, $for_resource, $product){
    	$availability_rules_temp = $availability_rules;
    	if(!empty($availability_rules_temp)){
    		foreach($availability_rules_temp as $rule){
    			if($rule['type'] == "custom:daterange"){
    				
    				foreach ($rule['range'] as $year => $months) {
    					foreach ($months as $month => $days) {
    						foreach ($days as $day => $timeRange) {
    							$fromTime = DateTime::createFromFormat('H:i', $timeRange['from']);
                        		$toTime = DateTime::createFromFormat('H:i', $timeRange['to']);
    							
    							if (!$fromTime || !$toTime) continue;
    							
    							// 1. Add new entry BEFORE (if from ≠ 09:00)
    							if ($timeRange['from'] !== '09:00') {
    								$beforeFrom = clone $fromTime;
    								$beforeFrom->modify('-1 hour');
    
    								$newBefore = $rule;
    								$newBefore['order'] = count($availability_rules) + 1;
    								$newBefore['range'] = [
    									$year => [
    										$month => [
    											$day => [
    												'from' => $beforeFrom->format('H:i'),
    												'to' => $fromTime->format('H:i'),
    												'rule' => false
    											]
    										]
    									]
    								];
    
    								$availability_rules[] = $newBefore;
    							}
    							
    							// 2. Add new entry AFTER (always)
    							if ($timeRange['to'] !== '21:00') {
    								$afterTo = clone $toTime;
    								$afterTo->modify('+1 hour');
    
    								$newAfter = $rule;
    								$newAfter['order'] = count($availability_rules) + 1;
    								$newAfter['range'] = [
    									$year => [
    										$month => [
    											$day => [
    												'from' => $toTime->format('H:i'),
    												'to' => $afterTo->format('H:i'),
    												'rule' => false
    											]
    										]
    									]
    								];
    								
    								$availability_rules[] = $newAfter;
    							}
    							
    						}
    					}
    				}
    				
    			}
    		}
    	}
    	
    	return $availability_rules;
    }

    Contact Us if you need any further advanced customization