Front-end Guide
Extending Booknetic’s booking and payment flows using JavaScript hooks, the object API, and addon architecture.
Extending Booknetic’s booking and payment flows using JavaScript hooks, the object API, and addon architecture.
Booknetic’s frontend layer gives developers a flexible way to extend the booking experience, integrate custom UI, modify request data, and build advanced addons without changing the core plugin directly.
This guide covers the full frontend extension layer, including the booking panel your customers use, the admin panel JavaScript structure, the client-side hook system, frontend architecture, payment flow integration, styling rules, and file structure references.
Booknetic’s frontend is built with a lightweight and familiar stack:
| Technology | Usage |
|---|---|
| jQuery 3.3.1+ | Core JavaScript framework |
| Vanilla JavaScript (ES6) | Supplementary logic |
| Bootstrap 4.x | CSS framework |
| Select2 | Enhanced dropdowns |
| Flatpickr | Date picker |
| intlTelInput | International phone input |
| NiceScroll | Custom scrollbar |
Booknetic does not use a build tool. There is no webpack, no gulp, and no compilation step. JavaScript and CSS files are loaded directly as they are, which means you can add or edit files without any asset build process.
Booknetic includes a client-side hook system inspired by WordPress hooks. This is the main extension mechanism for frontend addons and custom booking panel behavior.
bookneticHooks ObjectThe global window.bookneticHooks object provides four core methods:
// Register a filter (modifies and returns a value)
bookneticHooks.addFilter(key, callback, uniqueId);
// Execute a filter (passes value through all registered callbacks)
let result = bookneticHooks.doFilter(key, initialValue, ...extraArgs);
// Register an action (side-effect, no return value needed)
bookneticHooks.addAction(key, callback, uniqueId);
// Execute an action
bookneticHooks.doAction(key, ...params);
Actions and filters have different purposes:
undefined.The optional uniqueId argument makes it possible to later replace or remove a specific hook by calling addFilter(key, null, uniqueId).
All hook keys are automatically converted to lowercase internally. That means booking_panel_loaded, Booking_Panel_Loaded, and BOOKING_PANEL_LOADED all point to the same hook.
For consistency, use snake_case naming.
Actions let you react to important lifecycle events inside the booking panel, AJAX system, payment flow, and customer panel.
booking_panel_loadedFired once after the booking panel is fully initialized. This is the main entry point for your addon JavaScript. Use it to bind event listeners and prepare custom UI.
Parameters:
| Parameter | Type | Description |
|---|---|---|
booknetic | object | The booking panel instance |
bookneticHooks.addAction('booking_panel_loaded', function (booknetic) {
let panel = booknetic.panel_js; // jQuery element for this panel
// Bind event listeners using delegation
panel.on('click', '.my_addon_button', function () {
// Handle click
});
});
A single page can contain multiple booking panels. Each panel fires its own booking_panel_loaded event and has its own booknetic instance. Always use booknetic.panel_js for DOM queries instead of global selectors.
before_step_loadingFired just before a step transition starts, before the AJAX request is sent.
Parameters:
| Parameter | Type | Description |
|---|---|---|
booknetic | object | The booking panel instance |
next_step | string | The step ID about to be loaded |
current_step | string | The step ID being left |
bookneticHooks.addAction('before_step_loading', function (booknetic, nextStep, currentStep) {
console.log(`Navigating from ${currentStep} to ${nextStep}`);
});
start_step_loadingFired when the AJAX request for the next step starts.
Parameters:
| Parameter | Type | Description |
|---|---|---|
booknetic | object | The booking panel instance |
next_step | string | The step being loaded |
current_step | string | The previous step |
loaded_stepFired after a step’s content is loaded and rendered. Use this to enhance or extend the UI of a specific step.
Parameters:
| Parameter | Type | Description |
|---|---|---|
booknetic | object | The booking panel instance |
step_id | string | The ID of the loaded step |
current_step | string | The previous step ID |
result | object | The AJAX response data |
bookneticHooks.addAction('loaded_step', function (booknetic, stepId, currentStep, result) {
if (stepId !== 'confirm_details') return;
// Inject a coupon input into the confirmation step
let html = '<div class="my_coupon_section">'
+ '<input type="text" placeholder="' + booknetic.__('enter_coupon') + '">'
+ '<button class="my_apply_btn">' + booknetic.__('apply') + '</button>'
+ '</div>';
booknetic.panel_js.find('.booknetic_confirm_details').append(html);
});
loaded_step_{step_id}Dynamic version of loaded_step. This hook only fires for the specified step, so you do not need a manual if check.
Available step IDs:
location, staff, service, service_extras, date_time, recurring_info, information, cart, confirm_details
Parameters:
| Parameter | Type | Description |
|---|---|---|
booknetic | object | The booking panel instance |
result | object | The AJAX response data |
bookneticHooks.addAction('loaded_step_date_time', function (booknetic, result) {
// Runs only when the Date & Time step is loaded
// Customize calendar behavior here
});
loaded_cached_stepFired when a previously visited step is loaded from cache.
Parameters:
| Parameter | Type | Description |
|---|---|---|
booknetic | object | The booking panel instance |
step_id | string | The step loaded from cache |
step_end_{step_id}Fired after a step passes validation and the user is about to move forward. This is useful when you want to react to a completed step before the next one loads.
Parameters:
| Parameter | Type | Description |
|---|---|---|
booknetic | object | The booking panel instance |
bookneticHooks.addAction('step_end_service', function (booknetic) {
// The user has selected a service and is moving forward
let serviceId = booknetic.getSelected.service();
console.log('Selected service:', serviceId);
});
step_loaded_with_errorFired when a step fails to load because the server returned an error.
Parameters:
| Parameter | Type | Description |
|---|---|---|
booknetic | object | The booking panel instance |
next_step | string | The step that failed to load |
current_step | string | The step being returned to |
result | object | The error response |
ajax_before_{action}Fired before an AJAX request is sent. {action} is the AJAX action name, such as get_data, confirm, or check_customer_exist.
Parameters:
| Parameter | Type | Description |
|---|---|---|
params | object | The request parameters |
booknetic | object | The booking panel instance |
bookneticHooks.addAction('ajax_before_confirm', function (params, booknetic) {
// Add a loading indicator, disable buttons, etc.
console.log('About to confirm appointment...');
});
ajax_after_{action}_successFired after a successful AJAX response.
Parameters:
| Parameter | Type | Description |
|---|---|---|
booknetic | object | The booking panel instance |
params | object | The original request parameters |
result | object | The server response |
bookneticHooks.addAction('ajax_after_get_data_success', function (booknetic, params, result) {
// Step data loaded successfully
});
ajax_after_{action}_errorFired after a failed AJAX response.
Parameters:
| Parameter | Type | Description |
|---|---|---|
booknetic | object | The booking panel instance |
params | object | The original request parameters |
result | object | The error response, which may be undefined on network errors |
before_processing_paymentFired before the payment process starts. Payment gateway addons use this hook to initialize their flow, such as opening a popup window.
Parameters:
| Parameter | Type | Description |
|---|---|---|
payment_method | string | The selected payment method, for example 'local', 'stripe', or 'paypal' |
data | object | Payment data returned from the server, including tokens, URLs, and related values |
bookneticHooks.addAction('before_processing_payment', function (paymentMethod, data) {
if (paymentMethod !== 'my_gateway') return;
// Open a popup window for the payment gateway
myPaymentWindow = window.open('', 'booknetic_payment_window', 'width=1000,height=700');
});
after_processing_paymentFired after the payment process has been initiated. This is typically used to redirect the popup to the payment gateway page.
Parameters:
| Parameter | Type | Description |
|---|---|---|
payment_method | string | The payment method |
status | boolean | Whether payment initiation succeeded |
data | object | Payment response data |
bookneticHooks.addAction('after_processing_payment', function (paymentMethod, status, data) {
if (paymentMethod !== 'my_gateway') return;
if (!status) {
myPaymentWindow.close();
return;
}
// Redirect the popup to the gateway's checkout page
myPaymentWindow.location.href = data['url'];
});
payment_completedFired when the payment process finishes successfully.
Parameters:
| Parameter | Type | Description |
|---|---|---|
status | boolean | Payment success status |
paymentData | object | Payment result data |
payment_completed_deprecatedLegacy hook kept for backward compatibility. Use payment_completed instead.
Parameters:
| Parameter | Type | Description |
|---|---|---|
booknetic | object | The booking panel instance |
payment_errorFired when a payment attempt fails.
Parameters:
| Parameter | Type | Description |
|---|---|---|
booknetic | object | The booking panel instance |
booking_finished_successfullyFired when the full booking flow is completed successfully and the finish screen is shown.
Parameters:
| Parameter | Type | Description |
|---|---|---|
booknetic | object | The booking panel instance |
bookneticHooks.addAction('booking_finished_successfully', function (booknetic) {
// Trigger conversion tracking, analytics event, etc.
if (typeof gtag !== 'undefined') {
gtag('event', 'booking_completed', {
appointment_id: booknetic.appointmentId
});
}
});
customer_panel_loadedFired when the customer panel is loaded. This is the interface where existing customers manage their bookings.
Parameters: None
bookneticHooks.addAction('customer_panel_loaded', function () {
// Add a custom tab to the customer panel
let tabHtml = '<li class="my_addon_tab" data-tab="my_tab">'
+ '<span>My Custom Tab</span></li>';
$('.booknetic_cp_tabs').append(tabHtml);
$(document).on('click', '.my_addon_tab', function () {
booknetic.ajax('my_addon_get_tab_content', {}, function (result) {
$('.booknetic_cp_content').html(result.html);
});
});
});
Filters allow you to modify outgoing request data, step validation results, cart items, and rendered frontend HTML before Booknetic uses them.
ajaxFilters all outgoing AJAX request parameters. This hook runs for every AJAX request made by the booking panel.
Parameters:
| Parameter | Type | Description |
|---|---|---|
params | object | The request parameters |
booknetic | object | The booking panel instance |
Returns: object — The modified parameters
bookneticHooks.addFilter('ajax', function (params, booknetic) {
// Add a custom parameter to every AJAX request
params.my_tracking_id = 'abc123';
return params;
});
ajax_{action}Filters parameters for one specific AJAX action only. For example, ajax_confirm affects only the confirmation request.
Parameters:
| Parameter | Type | Description |
|---|---|---|
params | object | The request parameters |
booknetic | object | The booking panel instance |
Returns: object
bookneticHooks.addFilter('ajax_confirm', function (params, booknetic) {
// Add extra data only to the confirmation request
params.special_note = document.getElementById('my_note_field').value;
return params;
});
appointment_ajax_dataFilters the final FormData sent when the appointment is confirmed. This is the last point where you can change submission data before it reaches the backend.
Parameters:
| Parameter | Type | Description |
|---|---|---|
data | FormData | The appointment submission data |
booknetic | object | The booking panel instance |
Returns: FormData
bookneticHooks.addFilter('appointment_ajax_data', function (formData, booknetic) {
// Append custom form field data
formData.append('coupon_code', document.getElementById('my_coupon_input').value);
formData.append('gift_card_code', document.getElementById('my_gift_card_input').value);
return formData;
});
step_validation_{step_id}Validates a step before the user can continue. Return { status: false, errorMsg: '...' } to block navigation.
Available step IDs:
location, staff, service, service_extras, date_time, recurring_info, information, cart, confirm_details
Parameters:
| Parameter | Type | Description |
|---|---|---|
result | object | { status: true, errorMsg: '' } |
booknetic | object | The booking panel instance |
Returns: object — { status: boolean, errorMsg: string }
bookneticHooks.addFilter('step_validation_information', function (result, booknetic) {
let customField = booknetic.panel_js.find('#my_required_field').val();
if (!customField) {
return {
status: false,
errorMsg: booknetic.__('my_field_required')
};
}
return result;
});
load_step_{step_id}Controls whether a step should load. Return false to prevent the step from loading. This is useful when you need to show a modal or collect extra input first.
Parameters:
| Parameter | Type | Description |
|---|---|---|
booknetic | object | The booking panel instance |
Returns: boolean
bookneticHooks.addFilter('load_step_date_time', function (booknetic) {
// Show a custom duration picker modal before proceeding to date/time
if (!durationSelected) {
showDurationModal(booknetic);
return false; // Block step loading
}
return true; // Allow step loading (default)
});
bkntc_cartFilters the cart item data before it is stored in the cart array.
Parameters:
| Parameter | Type | Description |
|---|---|---|
cart_item | object | The cart item data object |
booknetic | object | The booking panel instance |
Returns: object
bookneticHooks.addFilter('bkntc_cart', function (cartItem, booknetic) {
// Add custom data to the cart item
cartItem.my_addon_data = {
coupon_code: mySelectedCoupon,
discount: myCalculatedDiscount
};
return cartItem;
});
bkntc_date_time_loadFilters the HTML of each time slot before it is rendered in the Date & Time step.
Parameters:
| Parameter | Type | Description |
|---|---|---|
html | string | The time slot HTML element |
time_obj | object | The time slot data, such as start_time, end_time, and other slot-related fields |
booknetic | object | The booking panel instance |
Returns: string
bookneticHooks.addFilter('bkntc_date_time_load', function (html, timeObj, booknetic) {
// Add a "Popular" badge to slots with high demand
if (timeObj.booking_count > 5) {
html = html.replace('</div>', '<span class="popular-badge">Popular</span></div>');
}
return html;
});
booknetic ObjectEach booking panel instance creates its own booknetic object. This acts as the panel’s main API and state container.
| Property | Type | Description |
|---|---|---|
panel_js | jQuery | jQuery reference to the root element of this panel |
cartArr | array | Cart items array |
cartCurrentIndex | number | Index of the cart item currently being edited |
calendarDateTimes | object | Available dates and time slots |
calendarYear | number | Currently displayed calendar year |
calendarMonth | number | Currently displayed calendar month |
paymentWindow | Window | Reference to the payment popup window |
paymentStatus | boolean | Payment success or failure state |
appointmentId | number | Created appointment ID after confirmation |
paymentId | number | Payment record ID |
booknetic.ajax(action, params, successCallback, loading, errorCallback, async)Sends an AJAX request to the Booknetic backend.
| Parameter | Type | Default | Description |
|---|---|---|---|
action | string|object | — | Action name. If it is a string, Booknetic prefixes it with bkntc_. If it is an object, it should be in {backend_action, frontend_action} format |
params | object|FormData | — | Request parameters |
successCallback | function | — | Callback triggered on success with (result) |
loading | boolean | true | Whether to show the loading spinner |
errorCallback | function | — | Callback triggered on error with (result) |
async | boolean | true | Whether the request is asynchronous |
booknetic.ajax('my_addon_action', {
param1: 'value1',
param2: 'value2'
}, function (result) {
// Success
console.log(result.data);
}, true, function (error) {
// Error
booknetic.toast(error.error_msg, 'error');
});
The action name is automatically prefixed with bkntc_. For example, 'my_addon_action' becomes bkntc_my_addon_action on the server.
booknetic.ajaxAsync(action, params, loading, async)Promise-based version of ajax(). It resolves with the result or rejects with the error.
try {
let result = await booknetic.ajaxAsync('my_action', { id: 123 }, true);
console.log(result.data);
} catch (error) {
booknetic.toast(error.error_msg, 'error');
}
booknetic.toast(message, type, duration)Displays a toast notification.
| Parameter | Type | Description |
|---|---|---|
message | string | Message text |
type | string | 'success', 'error', or 'warning' |
duration | number | Auto-dismiss time in milliseconds |
booknetic.__(key)Returns a localized string by key.
booknetic.__('select_service'); // → "Select a service" (or translated equivalent)
booknetic.getSelectedAn object containing getter methods for the current booking selections:
booknetic.getSelected.location(); // Selected location ID
booknetic.getSelected.service(); // Selected service ID
booknetic.getSelected.staff(); // Selected staff ID
booknetic.getSelected.serviceExtras(); // [{extra, quantity}, ...]
booknetic.getSelected.date(); // 'YYYY-MM-DD'
booknetic.getSelected.time(); // 'HH:MM'
booknetic.getSelected.formData(); // {email, name, surname, phone}
booknetic.getSelected.customerId(); // Customer ID (if existing customer)
booknetic.getSelected.paymentMethod(); // Payment method key
booknetic.getSelected.paymentDepositFullAmount(); // 'deposit' or 'full_amount'
// Recurring appointments
booknetic.getSelected.recurringStartDate();
booknetic.getSelected.recurringEndDate();
booknetic.getSelected.recurringTimesArr();
booknetic.stepManagerThe step navigation controller:
booknetic.stepManager.goForward(); // Navigate to the next step
booknetic.stepManager.goBack(); // Navigate to the previous step
booknetic.stepManager.loadStep(stepId); // Load a specific step
booknetic.stepManager.getNextStep(); // Get the next step ID
booknetic.stepManager.getPrevStep(); // Get the previous step ID
booknetic.stepManager.saveData(); // Save current selections to cart
booknetic.throttle(fn, delay); // Returns throttled function (default 500ms)
booknetic.debounce(fn, delay); // Returns debounced function (default 300ms)
booknetic.htmlspecialchars(string); // HTML-encode a string
booknetic.htmlspecialchars_decode(string); // HTML-decode a string
booknetic.sanitizeHTML(node); // Remove <script> tags from a DOM node
booknetic.isMobileView(); // true if viewport ≤ 1000px
booknetic.zeroPad(number, length); // Zero-pad a number (e.g. zeroPad(42, 4) → "0042")
booknetic.loading(state); // Show (1) or hide (0) the loading spinner
Understanding the booking panel architecture makes addon development much easier, especially when you need to hook into steps, cache behavior, or AJAX flow.
The booking panel follows a step-based wizard pattern:
Location → Staff → Service → Service Extras → Date/Time → [Recurring Info] → Information → Cart → Confirmation → Finish
Steps can be hidden or reordered from the admin settings. Navigation, caching, and validation are handled automatically by the step manager.
When a page containing the [booknetic] shortcode loads, the following happens:
bookneticInitBookingPage() for each panel on the page.bkntc_get_data.booking_panel_loaded action is fired.// Simplified initialization flow
jQuery(".booknetic_appointment").each((i, element) => {
window.bookneticInitBookingPage(element);
});
<div class="booknetic_appointment" id="booknetic_theme_{themeId}">
<div class="booknetic_appointment_steps">
<!-- Step sidebar / progress indicator -->
</div>
<div class="booknetic_appointment_container">
<div class="booknetic_appointment_container_header">
<!-- Current step title + cart icon -->
</div>
<div class="booknetic_appointment_container_body">
<!-- Dynamic step content (loaded via AJAX) -->
</div>
<div class="booknetic_appointment_container_footer">
<!-- Back, Next, Confirm buttons -->
</div>
</div>
<div class="booknetic_appointment_finished">
<!-- Success / error screen -->
</div>
</div>
Each step is loaded through AJAX via the bkntc_get_data endpoint. The backend returns HTML, which is inserted into .booknetic_appointment_container_body.
To improve performance, step content is cached in booknetic.cartHTMLBody, so going back to a previous step does not trigger another request.
Every AJAX request follows this pipeline:
1. Action is prefixed: 'bkntc_' + action
2. tenant_id is appended
3. Hook: bookneticHooks.doAction('ajax_before_' + action, params, booknetic)
4. Hook: params = bookneticHooks.doFilter('ajax', params, booknetic)
5. Hook: params = bookneticHooks.doFilter('ajax_' + action, params, booknetic)
6. $.ajax() POST → admin-ajax.php
7. On success: callback(result) + doAction('ajax_after_{action}_success', ...)
8. On error: callback(result) + doAction('ajax_after_{action}_error', ...)
All Booknetic AJAX responses use the following structure:
{
status: 'ok' | 'error',
error_msg: 'Error message', // Present on errors
errors: [{ field, message }], // Field-level validation errors
html: '<div>...</div>', // Step HTML content
data: { ... } // Additional data
}
A payment gateway addon must implement three key hooks to integrate smoothly into the booking flow.
(function () {
let paymentWindow;
let paymentData;
// Step 1: Open the payment window before processing starts
bookneticHooks.addAction('before_processing_payment', function (paymentMethod, data) {
if (paymentMethod !== 'my_gateway') return;
paymentData = data;
paymentWindow = window.open('', 'booknetic_payment_window', 'width=1000,height=700');
});
// Step 2: Redirect the popup to the gateway after the server creates the payment
bookneticHooks.addAction('after_processing_payment', function (paymentMethod, status, data) {
if (paymentMethod !== 'my_gateway') return;
if (!status) {
paymentWindow.close();
return;
}
// Redirect to the gateway's checkout page
paymentWindow.location.href = data['url'];
});
})();
On the gateway callback page, after the customer completes payment, the popup should call one of the following:
// In the callback view loaded inside the popup:
window.opener.bookneticPaymentStatus(true); // Payment successful
// or
window.opener.bookneticPaymentStatus(false); // Payment failed
The main booking panel watches the popup. When it closes, it checks the payment result and then displays either the success screen or an error state.
1. User clicks "Confirm"
2. confirmAppointment() → AJAX: bkntc_confirm
3. Server creates appointment, returns payment URL
4. doAction('before_processing_payment') → Gateway opens popup
5. doAction('after_processing_payment') → Popup redirected to gateway URL
6. Customer completes payment in popup
7. Gateway callback calls window.opener.bookneticPaymentStatus(true)
8. Popup closes, main window detects it
9. doAction('payment_completed')
10. doAction('booking_finished_successfully') → Success screen shown
Booknetic’s admin side has its own JavaScript architecture and its own booknetic object.
The main admin object is defined in app/Backend/Base/assets/js/booknetic.js:
var booknetic = {
// Localization
__(key) — Get a translated string
// UI
loadModal(modal, parameters, options) — Load a modal via AJAX
modalHide($modal) — Close a modal
toast(title, type, duration) — Show a toast notification
confirm(text, bg, icon, okCallback, okText, cancelText) — Show confirmation dialog
// AJAX
ajax(action, params, callback) — Make an AJAX request
// DataTable
dataTable.init(container) — Initialize a data table
dataTable.reload() — Reload table data
dataTable.doAction(action, ids, params, callback) — Execute a table action
};
Each admin module usually has its own JavaScript file and follows a consistent interaction pattern:
$(document).ready(function () {
// Add button
$(document).on('click', '#addBtn', function () {
booknetic.loadModal('add_new', {});
});
// DataTable edit action
booknetic.dataTable.actionCallbacks['edit'] = function (ids) {
booknetic.loadModal('add_new', { id: ids[0] });
};
// DataTable delete action
booknetic.dataTable.actionCallbacks['delete'] = function (ids) {
booknetic.confirm(
booknetic.__('are_you_sure_want_to_delete'),
'danger', 'trash',
function () {
booknetic.dataTable.doAction('delete', ids, {}, function () {
booknetic.toast(booknetic.__('deleted'), 'success', 2000);
});
}
);
};
});
$(document).on('click', '#saveBtnModal', function () {
let name = $('#input_name').val();
if (!name) {
booknetic.toast(booknetic.__('name_required'), 'error');
return;
}
booknetic.ajax('save', {
id: $('#input_id').val(),
name: name
}, function (result) {
booknetic.toast(booknetic.__('saved'), 'success', 2000);
booknetic.modalHide($('.modal'));
booknetic.dataTable.reload();
});
});
A frontend addon usually needs to load assets, register localized strings, hook into lifecycle events, inject UI, send custom data, and sometimes validate steps.
Register your addon scripts and styles through PHP hooks:
// In your AddonLoader's initFrontend() method:
public function initFrontend()
{
// Load assets on the booking panel
add_filter('bkntc_booking_panel_assets', function ($assets) {
$assets[] = ['type' => 'js', 'url' => self::loadAsset('assets/js/init.js')];
$assets[] = ['type' => 'css', 'url' => self::loadAsset('assets/css/style.css')];
return $assets;
});
}
// In your AddonLoader's initBackend() method:
public function initBackend()
{
// Load assets on admin pages
add_action('bkntc_enqueue_assets', function ($module, $action, $viewPath) {
if ($module === 'appointments') {
wp_enqueue_script('my-addon-admin', self::loadAsset('assets/js/admin.js'), ['jquery']);
}
}, 10, 3);
}
You can pass translatable strings from PHP into JavaScript:
// PHP: Register strings
add_filter('bkntc_frontend_localization', function ($strings) {
$strings['my_addon_label'] = bkntc__('My Label');
$strings['my_addon_error'] = bkntc__('Something went wrong');
return $strings;
});
// JavaScript: Use the strings
booknetic.__('my_addon_label'); // → "My Label"
booknetic.__('my_addon_error'); // → "Something went wrong"
A typical addon init.js can follow this structure:
// Panel loaded — bind events
bookneticHooks.addAction('booking_panel_loaded', function (booknetic) {
let panel = booknetic.panel_js;
// Bind DOM events using delegation
panel.on('click', '.my_addon_apply_btn', function () {
let code = panel.find('#my_addon_input').val();
booknetic.ajax('my_addon_validate', { code: code }, function (result) {
// Update UI with result
panel.find('.my_addon_status').text(result.data.message);
}, true, function (error) {
booknetic.toast(error.error_msg, 'error');
});
});
});
// Inject UI into a specific step
bookneticHooks.addAction('loaded_step_confirm_details', function (booknetic, result) {
let html = '<div class="my_addon_section">'
+ '<input type="text" id="my_addon_input" placeholder="' + booknetic.__('my_placeholder') + '">'
+ '<button class="my_addon_apply_btn">' + booknetic.__('apply') + '</button>'
+ '</div>';
booknetic.panel_js.find('.booknetic_confirm_details').append(html);
});
// Append custom data to the appointment submission
bookneticHooks.addFilter('appointment_ajax_data', function (formData, booknetic) {
let myValue = booknetic.panel_js.find('#my_addon_input').val();
if (myValue) {
formData.append('my_addon_code', myValue);
}
return formData;
});
// Custom step validation
bookneticHooks.addFilter('step_validation_information', function (result, booknetic) {
let field = booknetic.panel_js.find('#my_required_field').val();
if (!field) {
return { status: false, errorMsg: booknetic.__('field_required') };
}
return result;
});
Booknetic frontend styles are scoped to reduce conflicts with WordPress themes and third-party styles.
Booking panel styles are isolated under .booknetic_appointment:
.booknetic_appointment * {
all: unset;
/* Properties are then explicitly re-set */
}
When writing custom styles, always keep them inside the booking panel scope:
.booknetic_appointment .my_addon_section {
padding: 15px;
border: 1px solid #e0e0e0;
border-radius: 8px;
margin-top: 10px;
}
| Prefix | Scope | Example |
|---|---|---|
booknetic_ | Core booking panel elements | .booknetic_service_card |
bkntc_ | Internal shorthand classes | .bkntc_service_list |
bkntcsaas_ | SaaS module elements | .bkntcsaas_signup_btn |
State modifiers use the _{state} suffix, such as .booknetic_card_selected and .booknetic_category_accordion_hidden.
| Class | Description |
|---|---|
.booknetic_appointment | Root container of the full booking panel |
.booknetic_appointment_steps | Left sidebar with step indicators |
.booknetic_appointment_container | Main content wrapper |
.booknetic_appointment_container_body | Dynamic step content area |
.booknetic_card | Selectable card element for location, staff, and similar items |
.booknetic_card_selected | Currently selected card |
.booknetic_service_card | Service selection card |
.booknetic_service_card_selected | Currently selected service |
.booknetic_calendar_rows | Calendar day grid |
.booknetic_calendar_days | Single calendar day |
.booknetic_time_element | Time slot element |
.booknetic_selected_time | Currently selected time slot |
.booknetic_appointment_finished | Success or completion screen |
The booking panel switches to mobile layout at 1000px viewport width.
booknetic.isMobileView(); // returns true when viewport ≤ 1000px
In mobile view:
RTL is detected automatically:
if ($('html').attr('dir') === 'rtl') {
$('body').addClass('rtl');
}
app/Frontend/assets/
├── js/
│ ├── booknetic.js # Core booking panel logic (~2400 lines)
│ ├── booknetic-popup.js # Modal/popup handler
│ ├── booknetic-signin.js # Customer sign-in
│ ├── booknetic-signup.js # Customer sign-up
│ ├── booknetic-forgot-password.js
│ └── steps/ # Step-specific JS files
│ ├── step_locations.js
│ ├── step_services.js
│ ├── step_staff.js
│ ├── step_service_extras.js
│ ├── step_date_time.js
│ ├── step_information.js
│ ├── step_cart.js
│ ├── step_confirm_details.js
│ └── step_recurring_info.js
├── css/
│ ├── booknetic.css # Main booking panel styles
│ └── bootstrap-booknetic.css # Bootstrap customization
└── icons/ # SVG icons (40+)
app/Frontend/view/booking_panel/ # Step PHP templates
├── locations.php
├── services.php
├── staff.php
├── service_extras.php
├── date_time.php
├── information.php
├── confirm.php
├── cart.php
└── booknetic.php # Main panel template
app/Backend/Base/assets/js/
├── booknetic.js # Admin panel core JS
└── generic-table.js # GenericTable class
app/Backend/[Module]/assets/ # Module-specific assets
├── js/
└── css/
Booknetic’s frontend developer layer gives you a practical extension system for the booking panel, customer panel, admin JavaScript, payment gateways, and UI rendering behavior.
| Hook Type | Count | Purpose |
|---|---|---|
| JS Actions | 18+ | React to lifecycle events such as step loading, payment completion, and booking success |
| JS Filters | 7+ | Modify request data, validation results, cart items, and rendered frontend output |
| Hook | Type | Common Use Case |
|---|---|---|
booking_panel_loaded | Action | Initialize addon and bind event listeners |
loaded_step / loaded_step_{id} | Action | Inject custom UI into a specific step |
step_end_{step_id} | Action | React when a step is completed |
before_processing_payment | Action | Open a payment gateway popup |
after_processing_payment | Action | Redirect the popup to the gateway checkout page |
booking_finished_successfully | Action | Conversion tracking and analytics |
step_validation_{step_id} | Filter | Add custom step validation |
appointment_ajax_data | Filter | Append custom data to the booking submission |
ajax / ajax_{action} | Filter | Modify AJAX request parameters |
bkntc_date_time_load | Filter | Customize time slot rendering |
load_step_{step_id} | Filter | Conditionally block step loading |
bkntc_cart | Filter | Modify cart item data |