JetAppointment Booking Form for a Custom Post Type Single Page

To add a JetAppointment booking form to a custom post type, set your JetEngine CPT as the services post type. Then build a JetFormBuilder form whose Appointment Date field takes the service ID from a Hidden Field set to Current Post ID. Put that form in the CPT's Elementor single template, and every property, venue or showroom gets its own visit calendar. Appointment workflows then email the visitor a cancel link and send the admin a copy with confirm and cancel links.
- Step 1 – Make your custom post type bookable
- Step 2 – Build the single-item booking form
- Step 3 – Save documents and extra fields
- Step 4 – Add the form to the Elementor single template
- Step 5 – Turn on the cancel and confirm links
- Step 6 – Email the visitor and the admin
- Step 7 – Notify both sides when a visit is cancelled
- Step 8 – Show all bookings to the admin on the front end
- Troubleshooting
Step 1 – Make your custom post type bookable
- Activate JetAppointment and finish the setup wizard before you build any booking form.
- For the service post type, pick your listing CPT, such as Properties or Venues. You don't need a separate Services CPT.
- If one person or team handles every visit, leave providers off. The form then needs no provider field.
- Set your working days, working hours and slot duration, for example 30-minute viewings.
- If the wizard offers to generate a Single Service form, accept it. You'll adapt it in Step 2.
You can change these choices later in Appointments > Settings. If one slot should hold several visitors, see our guide to JetAppointment capacity by number of people.
Step 2 – Build the single-item booking form
Go to JetFormBuilder > Add New, or open the generated Single Service form, and set up these fields in order:
- Hidden Field named
service_id, FIELD VALUE Current Post ID. On a single page it holds the ID of the item being viewed. - Appointment Date field named
appointment_date. In Field Settings, set GET SERVICE ID FROM to Form Field and SELECT SERVICE FIELD toservice_id. You could pick Current Post ID directly, but the hidden field can be reused, for example in a Send Email action. Turn on Auto-Select Single Slot so a day with one slot selects it automatically. - Optional: a Form Break Field (JetFormBuilder's multi-step break), so visitors pick a slot first and enter details second.
- Text Field named
user_name. - Text Field named
user_email, FIELD TYPE Email, required. The form won't submit without an email field. - Text Field named
phone, FIELD TYPE Tel. - Textarea named
comments. - Media Field named
documents, only if you really need files (see Step 3). - Action Button set to Submit, as the last field.
Then go to JetForm settings > Post Submit Actions > New Action, choose Insert appointment and map:
- SERVICE ID FIELD:
service_id - PROVIDER ID FIELD: empty when providers are off
- APPOINTMENT DATE FIELD:
appointment_date - USER E-MAIL FIELD:
user_email - USER NAME FIELD:
user_name
With WooCommerce active, the action also shows price, order details and checkout field settings. Free visits don't need them.
Step 3 – Save documents and extra fields with the appointment
Phone, comments and documents aren't default appointment fields. On the Tools tab of the JetAppointment settings, use New DB Column to add a column for each, then click Save and update appointments table. Until you do, the columns don't exist and nothing is stored.
Reopen the Insert Appointment action and map each form field to its column. If you don't see a place to map them, check how your version handles custom columns. Every new column also has to be added to the notification emails.
Set up the Media Field like this:
- USER ACCESS: for bookings without login, choose Any user (incl. Guest). Guest uploads attract spam files, so keep the next two limits tight.
- ALLOW MIME TYPES: list only what you accept, for example PDF and JPEG. Empty means every type is allowed.
- MAXIMUM SIZE IN MB: lower it to what a scanned document needs.
- Insert attachment: turn it on, then set FIELD VALUE to Attachment URL. Attachment ID or the array option would put a number or an array into the email link and admin list instead of a working URL.
Privacy: ID scans and payslips are sensitive. With Insert attachment on, uploads become Media Library attachments, and anyone with the direct URL can open them. Check how your JetFormBuilder version stores and protects uploads. Ask only for the documents you need, limit types and size, delete the files after the visit, and state how long you keep them in your privacy policy. For more upload options, see our JetFormBuilder file upload guide.
Step 4 – Add the form to the Elementor single template
- Open your CPT's single template in Elementor.
- Drag in the JetForm widget and select your form.
- Save, then book a slot on item A. In Appointments > Appointments, the booking should show item A as its service and Pending as its status.
- Open item B and check that its own calendar loads.
To help visitors find items first, pair it with our real estate property search filter.
Step 5 – Turn on the cancel and confirm links
Go to Appointments > Settings > Advanced and switch on Generate Confirmation URLs. Without it, %cancel_url% and %confirm_url% have no token to work with. Fill in Confirmed Message and Cancelled Message, the text people see after clicking a link.
Each appointment gets a unique token that isn't tied to a user account, so guests can cancel from the email without logging in. Following the confirm link sets the status to Completed; following the cancel link sets it to Cancelled. See the official guide to confirming appointments via link.
Step 6 – Email the visitor and the admin
On the Workflows tab, click Add New Workflow (some versions say New Workflow Item). Set Event to Appointment Created and Start to Immediately. Click New Action, set Action to Send Email and fill in:
- Action name: Visitor confirmation
- Email to: Dynamic Tags > Appointment User Email
- Email Subject: for example "Your visit request"
- Sent from email / Sent from name: an address on your domain and your business name
For the item's address, look in the Dynamic Tags list for a tag that reads a meta field from the booked service post. If there isn't one, add a second hidden field filled with the item's address meta, save it to its own DB column, and check whether that column appears in the Dynamic Tags list.
Paste this into the Email Message field. Replace each [bracketed] placeholder using the Dynamic Tags button, and keep the %…% macros exactly as shown:
Code
<div style="font-family:Arial,Helvetica,sans-serif;font-size:15px;line-height:1.6;color:#1f2933;max-width:560px;">
<p style="margin:0 0 16px;">Hello,</p>
<p style="margin:0 0 16px;">Thank you for booking a visit. We have received your request and will confirm it shortly.</p>
<p style="margin:0 0 8px;"><strong>Date and time:</strong> %appointment_start|format_date(F j, Y g:i a)%</p>
<p style="margin:0 0 8px;"><strong>Visit to:</strong> [Replace with the Service Title tag from the Dynamic Tags button]</p>
<p style="margin:0 0 24px;"><strong>Address:</strong> [Replace with your address tag from the Dynamic Tags button]</p>
<p style="margin:0 0 16px;">Plans changed? You can cancel the visit with one click:</p>
<p style="margin:0 0 24px;"><a href="%cancel_url%" style="display:inline-block;padding:12px 24px;background-color:#b42318;color:#ffffff;text-decoration:none;border-radius:4px;font-weight:bold;">Cancel my visit</a></p>
<p style="margin:0;font-size:13px;color:#52606d;">If the button does not work, copy this link into your browser: %cancel_url%</p>
</div>The %appointment_start|format_date(F j, Y g:i a)% macro prints the slot with a PHP date format. g:i a adds am/pm so "3:30" can't be misread. For a day-first date with 24-hour time, use j/m/Y H:i. All styling is inline because many email clients ignore <style> blocks.
The visitor's email has no confirm link on purpose. Confirming sets the appointment to Completed, so a visitor with that link could approve their own visit.
Action 2: the admin copy with confirm and cancel links
In the same workflow, click New Action, choose Send Email, type your admin address into Email to and paste this:
<div style="font-family:Arial,Helvetica,sans-serif;font-size:15px;line-height:1.6;color:#1f2933;max-width:560px;">
<p style="margin:0 0 16px;">New visit request. Status: Pending.</p>
<p style="margin:0 0 8px;"><strong>Item:</strong> [Service Title tag]</p>
<p style="margin:0 0 8px;"><strong>Date and time:</strong> %appointment_start|format_date(F j, Y g:i a)%</p>
<p style="margin:0 0 8px;"><strong>Visitor email:</strong> [Appointment User Email tag]</p>
<p style="margin:0 0 8px;"><strong>Phone:</strong> [phone column tag]</p>
<p style="margin:0 0 8px;"><strong>Comments:</strong> [comments column tag]</p>
<p style="margin:0 0 24px;"><strong>Documents:</strong> <a href="[documents column tag]">View documents</a></p>
<p style="margin:0;">
<a href="%confirm_url%" style="display:inline-block;padding:12px 24px;margin:0 8px 8px 0;background-color:#0f766e;color:#ffffff;text-decoration:none;border-radius:4px;font-weight:bold;">Confirm visit</a>
<a href="%cancel_url%" style="display:inline-block;padding:12px 24px;margin:0 0 8px 0;background-color:#b42318;color:#ffffff;text-decoration:none;border-radius:4px;font-weight:bold;">Cancel visit</a>
</p>
</div>If a custom column is missing from the Dynamic Tags list, make sure it exists and is mapped in the Insert Appointment action; if it still doesn't appear, your version can't put that value into a workflow email. Delete the Documents line if you don't use the Media Field; if you do, the link only works with the Attachment URL setting from Step 3 and stays empty when nothing was uploaded.
Visitor-typed text: phone and comments go into an HTML email next to the Confirm and Cancel buttons, and the workflow may not escape them. Submit a test booking with <b>test</b> as the comment. If the admin email shows bold text instead of the literal tags, remove the comments line (and the phone line, if it behaves the same) and read those values in Appointments > Appointments instead.
To test, book a visit with an email address you can read. Check the date, item and address in both emails, make sure no [bracketed] text is left, and open the documents link.
Step 7 – Notify both sides when a visit is cancelled
- Add a workflow with Event Appointment Status Changed and New Status Cancelled.
- Add one Send Email action to Appointment User Email and one to the admin address, both with the date macro and the Service Title tag.
- Optional: a workflow with New Status Completed that tells the visitor their visit is confirmed once the admin clicks the confirm link.
Click the cancel button in a visitor email to test. You should see your Cancelled Message, the booking should show Cancelled under Appointments > Appointments, and both emails should arrive. Admins can still change any status by hand there.
Front-end modification and cancellation has been requested on Crocoblock's suggestions board.
Step 8 – Show all bookings to the admin on the front end
- Create a JetEngine listing item with the User Appointments source, as in the official guide to creating listings for appointments.
- Add Dynamic Field widgets with Object Fields for date, slot, service and visitor email. The service field outputs a post ID, so turn on Filter field output with the Get post/page link (linked title) callback. Date and slot need the Format date callback. If your custom columns appear as Dynamic Field options, output the documents URL as a link.
- Place a Listing Grid on a new page and set its Appointments Query (User, Status, Date) to return every user and the statuses you want. The guide targets a user's own appointments, so compare the count with Appointments > Appointments.
- Restrict the page or its container to administrators with JetEngine Dynamic Visibility, or the role condition your version offers, and keep it out of your menus. Log in as a non-admin user to confirm the bookings are hidden.
Troubleshooting
- The form won't submit. Add an Action Button set to Submit and map an Email-type Text Field to USER E-MAIL FIELD (Step 2).
- No slots, or another item's slots. Point the Appointment Date field at
service_idand keep the form on the single template, not a static page (Steps 2 and 4). - Provider fields don't load. Only the Appointment Provider field selects providers; turn providers off if you don't need them.
- Phone, comments or files are missing. Recheck the columns, mapping and Media Field settings in Step 3.
- Email links do nothing. Switch on Generate Confirmation URLs (Step 5) and test with a new booking.
- Visitors approve their own bookings. Remove
%confirm_url%from every email sent to Appointment User Email.
You now have a calendar on every item, a cancel link for visitors and a private booking overview for the admin. If you'd rather have this booking flow built for you, post a request on BoostMyCroco.
Frequently Asked Questions
Can JetAppointment use a JetEngine custom post type instead of a Services post type?
Can visitors reschedule a JetAppointment booking themselves?
Why does my appointment show as Completed before the visit has happened?
Why does the documents link in my JetAppointment email show a number instead of a file?
Do I need WooCommerce for JetAppointment booking forms?
This guide answers a question from our community: Integrar JetAppointment a medida
Sources
- JetAppointment: Wizard Plugin Installation (crocoblock.com)
- How to Confirm Appointments via Link - Help Center (crocoblock.com)
- Front-End Appointment Modification & Cancellation for JetAppointment · Issue #8214 · Crocoblock/suggestions · GitHub (github.com)
- How to Create Listings for Appointments — JetAppointment (crocoblock.com)
- How to Set Appointment Booking Forms — JetAppointment (crocoblock.com)


