I'm back to bring you the next part of this series. In this part, we are adding a booking form to the Room’s page, which shows all the room’s information. This form allows your customers to submit a booking from the frontend easily.

This booking form has some different things from the one we created in part 2, mainly the functionality of fields. Let’s see how.
Video Version
Before Getting Started
In this part, we’ll create a custom booking form anywhere you want, inside a room detail, or even in a popup. The submitted data will still be saved in the Booking post type we created earlier in part 2. And of course, we’ll use custom fields to collect all the booking information.
So, we need many basic and advanced features. So, we highly recommend you use Meta Box AIO to have a framework to have everything. Also, it includes all the Meta Box extensions that you need for your creation.
In this part, we especially use MB Frontend Submission, which creates editorial forms so users can submit blog posts on the front end. And, MB Views to create a template for a single room page to display the booking form on the frontend.
Create a Single Room
I already have some rooms created in part 1 of this series.

Now, let’s create a new php file for the single room. Add the following code:
<?php get_header(); ?>
<main class="main" role="main">
<?php
while ( have_posts() ) {
the_post();
get_template_part( 'template-parts/content/post' );
}
?>
</main>
<?php get_footer(); ?>
This code helps to create a single room page to show the room details, just like a normal single post.
Next, to display the room information more nicely, I’ll use MB Views to build a template.

Now, you can insert custom fields from the room group field or directly write code in the Template section.

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css" />
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
<div class="swiper main-slider">
<div class="swiper-wrapper">
{% for item in post.gallery %}
<div class="swiper-slide">
<img src="{{ item.full.url }}" width="{{ item.full.width }}" height="{{ item.full.height }}"
alt="{{ item.full.alt }}">
</div>
{% endfor %}
</div>
<!-- Navigation -->
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
<!-- Dots -->
<div class="swiper-pagination"></div>
</div>
<!-- Thumbnail slider -->
<div class="swiper thumb-slider">
<div class="swiper-wrapper">
{% for item in post.gallery %}
<div class="swiper-slide">
<img src="{{ item.full.url }}" width="100" height="60" alt="{{ item.full.alt }}">
</div>
{% endfor %}
</div>
</div>
<div class="infor">
<div class="regular_price">Price Regular: {{ post.price }}$ </div>
<ul class="number_of_adults max_children_per_room">
<li>Number of Adults: {{ post.number_of_adults }}</li>
<li>Max Children per room: {{ post.max_children_per_room }} </li>
</ul>
<div class="additional_information">{{ post.additional_information }} </div>
</div>
Explanation:
I add code to include CSS and JS from the Swiper library.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css" /> <script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
We build two sliders. The first one is for the main slider, showing large room images.
In there, we use a loop to get all images from the advanced image field with the gallery ID.
<div class="swiper main-slider">
<div class="swiper-wrapper">
{% for item in post.gallery %}
<div class="swiper-slide">
<img src="{{ item.full.url }}" width="{{ item.full.width }}" height="{{ item.full.height }}"
alt="{{ item.full.alt }}">
</div>
{% endfor %}
</div>
<!-- Navigation -->
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
<!-- Dots -->
<div class="swiper-pagination"></div>
</div>
The second one is for thumbnail slider, showing smaller preview images. It also loops through the same gallery field.
<!-- Thumbnail slider -->
<div class="swiper thumb-slider">
<div class="swiper-wrapper">
{% for item in post.gallery %}
<div class="swiper-slide">
<img src="{{ item.full.url }}" width="100" height="60" alt="{{ item.full.alt }}">
</div>
{% endfor %}
</div>
</div>
And this part of the code retrieves all room information, such as price, maximum children, adults, and more.
<div class="infor">
<div class="regular_price">Price Regular: {{ post.price }}$ </div>
<ul class="number_of_adults max_children_per_room">
<li>Number of Adults: {{ post.number_of_adults }}</li>
<li>Max Children per room: {{ post.max_children_per_room }} </li>
</ul>
<div class="additional_information">{{ post.additional_information }} </div>
</div>
Then, in the CSS tab, I’ll add styles for the layout:
.regular_price {
color: red;
font-weight: bold;
font-size: 22px;
}
.additional_information {
color: #13acc7;
font-size: 20px;
font-weight: 500;
}
ul.number_of_adults.max_children_per_room {
font-weight: bold;
padding-top: 15px;
}
.infor {
border-top: 2px solid #919191;
margin-top: 20px;
padding-top: 20px;
}
.thumb-slider img {
height: 85px !important;
}
.main-slider {
width: 100%;
/* max-width: 800px; */
margin: 0 auto 20px;
}
.thumb-slider {
width: 100%;
max-width: 800px;
margin: 0 auto;
}
.swiper-pagination.swiper-pagination-clickable.swiper-pagination-bullets.swiper-pagination-horizontal {
position: static;
}
.thumb-slider .swiper-slide {
width: auto;
opacity: 0.4;
cursor: pointer;
transition: opacity 0.3s ease;
}
.thumb-slider .swiper-slide-thumb-active {
opacity: 1;
border: 2px solid #007aff;
}
.swiper-slide img {
width: 100%;
height: auto;
object-fit: cover;
display: block;
}
.swiper-button-next,
.swiper-button-prev {
color: #007aff;
}
.swiper-pagination {
text-align: center;
margin-top: 10px;
}
.thumb-slider .swiper-slide {
width: auto;
cursor: pointer;
transition: border 0.3s ease;
opacity: 1;
}
.thumb-slider .swiper-slide-thumb-active {
border: 2px solid #007aff;
}
And in the JavaScript tab, I’ll initialize the slider.
const thumbSlider = new Swiper('.thumb-slider', {
spaceBetween: 10,
freeMode: true,
watchSlidesProgress: true,
breakpoints: {
0: { slidesPerView: 3 },
640: { slidesPerView: 4 },
768: { slidesPerView: 4 },
1024: { slidesPerView: 5 }
}
});
const mainSlider = new Swiper('.main-slider', {
spaceBetween: 10,
autoHeight: true,
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
pagination: {
el: '.swiper-pagination',
clickable: true,
},
thumbs: {
swiper: thumbSlider,
},
});
In particular:
const thumbSlider = new Swiper('.thumb-slider', {
spaceBetween: 10,
freeMode: true,
watchSlidesProgress: true,
breakpoints: {
0: { slidesPerView: 3 },
640: { slidesPerView: 4 },
768: { slidesPerView: 4 },
1024: { slidesPerView: 5 }
}
});
This part declares and creates the thumbnail slider below the main slider, using the properties of Swiper JS.
The next part declares and creates the main slider, also based on the properties of Swiper JS.
const mainSlider = new Swiper('.main-slider', {
spaceBetween: 10,
autoHeight: true,
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
pagination: {
el: '.swiper-pagination',
clickable: true,
},
thumbs: {
swiper: thumbSlider,
},
});
Scroll down to the settings section, choose the type of this template as singular. Then, choose the location as the room and set the position for it.

