View, filter, and manage all service appointments. Each appointment can contain one or more service details, assigned employees, and payment information.
| Filter Field | Description |
|---|---|
| Date Range | Filter by appointment date (from date – to date). |
| Branch | Filter by service branch/location. |
| Status | Filter by appointment status: Draft, New, Accepted, Success, Cancel. |
| Employee | Filter by assigned employee. |
| Customer | Search by customer name or phone number. |
| Status | Value | Description |
|---|---|---|
| Draft | -1 | Appointment created but not yet confirmed by customer or admin. |
| New | 1 | Customer has submitted the appointment; awaiting admin confirmation. |
| Accepted | 4 | Admin has accepted the appointment. Service will be performed as scheduled. |
| Success | 3 | Service has been completed successfully. |
| Cancel | 2 | Appointment has been cancelled by admin or customer. |
- Change Status — Move the appointment through allowed status transitions.
- Add History Note — Record internal notes or status change reasons in the appointment timeline.
- Edit Customer Info — Update the customer's name, phone, or notes linked to this appointment.
- View Payment — See linked payment details, amount, and payment channel.
- Contact Info Log — If the booking email matches an existing account but the submitted name/phone differs, the system logs a
contact_info_submittedhistory entry for review.
Manage your salon or spa locations. Each branch has its own set of services, employees, and booking schedules.
| Field | Required | Description |
|---|---|---|
| Name | Yes | Display name of the branch. |
| Slug | Yes | URL-friendly identifier. Used in the booking page URL. |
| Address | Optional | Physical address of the branch. |
| Phone | Optional | Contact phone number for the branch. |
| Image | Optional | Cover image displayed on the branch booking page. |
| Status | Yes | Active — Branch is bookable. Inactive — Branch is hidden from the booking flow. |
Create and manage the services offered at your branches. Services are grouped by category and linked to a branch.
| Field | Required | Description |
|---|---|---|
| Name | Yes | Service name displayed to customers. |
| Slug | Yes | URL-friendly identifier for the service. |
| Category | Yes | Service category, which is linked to a branch. |
| Price | Optional | Base price of the service. Enable "Price From" to display as a starting price range. |
| Duration | Optional | Estimated service duration in minutes. Used for scheduling time slots. |
| Image | Optional | Service thumbnail displayed on the booking page. |
| Status | Yes | Active — Service is bookable. Inactive — Hidden from customers. |
View all customers who have made bookings. Customers are user accounts created automatically during the booking flow.
| Column | Description |
|---|---|
| Name | Customer's full name. |
| Customer's email address used for account and booking notifications. | |
| Phone | Customer's phone number. |
| Guest | Indicates if the customer is a guest account (auto-created during booking, has not yet set their own password). |
| Total Appointments | Number of bookings this customer has made. |
| Created Date | Date the customer account was created. |
Overview charts and statistics for appointment activity, revenue, employee performance, and popular services.
| Chart | Description |
|---|---|
| Total Appointments | Total number of appointments over the selected date range, broken down by status. |
| Revenue | Total revenue from completed appointments over the selected date range. |
| Employee Performance | Revenue and appointment count per employee. Filter by date range to see individual performance. |
| Popular Services | Most frequently booked services, ranked by appointment count. |
| Time Slot Distribution | Booking frequency by time slot, useful for identifying peak hours. |
Create and manage discount coupons applied at booking checkout. Supports fixed-amount and percentage discounts, scoped to all bookings, specific customers, service categories, services, or branches.
| Filter | Description |
|---|---|
| Coupon Name | Partial search by coupon name. |
| Coupon Code | Exact or partial match on the code string entered by customers. |
| Status | Filter by Active or Inactive. |
| Field | Required | Description |
|---|---|---|
| Coupon Name | Required | Display name for internal management. |
| Coupon Code | Required | Unique code customers enter at checkout. Must be unique system-wide. Case-insensitive matching recommended. |
| Discount Type | Required |
Amount (fixed) — deduct a fixed amount from the total. Percent (%) — deduct a percentage of the applicable subtotal. |
| Discount Value | Required | The amount or percentage to deduct. Must be ≥ 0. |
| Max Discount Value | Optional | Only applies when Discount Type is Percent. Caps the maximum discount amount. Leave 0 for no cap. |
| Minimum Order Value | Optional | The minimum booking total required before this coupon can be applied. Leave 0 for no minimum. |
| Quantity | Required | Total number of times this coupon can be used across all customers. Set to 0 for unlimited uses. |
| Start Date | Optional | Date from which the coupon becomes valid. If not set, valid immediately upon activation. |
| End Date | Optional | Date after which the coupon expires. If not set, the coupon never expires. |
| Applicable Object | Required | Determines which bookings the discount applies to (see table below). |
| Thumbnail Image | Optional | Image shown in the customer-facing coupon list. |
| Note | Optional | Internal admin note — not shown to customers. |
| Status | Required | Active — coupon is usable. Inactive — coupon is disabled and rejected at checkout. |
| Scope | How the Discount is Calculated |
|---|---|
| All apply_to = 0 | Discount applied on the entire booking total. Any customer can use it. |
| Customer apply_to = 1 | Only specific customers (selected by name/account) can redeem. Discount applies to the full booking total. Requires the customer to be logged in. |
| Service Category apply_to = 2 | Discount calculated only on the subtotal of services belonging to the selected categories. Other services in the same booking are not discounted. |
| Service apply_to = 3 | Discount calculated only on the subtotal of the selected services (or their specific variants). Services not in the list are excluded from the discount base. |
| Branch apply_to = 4 | Discount applies to the entire booking total, but only when the customer books at one of the selected branches. |
When a customer enters a coupon code during booking, the system validates in order:
- Code exists & status is Active — invalid or inactive codes are rejected immediately.
- Applicable Object scope — if scoped to a specific customer, the customer must be logged in and match.
- Start Date / End Date — coupon must be within its validity window.
- Per-user usage — each customer (by user account or guest session) can only use the same coupon once.
- Total quantity — remaining uses must be > 0 (if quantity ≠ 0).
- Minimum order value — booking total must meet or exceed
min_order. - Discount calculation — discount computed on the applicable subtotal; capped at
max_valuefor percent type.
Track every coupon redemption — which customer used which code on which appointment, the discount amount, and the usage status.
| Field | Description |
|---|---|
| Coupon Code | The code that was redeemed. |
| Appointment | Link to the appointment this coupon was applied to. |
| Customer | The user account that applied the code (empty for anonymous/guest checkouts). |
| Total Before | Original booking total before the discount was applied. |
| Total After | Booking total after the discount. Discount value = Total Before − Total After. |
| Discount Type | 1 = Fixed amount, 2 = Percent. |
| Discount Value | The raw coupon value at time of use (amount or percent figure). |
| Status |
1 — Used: discount has been confirmed and applied. 2 — Pending: coupon reserved but appointment not yet finalized. 3 — Cancelled: appointment was cancelled; coupon returned to pool. -1 — Rolled back: system-level rollback (e.g., payment failure). |
-1), freeing that usage slot so the coupon quantity is restored.
The API Token module allows third-party applications to access booking data via a REST API. All endpoints require Sanctum token authentication and are subject to rate limiting.
-
Go to Admin → API Token.
-
Click "Create New API Client". Enter a client name, set the rate limit (requests per minute), and save.
-
Copy the generated token — it will only be shown once.
-
Use the token in the
Authorizationheader of every API request.
Include this header in every API request:
Authorization: Bearer {your_api_token}
Content-Type: application/json
{
"success": true,
"data": [ ... ],
"meta": {
"current_page": 1,
"last_page": 5,
"per_page": 15,
"total": 72
}
}
Error responses return "success": false with a "message" field and an appropriate HTTP status code (404, 422, 500).
429 Too Many Requests. Configure the limit when creating the API client.
Read and update appointment data via API.
List appointments with optional filters.
| Parameter | Type | Description |
|---|---|---|
| status | int | Filter by status: -1 Draft, 1 New, 2 Cancel, 3 Success, 4 Accepted. |
| date_from | date | Filter appointments on or after this date. Format: YYYY-MM-DD. |
| date_to | date | Filter appointments on or before this date. Format: YYYY-MM-DD. |
| branch_id | int | Filter by branch ID. |
| per_page | int | Items per page. Default: 15. Max: 100. |
| page | int | Page number. Default: 1. |
Get full detail of a single appointment including services, employees, and payment.
Response fields: id, code, status, statusText, dateAppointment, timeStart, timeEnd, totalPrice, note, branch, customer, details[] (service, employee, price, duration), payment, createdAt.
Update the status of an appointment. Only valid transitions are accepted.
// Request body
{
"status": 4, // required — target status value
"reason": "..." // optional — reason note for cancel
}
| Current Status | Allowed Transitions |
|---|---|
| Draft (-1) | → New (1) |
| New (1) | → Accepted (4) | Cancel (2) |
| Accepted (4) | → Success (3) | Cancel (2) |
Read branch (location) data.
List active branches. Only returns branches with status = Active.
| Parameter | Type | Description |
|---|---|---|
| search | string | Search by branch name. |
| per_page | int | Items per page. Default: 15. Max: 100. |
| page | int | Page number. |
Response fields: id, name, slug, address, phone, image, status, createdAt.
Get details of a single active branch by ID. Returns 404 if not found or inactive.
Read service data. Filter by branch to get only the services available at a specific location.
List active services. Pass branch_id to get all services available at a specific branch (bypasses pagination).
| Parameter | Type | Description |
|---|---|---|
| search | string | Search by service name. |
| branch_id | int | Return all services for this branch (no pagination). |
| per_page | int | Items per page. Default: 15. Max: 100. |
| page | int | Page number. |
Response fields: id, name, slug, image, price, duration, status, category (id, name).
Get details of a single active service by ID, including its category. Returns 404 if not found or inactive.
Read customer (user) data for customers who have at least one booking.
List customers with optional search.
| Parameter | Type | Description |
|---|---|---|
| search | string | Search by name, email, or phone number. |
| per_page | int | Items per page. Default: 15. Max: 100. |
| page | int | Page number. |
Response fields: id, name, email, phone, isGuest (boolean — true if the account was auto-created during booking and the user has not yet set a password), createdAt.
Get details of a single customer by ID. Returns 404 if not found.
isGuest: true) are created automatically when someone books without an existing account. They can set their own password via the Change Password page, at which point isGuest becomes false.
View, search, filter and manage all published posts.
The list page displays all posts in a table format, including: post title, author, category, published date, SEO score and status.
| Filter field | Description |
|---|---|
| Post title | Search by post title (partial match). |
| Slug | Search by the post's SEO-friendly URL. |
| Category | Filter posts by a specific category. |
| Created date | Filter by post date range (from date → to date). |
| Status | Filter by Active / Inactive / Trash. |
| Action | Description |
|---|---|
| Edit | Click the post title or pencil icon to go to the edit page. |
| Duplicate | Copy all post content to a new draft for editing. |
| Delete / Restore | Move the post to trash. Can be restored or permanently deleted from the Trash tab. |
| Bulk status change | Select multiple posts → choose action → Apply. |
| Export Excel | Export the post list to an .xlsx file (requires posts_exports permission). |
Create and publish a new post on the system.
| Field | Required | Description |
|---|---|---|
| Title | ✓ Required |
The post title, displayed
on the website interface and the
<title> tag.
|
| Slug (URL) | ✓ Required | SEO-friendly URL. Auto-generated from the title; can be edited manually. |
| Short description | Optional | Post excerpt, commonly used for listing pages and the meta description tag. |
| Content | ✓ Required | Full post content. Edited with a WYSIWYG editor. |
| Thumbnail | Optional | Thumbnail image displayed in listings and as the OG image when sharing on social media. |
| Category | Optional | Select one or more categories for the post. |
| Author | Optional | Select the author from the admin user list. Defaults to the logged-in user. |
| Language | Optional | Select the post language if the system runs in multi-language mode. |
| Featured | Optional | Mark the post as featured (highlighted) to prioritize its display. |
| Status | ✓ Required | Active = visible, Inactive = hidden from website. |
| Field | Description |
|---|---|
| SEO Title | A separate SEO title, different from the post title (if you want to optimize for a different keyword). |
| SEO Description | Meta description displayed in Google search results. Recommended: 150–160 characters. |
| SEO Keywords | SEO keywords for the post (has little impact on current rankings). |
-
Go to Posts → Add Post.
-
Enter the Title. The Slug will be auto-generated; edit if needed.
-
Write the Content in the editor. Upload images directly into the editor or via the Media Library.
-
Select a Category and upload a Thumbnail.
-
Fill in SEO Title and SEO Description in the SEO tab.
-
Set the status to Active and click Save.
Organize posts by topic or subject area.
| Field | Required | Description |
|---|---|---|
| Category name | ✓ Required | The display name of the category on the website. |
| Slug | ✓ Required |
Category URL. Auto-generated from the
name, e.g.:
tin-tuc.
|
| Parent category | Optional | Select a parent category to create a subcategory structure. |
| Description | Optional | Short description for the category, typically displayed on the category listing page. |
| Thumbnail | Optional | Thumbnail image representing the category. |
| Status | ✓ Required | Active to display the category on the website. |
Configure the display and behavior of the post module:
sidebar, comments, SEO schema, "read more" button and
default news page. Configuration is saved with
setting_name = 'post_setting'.
| Option | Default | Description |
|---|---|---|
| Show Sidebar | Yes |
Enable/disable sidebar widget on
the post detail page and category pages. Helper:
BlogHelper::isSidebarEnabled().
|
| Allow comments | Yes |
Enable/disable comments on
all posts. When disabled, the comment form
is completely hidden even if the Comment module is active. Helper:
BlogHelper::isCommentEnabled().
|
| Show Default Schema | Yes |
Automatically inject JSON-LD Schema.org
(Article /
NewsArticle) into the
<head> of the
post detail page to optimize SEO
and rich results. Helper:
BlogHelper::isSchemaEnnabled().
|
| "Read more" button | No |
Display a content truncation button — long posts are
cut after X characters with a "Read more" button to expand. Helper:
BlogHelper::isViewMoreEnnabled().
|
| Select news page | — |
Link to the Page that will display
the post list. The system uses this ID to build
the breadcrumb and hierarchical URL.
Helper:
BlogHelper::blogPageId().
|
The number of posts displayed on the list and category pages is configured at Settings → Reading Settings, field "Posts per page".
The Post module injects this field into the Reading
Settings page via the hook
BASE_FILTER_ADD_PAGINATION_CONFIG
with key post_paginate, default value 10. In code, retrieve it with:
$readingConfig['post_paginate'] ??
10.
View, search, filter and manage all static pages on the system.
The list page displays all static pages in a table format, including: page name, URL (slug), order, SEO score and status.
| Filter field | Description |
|---|---|
| Page name | Search by static page title (partial match). |
| Slug | Search by the page's SEO-friendly URL. |
| Created date | Filter by page creation date range (from date → to date). |
| Status | Filter by Active / Inactive / Trash. |
| Action | Description |
|---|---|
| Edit | Open the static page content edit form. |
| Delete | Move the page to trash. Can be restored or permanently deleted afterward. |
| View on site | Open the static page on the website in a new tab. |
Create a new static page or edit an existing page's content.
| Field | Required | Description |
|---|---|---|
| Title | ✓ Required |
Page name, displayed in menus,
breadcrumbs and the
<title> tag.
|
| Slug (URL) | ✓ Required |
SEO-friendly URL. Auto-generated from the title;
can be edited manually. Example:
gioi-thieu.
|
| Content | Optional | Full page content. Edited with a WYSIWYG editor. |
| Thumbnail | Optional | Thumbnail image displayed when sharing the page on social media (OG image). |
| Language | Optional | Select the page language if the system runs in multi-language mode. |
| Status | ✓ Required | Active — visible on website; Inactive — hidden from website. |
| SEO field | Description |
|---|---|
| SEO Title | Title displayed in Google search results. Recommended: under 60 characters. |
| Meta Description | Short description for Google. Affects click-through rate. Recommended: under 160 characters. |
| Meta Keywords | Keywords related to the page content (little effect on modern SEO). |
-
Go to Static Pages → Add New Page.
-
Enter the Title — a slug will be automatically generated.
-
Write the Content with the editor.
-
Switch to the SEO tab to fill in the SEO Title and Meta Description.
-
Select a Status and click Save.
Configure header, footer, and custom 404 page information. This page has 3 configuration tabs.
| Field | Required | Description |
|---|---|---|
| Header phone number | Optional | Hotline displayed on the top header bar. |
| Quick Search — Products | Optional | List of suggested products appearing in the quick search box in the header. |
| Quick Search — Articles | Optional | List of suggested articles in the quick search box. |
| Popular keywords | Optional | List of keywords + links displayed below the header search box. Each item includes: Keyword name and Link. |
| Field | Required | Description |
|---|---|---|
| Footer form | Optional | Select a registration form (from the Custom Forms feature) to display in the footer. |
| Company name | Optional | Business name displayed in the footer. |
| Company address | Optional | Headquarters address displayed in the footer. |
| Company phone number | Optional | Hotline displayed in the footer. |
| Company email | Optional | Contact email displayed in the footer. |
| DMCA Link / Image | Optional | Copyright protection badge: enter the DMCA badge link and image. |
| Copyright | Optional | Copyright line displayed at the bottom of the footer. Can be plain text or HTML format. |
| Field | Required | Description |
|---|---|---|
| 404 Title | Optional | Large title displayed on the 404 error page. |
| 404 Description | Optional | Short explanatory paragraph below the 404 title. |
| Redirect suggestion list | Optional | Suggested navigation items when the user visits a non-existent link. Each item includes: Title, Description, Button name, and Link. |
An overview of all configuration pages within the
Theme menu group (parent:
group_interface).
| Configuration item | Main function |
|---|---|
| Font, size, color & logo configuration | Set overall style: typography, primary color, header/footer display configuration by layout, logo and style for desktop/mobile. |
| Menu | Manage website navigation menu structure, arrange order, assign links, and configure menu display. |
| CSS | Enter custom CSS to override the theme interface per specific needs without editing the theme source directly. |
| Sidebar widgets | Manage widgets in the sidebar/footer area: add, arrange, and configure content blocks to display. |
Add custom CSS or JavaScript to the website without editing the theme files directly.
Write custom CSS in the editor area. The CSS will be automatically applied to the entire website.
- Change the interface without editing theme files.
- Change fonts, colors, spacing per project requirements.
- Show/hide specific elements.
Inject custom JavaScript. The JS will be automatically added to the end of the page.
- Add tracking code (Google Analytics, Facebook Pixel...).
- Integrate livechat, chatbot.
- Other custom scripts.
Configure basic website information: company name, email, phone number, address, description, and copyright. This information is used across many modules: email, footer, shortcode.
| Field | Type | Description |
|---|---|---|
| Company name | text | Brand / company name displayed on the website and in emails. |
| text | Website's representative email. Used in system email templates. | |
| Phone number | text | Primary contact phone number. |
| Hotline | text | Customer support Hotline number. |
| Address | text | Office / store address. |
| Description | textarea | Short description of the website / business. |
| Copyright | textarea | Copyright content displayed in the footer. |
setting_name = 'overview'. Can be
read in code using
getSiteName(),
getSiteDescription() or
getOption('overview').
Set up the SMTP server for the system to send notification emails, appointment confirmations, and status updates. Includes a test email feature to verify the configuration.
| Field | Type | Description |
|---|---|---|
| Protocol | select |
Currently supports
SMTP.
|
| SMTP Host | text |
SMTP server address. Example:
smtp.gmail.com,
smtp.sendgrid.net.
|
| SMTP Port | text |
Connection Port. Usually
587 (TLS) or
465 (SSL).
|
| Encryption | select |
TLS or
SSL.
|
| Username | text | SMTP username (usually the email address). |
| Password | password | SMTP password or App Password (for Gmail, 2FA must be enabled and an App Password created). |
| Charset | text |
Character set. Default
utf-8.
|
| From Address | text | Sender email address displayed in the recipient's inbox. |
| From Name | text |
Sender display name. Example:
Dream Team.
|
| Reply-To | text | Email to receive replies when the user clicks Reply. Leave blank to use From Address. |
After saving the configuration, enter an email address in the "Send test email" field and click the test button. The system sends a sample email to that address immediately.
- If you receive the email → SMTP configuration is correct.
- If not received → check host, port, encryption, and App Password again.
Customize HTML content for each type of system email (appointment confirmation, status change...) at:
myaccount.google.com → Security → App
passwords
to create one.
Inject Google Analytics, Facebook Pixel, Google Tag
Manager scripts or any code snippet into the
<head> or
<body> of all pages. Includes an
option to delay script loading to improve page
speed.
| Field | Description |
|---|---|
| HTML Head (No Script) |
Inject at the end of
<head> as
<noscript> —
used for GTM noscript tags or
pixels that don't need JS.
|
| HTML Head |
Inject at the end of
<head> —
used for Google Analytics, GTM
scripts, verification meta tags.
|
| HTML Body (Footer) |
Inject before
</body> —
used for chat widgets, pixels,
scripts initialized after DOM.
|
This feature delays the execution of injected scripts until the user interacts with the page (scroll, click, mouse move). This helps improve Core Web Vitals and PageSpeed scores.
| Option | Description |
|---|---|
| Enable delay | Toggle to enable/disable the delay feature. When disabled, scripts run as soon as the page loads. |
| Delay time (seconds) |
If the user does not interact,
scripts will still run after
exactly this many seconds.
Default is 10
seconds.
|
<script async
src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX">
snippet into HTML Head. To
install GTM: paste the script into
HTML Head and the noscript
into
HTML Head (No Script).
Configure the homepage, pagination, asset version and no-index options for the entire website.
| Field | Type | Description |
|---|---|---|
| Homepage | select |
Select a CMS page to use as the
homepage. The selected page will
be mapped to the URL /.
|
| Items per page (pagination) | number |
Number of items displayed per page on
listing pages (posts, products...).
Modules can register their own
pagination config via the filter
BASE_FILTER_ADD_PAGINATION_CONFIG.
|
| Asset Version | number |
Asset version (CSS/JS). Increment
this number to force browsers to
reload the latest asset files (cache
busting). Defaults to the value from
cms_assets.version.
|
| No-index | checkbox |
Enable to add
<meta name="robots"
content="noindex">
to all pages — typically used in
staging/dev environments to
prevent Google from indexing.
|
Configure Google reCAPTCHA v2 for use across the entire system — protects booking forms, contact forms, and customer-facing forms from spam and bots.
| Field | Type | Description |
|---|---|---|
| Enable reCAPTCHA | toggle | Enable/disable reCAPTCHA across the entire system. When enabled, two additional key fields will appear below. |
| Site Key | text | Public key embedded into the frontend page to display the reCAPTCHA widget. |
| Secret Key | text | Secret key used server-side to verify the response from Google. Never expose this on the frontend. |
- Go to google.com/recaptcha/admin/create.
- Enter a name for the site, select the type reCAPTCHA v2 → "I'm not a robot".
- Add your website's domain to the allowed list.
- Click Submit — Google will provide the Site Key and Secret Key.
- Copy both keys into the settings form and save.
Once enabled and configured, reCAPTCHA is automatically applied at the following points:
- Booking form — customer appointment booking page.
- Contact form — customer enquiry / contact page.
isRecaptchaEnabled() and get the key
with getRecaptchaConfig('key') /
getRecaptchaConfig('secret').
Configure floating contact buttons on the website: Zalo, Facebook, Phone, WhatsApp, Messenger... Admin sets up links, descriptions and display types.
Each channel is an independent list of accounts (you can add multiple phone numbers, multiple Zalo accounts...). Each item includes:
| Channel | Example link |
|---|---|
https://facebook.com/yourid
|
|
| Zalo |
https://zalo.me/0912345678
|
| Phone | tel:0912345678 |
https://wa.me/84912345678
|
|
| Viber |
viber://chat?number=+84912345678
|
| Messenger |
https://m.me/yourpage
|
https://instagram.com/yourprofile
|
Each item also has a Description field (displayed as tooltip) and Google Conversion Tag (to track clicks via Google Ads).
| Option | Description |
|---|---|
| Show button | Enable/disable the entire CTA area on the website. |
| Display type |
Icon
— show icon only Icon + Text — show icon with label Combined Icon — grouped into a single button, click to expand the list |
| Position | Left or Right of the screen. |
| Support title | Label displayed above the button group (when display type is Combined Icon). Example: "Contact us". |
Enable two-step authentication using Google Authenticator for all admin accounts. When enabled, each login requires the admin to enter an OTP code from the Google Authenticator app on their device.
| Field | Description |
|---|---|
| Enable 2FA | Toggle to enable/disable the two-step authentication requirement for all admins. Applies to all accounts simultaneously. |
- Enable the "Enable 2FA" toggle on this page and save.
- Each admin goes to Account Information → scans the QR code using the Google Authenticator app (iOS / Android).
- Enter the 6-digit OTP code from the app to confirm the link.
- From the next login, the system requires entering the OTP after the correct username and password have been entered.
2fa middleware. When 2FA is enabled and
the admin has not verified the OTP in the current session,
they will be redirected to the OTP entry page before
accessing the admin panel.
Configure the URL structure for posts, post categories, single pages, products and product categories across the website. Changes will update all links in the database.
Choose from one of the predefined URL structures or fully customize it:
| Type | Example URL |
|---|---|
| Day and post name |
/2023/04/04/post-name
|
| Month and post name |
/2023/04/post-name
|
| Post ID string | /blog/123 |
| Post name | /post-name |
| Category & post name |
/category/post-name
— enable
Link structure
to create hierarchical URLs based on
the category tree
|
| Custom |
Enter your own structure with dynamic tags:{year}
{monthnum}
{day}
{hour}
{minute}
{second}
{post_id}
{postname}
{category}
{author}{postname} must be at
the end.
|
Enter a prefix string for post categories. Example:
enter news → category URL becomes
/news/category-name. Leave blank →
/category-name.
-
Show as .html —
adds the
.htmlsuffix to category URLs. - Link structure — creates hierarchical URLs following the parent → child category tree.
Enter a prefix for single pages. Example: enter
page → page URL becomes
/page/{pagename}. Leave blank →
/{pagename}.
The {pagename} tag must be at
the end.
This section is injected by the Service Booking module
via the hook INIT_LINK_CUSTOM — it only
appears when the Service Booking module is activated.
| Item | Description |
|---|---|
| Branch URL prefix |
Enter a prefix for branch pages. Example:
spa →
/spa/branch-slug.
|
| Service URL structure |
Enter your own structure with tags:
{servicename},
{branchslug}.Example: /{servicename}.html
or
/book/{branchslug}/{servicename}.
|
replaceMenuLink() to update all
links in menus and the database. For sites with
a lot of content, this operation may take a
few seconds to a few minutes. Do not close the page while
saving is in progress.
Manage the content of the ads.txt file —
the authorized seller verification file per IAB standards.
Required when running Google AdSense, Google Ad Manager
or other advertising networks.
Paste the content of the ads.txt file
into the textarea. Each line is a seller declaration,
following the standard:
google.com, pub-XXXXXXXXXXXXXXXX, DIRECT, f08c47fec0942fa0 advertising.com, 12345, RESELLER
The content is saved to the physical file at
public/ads.txt. Leave blank to delete
the file.
/ads.txt. Ad
networks will automatically crawl this URL to
verify. Check after saving by opening that URL
in a browser.
Configure conversion tracking codes (Google Ads Conversion, Facebook Pixel...) for key booking events: initiate booking, appointment confirmed, payment successful.
The Tracking page has no hardcoded content — all
tabs and forms are injected by other modules
via the filter hook
FILTER_LIST_DATA_GOOGLE_CONVERSION. Each module registers its own group of tabs.
If no module has registered yet, the page displays the message: "This feature is only available for service booking platforms!"
The Service Booking module injects tracking tabs
via renderGoogleConversionSetting():
| Tab / Event | When triggered |
|---|---|
| Initiate booking | When a customer starts the booking flow — selects a service or branch. |
| Appointment confirmed | When the booking form is submitted and an appointment is created successfully. |
| Payment successful | When the customer completes online payment for an appointment — runs on the thank-you page. |
Each tab has the following fields:
| Field | Description |
|---|---|
| Activate | Enable/disable this tracking event. |
| Page load code | Script that runs when the event page loads. Paste code from Google Ads, GTM event or Pixel. |
| Conversion code (on click) | Script that runs when the button is clicked. Supports dynamic parameters. |
| Dynamic parameters |
The system replaces parameters
in the code before rendering:{price_total} —
Total order value{currency} — Currency
unit{transaction_id} —
Order ID
|
gtag('event', 'conversion', {...})
and paste it into the
Conversion code field of the corresponding event.
Use the {price_total} parameter for
the conversion value.
Automatically generate and insert a Table of Contents (TOC) into post, single page and product content based on heading tags (H2, H3...) found in the content.
| Field | Type | Description |
|---|---|---|
| Enable / Disable TOC | toggle | Enable to activate the feature. When disabled, the TOC will not appear anywhere on the website. |
| Display position | select |
Before first heading
— insert TOC just before the first H2/H3 After first heading — insert after the first heading Before content — insert at the top of all content After content — insert at the bottom of content |
| Display modules | checkbox |
Select which modules will apply
TOC: Posts — post detail page (Post) Single Pages — Page Products — service detail page (Booking) Multiple modules can be selected at once. |
When TOC is enabled, the system analyzes the HTML of
the content field (CKEditor), finds all
<h2>,
<h3>,
<h4>... tags, assigns
id attributes to each heading, then generates
a TOC HTML block and inserts it at the selected position.
The TOC is only generated if the content has 2 or more headings. If there is only 1 heading, TOC will not be displayed.
setting_name = 'cms_toc'. The post
and product modules automatically read this config to
decide whether to show TOC or not — no need to add
shortcodes or code to templates.
Manage supported currencies, configure price display formatting and options for auto-detecting currency based on visitor location.
| Field | Description |
|---|---|
| Default currency symbol |
String displayed before/after the price.
Example: ₫,
đ,
VND.
|
| Auto-detect currency | Enable to have the system automatically switch to the appropriate currency based on the visitor's IP geolocation. Requires multiple currencies to be configured. |
| Space between price and symbol |
Enable to add a space between the price
number and the currency symbol. Example:
100,000 đ instead of
100,000đ.
|
| Thousands separator |
, (comma),
. (period), or space.
Example:
1,000,000 or
1.000.000.
|
| Decimal separator |
. (period) or
, (comma). Example:
99.99 or
99,99.
|
The lower section of the page allows adding/editing/deleting currencies. Each currency includes:
| Field | Description |
|---|---|
| Name |
Currency name. Example:
Vietnamese Dong.
|
| Code |
ISO 4217 code. Example:
VND,
USD.
|
| Symbol |
Display symbol. Example:
₫, $.
|
| Exchange rate |
Exchange rate relative to the default currency.
The default currency is set to
1.
|
| Default | Mark as the default currency displayed to visitors. Only one currency can be set as default. |
Manage supported languages on the website, configure the default language, enable multilingual mode and set the system timezone.
The Language tab (default) allows adding, editing, deleting and enabling/disabling languages. Each language includes:
| Field | Description |
|---|---|
| Select language | Dropdown to select from a standard language list. When selected, the system auto-fills Name, Locale, Code and Text direction. |
| Name |
Display name of the language. Example:
Vietnamese,
English.
|
| Locale |
Locale code used for translation directories.
Example: vi,
en_US.
|
| Language code |
ISO code (2 characters). Example:
vi,
en. Used in query
strings when switching language.
|
| Text direction | LTR (left to right — default) or RTL (right to left — Arabic, Hebrew). |
| Country flag | Flag icon displayed in the language switcher on the frontend. |
| Sort order | Display order in the list. Lower numbers appear first. |
lang/{locale}/
by copying from lang/en/.
Configuration saved with
setting_name = 'siteLanguage'.
Contains 4 fields:
| Field | Type | Description |
|---|---|---|
| Default language | select |
Default display language for website
visitors. Must be selected from the
list of Active languages.
Default: vi.
|
| Admin panel language | select |
Language displayed in the admin
interface (admin panel).
Separate from the frontend language.
Default: vi.
|
| Enable multilingual | toggle | Enable to allow visitors to choose their language. When enabled, the Language Switcher appears on the frontend. When disabled, the website uses only the default language. |
| Timezone | select |
Timezone used for the entire system —
post publish times, orders, logs... Default:
Asia/Ho_Chi_Minh
(UTC+7).
|
When multilingual is enabled, visitors can
switch languages via the query string
?lang={code}. The system uses
LanguageNegotiator to determine the
current language by priority order: query
string → cookie → Accept-Language header →
default language.
Configure social login (Google OAuth) and policy links
displayed in the registration form. All settings are saved
to the settings table with
setting_name = 'setting_auth_social'.
Allows users to log in quickly using their Google account instead of creating a separate password. You need to create an OAuth application at Google Cloud Console to obtain the credentials to fill in here.
| Field | Required | Description |
|---|---|---|
Google Client ID
auth_social_google_client_id
|
No |
Application identifier issued by Google.
Format:
123456789-xxxx.apps.googleusercontent.com. Obtain from
Google Cloud Console → APIs
& Services →
Credentials.
|
Google Client Secret
auth_social_google_client_secret
|
No | Secret password corresponding to the Client ID. Do not expose or commit to code. |
https://yoursite.com/auth/google/callback
to the
Authorized redirect URIs field.
When users register an account, the form displays the sentence: "By checking and proceeding to register, you agree with us regarding..." along with links to policy pages. Each field below allows selecting a single page already created in the Page module — the system will automatically use that page's URL to create the link.
| Field | Required | Description |
|---|---|---|
Privacy policy page
policy_page_id
|
No | Page describing how the website collects and uses users' personal information. Displays as "privacy policy" link in the registration form. |
General policy page
policy_general_page_id
|
No | Page outlining general policies when using the service (rules, return policy, etc.). |
Terms of service page
term_page_id
|
No | Page presenting the terms and conditions users must accept when registering an account. |
Complaint handling mechanism page
complaint_handling_mechanism_page_id
|
No | Page guiding users on how to submit complaints about personal data handling (often required by digital service legal regulations). |
Support page
support_page_id
|
No | Contact or customer support page. Displays as "support" link at the end of the terms confirmation sentence. |
hasLocale = false) — no need to reconfigure per locale.
Store and track all contact requests from website users — including regular contact forms and newsletter subscriptions. Admins can view content and mark processing status.
| Filter field | Description |
|---|---|
| Search by sender's email address. | |
| Subject | Search by contact subject / title. |
| Status |
New (0)
— unread, displayed prominently Contacted (1) — opened and viewed or manually marked |
| Type |
contact
— contact request from the contact form newsletter — email newsletter subscription |
| Time | Filter by date range submitted. |
| Column | Description |
|---|---|
| Sender's email address. | |
| Full name | Sender's name (if provided). |
| Phone number | Sender's phone number (if provided). |
| Subject | Contact subject / title. |
| Status | Can be changed quickly directly in the table (quick edit) without opening the detail view. |
| Time | Date and time the request was sent. |
Click the View icon to open the detail page. At that point:
- If the contact is in the New status, the system automatically changes it to Contacted.
- Displays the full message content, contact information, and the URL of the form submission page.
Records all admin actions across the system: create, update, delete, login. Allows searching and restoring permanently deleted data.
| Filter Field | Description |
|---|---|
| Admin | Filter by the admin performing the action. |
| Module Type | Filter by the affected table/module (posts, products, orders...). |
| Record Name | Search by the specific name of the affected item (LIKE). |
| Action | Filter by the type of action (see table below). |
| Time | Filter by the time range. |
| Action | Description |
|---|---|
create
|
Admin creates a new record. |
update
|
Admin updates the content of a record. |
quick_update
|
Quick update from the list table (change status, quick edit). |
quick_delete
|
Move the record to the trash. |
quick_restore
|
Restore the record from the trash. |
delete_forever
|
Permanently delete from the trash. This type of log allows rollback of data. |
login
|
Admin logs into the system. |
| Column | Description |
|---|---|
| Admin | The account name that performed the action. |
| IP | The IP address of the device at the time of the action. |
| Action | The type of operation (create / update / delete_forever...). |
| Module | The affected table/module (posts, products, orders...). |
| Time | The exact time the action was performed. |
When an admin permanently deletes a record, the system saves all old data (including slug, language, SEO, related comments) into the log with the action delete_forever.
To restore, open the log detail page and click "Rollback":
- The system recreates the record in the original table with the old data.
-
Restores with
slug,language_metas,meta_seoandcommentsif any. - Removes the log from the list (to avoid duplicate rollbacks).
- Redirects to the edit page of the restored record.
delete_forever.
Logs of other actions (create,
update...) are for reference only and do not support
rollback.
Use the "Delete Logs" button on
the list page (visible if you have the
system_logs_delete permission) to delete the
filtered logs. This action cannot be undone — the rollback data of the deleted logs
will also be lost.
View all payment transactions from booking orders. Supports filtering by transaction ID, method, and status.
| Filter Field | Description |
|---|---|
| Transaction ID |
Search by
charge_id — transaction ID from the payment gateway.
|
| Payment Method | Filter by gateway: COD, Bank Transfer, VNPay, PayPal, Stripe... The list is automatically extended when additional payment packages are installed. |
| Status |
Pending — new transaction, not yet confirmed Success — payment completed Failed — transaction rejected / error Refunded — refunded to customer |
| Column | Description |
|---|---|
| Transaction ID | Transaction ID from the payment gateway. Click to view details. |
| Payer Information | Name and information of the customer associated with the order. |
| Amount | Transaction value, displayed with currency unit. |
| Payment Method | Payment gateway used. |
| Status | Badge color indicating the current status. Can be updated manually on the detail page. |
| Time | Transaction creation time. |
View full information of a payment transaction, manually update the status, and view refund information from the payment gateway.
The detail page consists of two parts:
basic information (from the
payments table) and
extended information (injected
from the corresponding payment package via the filter
PAYMENT_FILTER_PAYMENT_INFO_DETAIL).
| Field | Description |
|---|---|
| Transaction ID (charge_id) | Original transaction ID from the gateway. Used to look up on the payment gateway dashboard. |
| Amount | Payment amount. |
| Method | Payment method used. |
| Status | Dropdown allows manual update. The Pending status is hidden if the transaction is already processed. |
| Payment Gateway Information | Extensions inject details for each gateway — for example, VNPay shows bank code, response code; PayPal shows payer email, PayPal transaction ID. |
| Refund |
If the gateway supports it (VNPay), there is a button to view refund information. Clicking it opens a popup where you can view detailed information of each refund attempt, including time and amount. The data is taken from the field metadata.refunds[].
|
Package payment con (VNPay, PayPal,
Stripe...) inject detailed content into
this page via the filter
PAYMENT_FILTER_PAYMENT_INFO_DETAIL. Each package returns its own HTML from its view, for example Vnpay::detail or
DreamTeamPayPal::detail. If
the package is not installed, this section is empty.
Enable/disable and configure each payment gateway. Includes built-in methods (COD, Bank Transfer) and extensions injected via hooks.
This page displays a table of all payment methods. Each method has a status badge (Enabled/Disabled) and a Settings button to access its configuration page. If it is the default method, a "Default" badge will be displayed.
| Payment Method | Description |
|---|---|
| COD |
Cash on Delivery. Pre-installed in
PaymentMethodEnum::COD, no API key required. Setting
key:
payment_cod_status.
|
| Bank Transfer |
Displays bank account information for customers to manually transfer funds. Setting key:
payment_bank_transfer_status.
|
| VNPay |
Vietnamese domestic payment gateway. Injected via hook
PAYMENT_METHODS_SETTINGS_PAGE
from package
payment-vnpay. Supports refunds.
|
| PayPal |
International payment gateway. Injected
from package
payment-paypal. Automatically converts to USD if the currency is not supported.
|
| Stripe |
International payment gateway. Injected
from package
payment-stripe.
|
Each payment method has its own configuration page. The form content is injected via the filter
PAYMENT_METHODS_SETTINGS_RENDER_PAGE. Common fields include:
| Field | Applicable to |
|---|---|
| Set as Default |
All. Setting key:
default_payment_method. Once enabled, the payment method name will be saved to this key.
|
| Display Name / Description | COD, Bank Transfer, VNPay, PayPal, Stripe — hiển thị trên trang checkout. |
| Client ID / Client Secret |
PayPal:
payment_paypal_client_id,
payment_paypal_client_secret.VNPay: payment_vnpay_client_id
(TmnCode),
payment_vnpay_client_secret
(HashSecret).
|
| Secret Key / Publishable Key |
Stripe:
payment_stripe_secret,
payment_stripe_publishable.
|
| Bank Account Information | Bank Transfer: bank name, account number, account holder, QR code for transfer. |
Extension gateways (VNPay, PayPal, Stripe) register into the payment system through 3 main hooks:
| Hook | Purpose |
|---|---|
PAYMENT_METHODS_SETTINGS_PAGE
|
Add a row to the payment methods list table. |
PAYMENT_METHODS_SETTINGS_RENDER_PAGE
|
Render configuration form when accessing the settings page of that payment method. |
PAYMENT_FILTER_AFTER_POST_CHECKOUT
|
Handle payment logic (create redirect link, call gateway API) when a customer places an order. |
PAYMENT_FILTER_PAYMENT_INFO_DETAIL
|
Inject HTML transaction details into the payment detail page in the admin panel. |
HookServiceProvider and register the
above filters. No need to modify the core
payment package. The system will
automatically detect and display the new gateway
in the list.
Create and manage admin accounts. Each account can be assigned roles, detailed permissions, avatars, and author information.
| Field | Description |
|---|---|
| Name | Search by username (slug). |
| Search by email. | |
| Role | Filter by created role groups. |
| Creation Date | Account creation date range. |
| Status | Active / Inactive / Draft. |
Two-column form: the left side is account information, the right side is permissions.
| Field | Required | Description |
|---|---|---|
| Username | Yes | Username used for login. Automatically converted to slug. Maximum 191 characters. |
| Yes | Login email, must be unique. | |
| Password / Confirm | Yes | Password is hashed using bcrypt. Supports random password generation button. |
| Avatar | No | Avatar image. Displayed in the post list and author page on the frontend. |
| Display Name | No | Display name on the frontend (different from username). |
| Position | No | Displayed under the author's name on the post page. |
| Author Bio | No | Short description (up to 200 characters). Displayed in the author box under the post. |
| Social links | No |
Website, Facebook, Twitter,
Pinterest, Instagram, YouTube —
stored as JSON in the
social column.
|
| Full Author Bio | No | CKEditor content for the full author page. |
| Role | No |
Select either a predefined role with specific permissions, such as
Super Admin
(full access) or a custom role. When selecting Super Admin,
is_supper_admin = 1, there is no need to select detailed permissions.
|
| Custom Permissions | No |
Override role permissions. Stored as JSON in the
capabilities column.
|
Create and manage role groups. Each role defines a set of permissions allowed to perform in the admin panel.
The system has 2 levels of permissions:
| Type | Description |
|---|---|
| Super Admin |
Account with
is_supper_admin = 1. Full access, not checked
for permissions. Can be selected directly
when creating/editing an admin user.
|
| Custom Role |
Role group created on this page.
Stores the list of permissions in JSON format
in the permissions column.
When an account is assigned a role, it will only have access to the allowed functions.
|
| Field | Required | Description |
|---|---|---|
| Role Name | Yes |
Display name. For example:
Editor,
Accountant.
|
| Permission List | Yes |
Permission checklist by module
(posts, appointments, services, settings, etc.).
Each module typically includes the following permissions:
view,
add,
edit,
delete,
deleteForever.
|
| Status | Required | Roles with Inactive status will not appear in the role selection dropdown when creating a new admin user. |
Review, edit, reply to, and delete customer comments. Comments can come from multiple modules: posts, products, static pages, etc.
| Field | Description |
|---|---|
| Name | Search by commenter name. |
| Content | Full-text search within comment content. |
| Comment Date | Comment creation date range. |
| Display Status | Visible — comment is publicly displayed on the frontend. Hidden — comment is hidden. |
| Processing Status | Processed (admin has replied/approved). Unprocessed — requires attention. |
| Action | Description |
|---|---|
| Edit Comment | Admin can modify customer comment content. For product comments, attached images can also be edited. |
| Quick Reply |
Admin replies directly from the list.
The reply is created as a child comment
(parent_id) with
admin_id assigned.
Status is automatically set to
Visible.
|
| Hide Comment | Change status to Hidden — the comment will not appear on the frontend but is not deleted. |
| Permanently Delete |
Completely remove from the database.
When a parent comment is deleted, all
child comments
(parent_id)
are deleted as well.
The action is recorded in the System Log.
|
The type column identifies which module
the comment belongs to. Examples:
posts,
products,
pages.
Hidden filters
type and
type_id
allow filtering comments by a specific
post/product when opened from the content detail page.
BlogHelper::isCommentEnabled()).
When disabled, the comment form is completely hidden
even though the Comment module remains active.
Manage 3-level geographical data: Country → Province/City → District → Ward. This data is used in address forms and branch location display.
| Level | Route | Description |
|---|---|---|
| Country |
admin.countries.*
|
Highest level. Examples: Vietnam, USA. Each province/city belongs to a country. |
| Province/City |
admin.provinces.*
|
Provincial/city level. Can be filtered by country. Deleting a province automatically deletes all child wards. |
| Ward | admin.wards.* |
Lowest level, belonging to a province/city. |
The module supports importing geographic data from
Excel files via
BulkImportController.
Used when importing complete province/district/ward
data for a new country.
| Action | Description |
|---|---|
| Import Excel |
Upload an
.xlsx file with the structure:
location name, code, parent ID.
The system automatically creates all records.
|
| Export Excel | Export all province/ward data to an Excel file. Used for backup or migration to another environment. |
code from its name (uppercase slug).
This code is used for lookup or synchronization with external systems.
Multi-language support is available — each location name can be translated by locale.
Manage URL redirects (301/302). When changing URL structures, add redirects to prevent SEO traffic loss and avoid 404 errors on old links.
| Field | Required | Description |
|---|---|---|
| Source URL (old) | Yes |
Old path to redirect.
Example:
/news/old-article.
Enter a relative path (without domain).
|
| Target URL (new) | Yes | New destination path. Can be a relative path or a full URL. |
| Redirect Type | Yes |
301 — Permanent Redirect
(Moved Permanently).
Google transfers PageRank to the new URL.
Use when the URL has permanently changed.
302 — Temporary Redirect (Found). Google keeps PageRank on the old URL. Use when the original URL will be restored later. |
| Status | Yes | Only Active redirects are applied on the website. |
When you need to add a large number of redirects (for example after migrating an old website), use the import feature:
| Action | Description |
|---|---|
| Import Excel |
Upload a
.xlsx or
.xls file.
Column structure:
Source URL,
Target URL,
Type (301/302).
Requires
sync_links_import
permission.
|
| Export Excel |
Export the current redirect list
(based on applied filters)
to
sync-links-{timestamp}.xlsx.
Requires
sync_links_export
permission.
|