Now you can see that the single room page displays all the room’s information.

Add Booking Form In Single Room
Now, let’s add the booking form to the single room page. First, go back to the PHP file you created for the single room.
At the position where you want the booking form to appear, insert this code to execute the shortcode and display its output.
echo do_shortcode( '[mb_frontend_form id="booking-fields"]' );
You can get the form’s shortcode ('[mb_frontend_form id="booking-fields"]') directly from the field group settings created with MB Frontend Submission. Since we don’t need the post title or content for bookings, we simply removed them.
You can see the booking form on the single page.

However, some fields are unnecessary for customers, so we can add CSS classes to hide those fields.

Then, in the theme’s Customizer, add CSS to hide any fields you don’t need. We can also hide the “Add more” button to make sure each submission corresponds to only one booking.
.hide-frontend {
display: none !important;
}
.rwmb-button.add-clone {
display: none !important;
}

Therefore, we already have the booking form we wanted. However, functions like automatic price calculation, date selection, and total nights calculation haven’t been implemented yet, so let’s move on to the next step.

Add Extra Functions to Booking on the Frontend
In this part, we’ll add extra functions so the form doesn’t just collect data but also works like a real booking system.
We’re going to:
- Automatically display the corresponding price of the booking room;
- Automatically show the valid booking dates;
- Automatically calculate the total nights of stay;
- Automatically calculate the total amount.
We’ll need to write quite a bit of code for this part. Let’s go!
Go to your theme folder, and open the functions.php file you created in Part 2. Add the following code.
function enqueue_scripts() {
// Register and enqueue script
wp_enqueue_script( 'jquery-ui-datepicker' );
wp_enqueue_script( 'booking-js', get_template_directory_uri() . '/js/booking.js', array( 'jquery', 'jquery-ui-datepicker' ), '1.0', true );
// Pass data into JavaScript using wp_add_inline_script()
$ajax_data = array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'booking_nonce' ),
);
$inline_script = 'var booking_ajax = ' . wp_json_encode( $ajax_data ) . ';';
wp_add_inline_script( 'booking-js', $inline_script, 'before' );
// Passing room data into JavaScript
$rooms = get_posts( array(
'post_type' => 'room',
'posts_per_page' => -1,
) );
$rooms_data = array();
foreach ( $rooms as $room ) {
// Get room rates from Meta Box
$room_price = rwmb_get_value( 'price', array(), $room->ID );
if ( ! $room_price ) {
$room_price = 0;
}
$rooms_data[] = array(
'id' => $room->ID,
'title' => $room->post_title,
'price' => $room_price,
);
}
$rooms_script = 'var rooms_data = ' . wp_json_encode( $rooms_data ) . ';';
wp_add_inline_script( 'booking-js', $rooms_script, 'before' );
// Pass current_room_id if available
$current_room_id = get_the_ID();
if ( $current_room_id ) {
$current_room_script = 'var current_room_id = ' . wp_json_encode( $current_room_id ) . ';';
wp_add_inline_script( 'booking-js', $current_room_script, 'before' );
}
}
add_action( 'wp_enqueue_scripts', 'enqueue_scripts' );
// Function to get all days between 2 dates
function get_dates_between( $start_date, $end_date ) {
$dates = array();
$current = strtotime( $start_date );
$end = strtotime( $end_date );
while ( $current <= $end ) { $dates[] = date( 'Y-m-d', $current ); $current = strtotime( '+1 day', $current ); } return $dates; } add_filter( 'rwmb_frontend_validate', function( $validate, $config ) { if ( 'booking-fields' !== $config['id'] ) { return $validate; } // Get all selected rooms in the form $selected_rooms = array(); if ( isset( $_POST['group_booking'] ) && is_array( $_POST['group_booking'] ) ) { foreach ( $_POST['group_booking'] as $index => $room ) {
if ( empty( $room['room'] ) || empty( $room['check_in'] ) || empty( $room['check_out'] ) ) {
continue;
}
// Make sure room_id is an integer
$room_id = is_array( $room['room'] ) ? $room['room'][0] : $room['room'];
$selected_rooms[] = array(
'room_id' => (int) $room_id,
'check_in' => date( "Y-m-d", strtotime( $room['check_in'] ) ),
'check_out' => date( "Y-m-d", strtotime( $room['check_out'] ) ),
);
}
}
// Check each selected room
foreach ( $selected_rooms as $room ) {
$room_id = $room['room_id'];
$check_in = $room['check_in'];
$check_out = $room['check_out'];
// Get a list of disabled days for this room
$disabled_dates = dates_disable( $room_id );
// Check check-in date
if ( in_array( $check_in, $disabled_dates ) ) {
$validate = false;
break;
}
// Check check-out date
if ( in_array( $check_out, $disabled_dates ) ) {
$validate = false;
break;
}
// Check the days between check-in and check-out
$dates = get_dates_between( $check_in, $check_out );
foreach ( $dates as $date ) {
if ( in_array( $date, $disabled_dates ) ) {
$validate = false;
break 2;
}
}
}
return $validate;
}, 10, 2 );
Explanation:
function enqueue_scripts() {
// Register and enqueue script
wp_enqueue_script( 'jquery-ui-datepicker' );
wp_enqueue_script( 'booking-js', get_template_directory_uri() . '/js/booking.js', array( 'jquery', 'jquery-ui-datepicker' ), '1.0', true );
// Pass data into JavaScript using wp_add_inline_script()
$ajax_data = array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'booking_nonce' ),
);
$inline_script = 'var booking_ajax = ' . wp_json_encode( $ajax_data ) . ';';
wp_add_inline_script( 'booking-js', $inline_script, 'before' );
// Passing room data into JavaScript
$rooms = get_posts( array(
'post_type' => 'room',
'posts_per_page' => -1,
) );
$rooms_data = array();
foreach ( $rooms as $room ) {
// Get room rates from Meta Box
$room_price = rwmb_get_value( 'price', array(), $room->ID );
if ( ! $room_price ) {
$room_price = 0;
}
$rooms_data[] = array(
'id' => $room->ID,
'title' => $room->post_title,
'price' => $room_price,
);
}
$rooms_script = 'var rooms_data = ' . wp_json_encode( $rooms_data ) . ';';
wp_add_inline_script( 'booking-js', $rooms_script, 'before' );
// Pass current_room_id if available
$current_room_id = get_the_ID();
if ( $current_room_id ) {
$current_room_script = 'var current_room_id = ' . wp_json_encode( $current_room_id ) . ';';
wp_add_inline_script( 'booking-js', $current_room_script, 'before' );
}
}
add_action( 'wp_enqueue_scripts', 'enqueue_scripts' );
These codes prepare everything the booking form needs on the frontend. They load the JavaScript file, and pass important data like AJAX settings, room prices, and the current room ID. Thanks to that, the booking form can automatically handle pricing, dates, and calculations in real time.
In here:
wp_enqueue_script( 'jquery-ui-datepicker' ); wp_enqueue_script( 'booking-js', get_template_directory_uri() . '/js/booking.js', array( 'jquery', 'jquery-ui-datepicker' ), '1.0', true );
This part loads the datepicker library and the custom booking.js file that handles booking calculations and interactions.
$ajax_data = array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'booking_nonce' ),
);
$inline_script = 'var booking_ajax = ' . wp_json_encode($ajax_data) . ';';
wp_add_inline_script('booking-js', $inline_script, 'before');
This section sends AJAX settings to JavaScript, including the admin URL and a security nonce, so the script can safely request disabled dates.
We collect all rooms with their IDs, titles, and prices, then send them as rooms_data to JavaScript. This lets the booking form display the correct price when a room is selected.
$rooms = get_posts(array(
'post_type' => 'room',
'posts_per_page' => -1,
) );
$rooms_data = array();
foreach ( $rooms as $room ) {
// Get room rates from Meta Box
$room_price = rwmb_get_value( 'price', array(), $room->ID );
if ( ! $room_price ) {
$room_price = 0;
}
$rooms_data[] = array(
'id' => $room->ID,
'title' => $room->post_title,
'price' => $room_price,
);
}
$rooms_script = 'var rooms_data = ' . wp_json_encode( $rooms_data ) . ';';
wp_add_inline_script( 'booking-js', $rooms_script, 'before' );
If we are on a single room page, this part below sends the current room ID to JavaScript, so the booking form will automatically pre-select that room.
$current_room_id = get_the_ID();
if ($current_room_id) {
$current_room_script = 'var current_room_id = ' . wp_json_encode($current_room_id) . ';';
wp_add_inline_script('booking-js', $current_room_script, 'before');
}
Next, we use the function that creates a list of dates between check-in and check-out. Each day is added one by one to check room availability.
function get_dates_between($start_date, $end_date) {
$dates = array();
$current = strtotime($start_date);
$end = strtotime($end_date);
while ($current <= $end) {
$dates[] = date('Y-m-d', $current);
$current = strtotime('+1 day', $current);
}
return $dates;
}
These following codes are used to validate booking data when users submit the booking form on the frontend. It makes sure no one can book a room on dates that are already disabled.
add_filter( 'rwmb_frontend_validate', function( $validate, $config ) {
if ( 'booking-fields' !== $config['id'] ) {
return $validate;
}
// Get all selected rooms in the form
$selected_rooms = array();
if ( isset( $_POST['group_booking'] ) && is_array( $_POST['group_booking'] ) ) {
foreach ( $_POST['group_booking'] as $index => $room ) {
if ( empty( $room['room'] ) || empty( $room['check_in'] ) || empty( $room['check_out'] ) ) {
continue;
}
// Make sure room_id is an integer
$room_id = is_array( $room['room'] ) ? $room['room'][0] : $room['room'];
$selected_rooms[] = array(
'room_id' => (int) $room_id,
'check_in' => date( "Y-m-d", strtotime( $room['check_in'] ) ),
'check_out' => date( "Y-m-d", strtotime( $room['check_out'] ) ),
);
}
}
// Check each selected room
foreach ( $selected_rooms as $room ) {
$room_id = $room['room_id'];
$check_in = $room['check_in'];
$check_out = $room['check_out'];
// Get a list of disabled days for this room
$disabled_dates = dates_disable( $room_id );
// Check check-in date
if ( in_array( $check_in, $disabled_dates ) ) {
$validate = false;
break;
}
// Check check-out date
if ( in_array( $check_out, $disabled_dates ) ) {
$validate = false;
break;
}
// Check the days between check-in and check-out
$dates = get_dates_between( $check_in, $check_out );
foreach ( $dates as $date ) {
if ( in_array( $date, $disabled_dates ) ) {
$validate = false;
break 2;
}
}
}
return $validate;
}, 10, 2 );
In particularly:
if ( 'booking-fields' !== $config['id'] ) {
return $validate;
}
This part hooks into the Meta Box validation filter. It runs only for our booking form, identified by booking fields.
$selected_rooms = array();
if ( isset( $_POST['group_booking'] ) && is_array( $_POST['group_booking'] ) ) {
foreach ( $_POST['group_booking'] as $index => $room ) {
if ( empty( $room['room'] ) || empty( $room['check_in'] ) || empty( $room['check_out'] ) ) {
continue;
}
// Make sure room_id is an integer
$room_id = is_array( $room['room'] ) ? $room['room'][0] : $room['room'];
$selected_rooms[] = array(
'room_id' => (int) $room_id,
'check_in' => date( "Y-m-d", strtotime( $room['check_in'] ) ),
'check_out' => date( "Y-m-d", strtotime( $room['check_out'] ) ),
);
}
}
This block collects all rooms that the user selected, along with their check-in and check-out dates, and formats them for validation.
foreach ( $selected_rooms as $room ) {
$room_id = $room['room_id'];
$check_in = $room['check_in'];
$check_out = $room['check_out'];
// Get a list of disabled days for this room
$disabled_dates = dates_disable( $room_id );
// Check check-in date
if ( in_array( $check_in, $disabled_dates ) ) {
$validate = false;
break;
}
// Check check-out date
if ( in_array( $check_out, $disabled_dates ) ) {
$validate = false;
break;
}
// Check the days between check-in and check-out
$dates = get_dates_between( $check_in, $check_out );
foreach ( $dates as $date ) {
if ( in_array( $date, $disabled_dates ) ) {
$validate = false;
break 2;
}
}
}
This part loads all disabled dates for each selected room, using the dates_disable function.
This one below is to check if either the check-in or check-out day is already booked. If yes, validation fails immediately.
// Check check-in date
if ( in_array( $check_in, $disabled_dates ) ) {
$validate = false;
break;
}
// Check check-out date
if ( in_array( $check_out, $disabled_dates ) ) {
$validate = false;
break;
}
Finally, this section checks all the days between check-in and check-out. If any of them overlap with disabled dates, the booking is rejected.
$dates = get_dates_between( $check_in, $check_out );
foreach ( $dates as $date ) {
if ( in_array( $date, $disabled_dates ) ) {
$validate = false;
break 2;
}
}
That’s it for creating the functions. All the code has been uploaded to GitHub, so you can check it out there.
Now, let’s move to the frontend and test if those functions work properly. You can now make a booking successfully, and also verify it from the back end.

Final Words
You have finished all the booking pages and forms for both internal and external use already. We will continue to the next post with the booking management page. This will help the hotel owner have an overall view about the stock. Wait for me in the next part.
- How to Build a Hotel Booking Website Using Meta Box - P1
- How to Build a Hotel Booking Website Using Meta Box - P2 - Booking Page in Backend
- How to Build a Hotel Booking Website Using Meta Box – P3 – Booking Page for Customer
- How to Build a Hotel Booking Website Using Meta Box - P4 - Booking Management Page
- Author Bio
- Better 404 Page
- Blogs for Developers
- Building a Simple Listing Website with Filters
- Building an Event Website
- Building Forms with MB Frontend Submission
- Coding
- Create a Chronological Timeline
- Custom Fields Fundamentals
- Design Patterns
- Displaying Posts with Filters
- Download and Preview Buttons
- Dynamic Banners
- Dynamic Landing Page
- FAQs Page
- Featured Products
- Filter Posts by Relationships
- Full Site Editing
- Google Fonts
- Gutenberg
- Hotel Booking
- Latest Products
- Logo Carousel
- MB Builder Applications
- MB Group Applications
- MB Views Applications
- Most Viewed Posts
- Opening Hours
- OTA Website
- Pricing Table Page
- Product Page
- Product Variations
- Querying and Showing Posts by Custom Fields
- Recipe
- Related Posts via Relationship
- Restaurant Menus
- SEO Analysis
- Simple LMS
- Speed Up Website
- Taxonomy Thumbnails
- Team Members
- User Profile
- Video Gallery
How to Build a Hotel Booking Website Using Meta Box - P4 - Booking Management Page
How to Build a Hotel Booking Website Using Meta Box - P2 - Booking Page in Backend
How to Build a Hotel Booking Website Using Meta Box - P1
Hi thx for prowiding the hotel booking.
We would be very happy if we could get an opinion on transportation. especially about the transfer from the airport to the hotels.
We can't find much reference on this subject.
Thanks for your idea. We will consider creating some articles about transportation.