Back to Tips & Tricks

JetFormBuilder Tour Booking Form With Price per Person and Date

BoostMyCroco TeamPublished on
Abstract travel booking scene with a calendar, traveller figures and a price calculator for a JetFormBuilder tour form

To build a JetFormBuilder tour booking form with a price per person, save each departure date as its own post with a price_adult field. Feed those departures into a Select field with the Get values list from JetEngine Query generator, using price_adult as each option's calculated value. A Calculated Field then multiplies that price by the number of travellers.

Child rates, the single supplement and extras live in JetEngine meta fields on the tour, and the formula reads them with %META::field_name%. No custom code is needed.

Map the client's pricing rules before you build

Ask these questions first. Each answer becomes a field or a form setting.

Question for the clientWhat it becomes
Is the price per person or per group?Per person: the formula multiplies by travellers. Per group: price_adult holds the group price.
Does the price change by departure date or season?A price_adult field on every Departure post.
Is the child price a percentage or a fixed amount? Up to what age?A child_factor (e.g. 0.7) or child_price field. The age range goes in the Children label.
Is there a single-room supplement?single_supplement on the tour, single_rooms in the form.
Which extras are per person and which per booking?One price field per extra. Per-person extras are multiplied by travellers.
Is there a seat limit for each departure?max_travellers caps one booking. Tracking seats sold across bookings needs extra work or JetBooking.
Are deposits required?A deposit_percent field and a second Calculated Field.
Is this an enquiry or an online payment?Enquiry: email and insert-post actions. Payment: a checkout plus a re-check of the total.

The content structure

Tours post type

Create a Tours post type in JetEngine with these meta fields. Store every price as a plain number, with no currency signs or thousands separators.

  • child_factor: the share of the adult price a child pays, for example 0.7
  • single_supplement: the charge per single room
  • extras_price: the per-person price of an optional extras package (one field per extra if there are several)
  • deposit_percent (if deposits are used): the deposit share, for example 20
  • max_travellers: the most people one booking can include
  • price_from (optional): the lowest adult price, shown in listings

Fill in child_factor, single_supplement, extras_price and deposit_percent on every tour. Use 0 for an unused supplement or extra, 0 for deposit_percent when no deposit is taken, and 1 for child_factor if children pay full price. An empty value can leave the total blank or showing NaN.

Departures post type

Create a Departures post type with departure_date and price_adult. Link each departure to its tour, either with a JetEngine relation (one tour to many departures) or with a meta field that stores the tour's ID. Title each departure as its date, such as 12 May 2027, because the form uses the title as the dropdown label.

Separate records let the form list only that tour's dates, with each option carrying its own price.

Destinations (optional)

Add a Destinations taxonomy, or a post type related to Tours, so each destination page can show its tours in a Listing Grid.

Query only the current tour's departures

  1. In JetEngine's Query Builder, add a Posts query for the Departures post type.
  2. Filter it to the tour being viewed: compare the departure's tour link (relation or meta field) with the current post's ID, using JetEngine's dynamic value for the current object.
  3. Show only future departures by comparing departure_date with today. Store dates in a format that compares correctly.
  4. Order by departure_date ascending, save, and confirm the query preview returns items.

If you type the pipe string (shown below) by hand, use the number in the browser URL while editing the query (…&id=NN), not the query's own "Query ID" text setting.

The current-object filter has to resolve to the tour when the form builds its options. That can depend on your setup, especially with Enable Auto-Update, which reloads options through a separate request. Open a real tour page and confirm the dropdown lists only that tour's dates. If the preview stays empty, see our common JetEngine Query Builder mistakes and fixes.

Build the booking form

Create the form in JetFormBuilder and place it on the single Tour template. %META::% reads from the post where the form sits, so on any other page it has no tour to read. Field names must be unique on the page and use only lowercase Latin letters, numbers, - and _, with no spaces.

Departure (Select, name: departure)

Make the field required. Check whether the Select field in your version offers a Placeholder setting. If it does, add one such as Choose a departure date. If it doesn't, the earliest departure starts out selected and priced, so make sure travellers can see which date is chosen. Set FILL OPTIONS FROM to Generate Dynamically, then:

GENERATOR FUNCTION: Get values list from JetEngine Query
QUERY ID:           select your Departures query
OPTION VALUE:       ID
OPTION LABEL:       post_title
CALCULATED FIELD:   price_adult

OPTION VALUE and OPTION LABEL take post properties such as ID and post_title. CALCULATED FIELD takes the meta key price_adult, the same way the docs use _regular_price. The option source documentation also shows a pipe format typed into the FIELD NAME box: query ID | value | label | calculated value. With a query ID of 47:

47|ID|post_title|price_adult

Each option's value is the departure's post ID, its label is the date, and its calculated value is that date's adult price.

Adults and Children (Number, names: adults, children)

Make both required. Give adults a default and min value of 1 and children a default and min value of 0. A max value on a Number field is the same for every tour, because one form serves them all, so set one only if all tours share the same limit. For a per-tour cap, use a Select with the Numbers range by meta value generator, which builds options from 1 up to a number stored on the tour. Set its META KEY (default _max_quantity) to max_travellers.

Separate limits cap each field on its own. A combined adults plus children cap needs your own validation, so check the head count against max_travellers before you confirm a booking.

Single rooms (Number, name: single_rooms)

Required, with a default and min value of 0.

Extras (Radio, name: extras)

Use Manual Input. In the bulk editor, each line is Label : value : calc:

No extras : no : 0
Add the extras package : yes : 1

Make No extras the default. The CALCULATE value works as a 0/1 switch, and the price stays in the tour's extras_price field. Don't build several extras as one Checkbox and count on how ticked values combine. One Radio or Switcher field per extra keeps each on its own line of the formula.

Total (Calculated Field, name: total_price)

  • VALUE TYPE: as Number. Only this mode uses each option's CALCULATE value.
  • DECIMAL PLACES NUMBER: 2
  • THOUSANDS SEPARATOR: a comma, if the client wants one
  • CALCULATED VALUE PREFIX: your currency sign, such as $ (or CALCULATED VALUE SUFFIX for currencies written after the amount)

Put total_price inside a Conditional Block that shows only when departure is not empty, so visitors never see a broken total before a date is chosen.

Paste this formula into the Calculated Field. The wrench icon above the field lists every available macro.

Code

%departure% > 0 ? %departure% * (%adults% + %children% * %META::child_factor%) + %single_rooms% * %META::single_supplement% + %extras% * %META::extras_price% * (%adults% + %children%) : 0

How the total price formula works

  • %departure% > 0 ? … : 0 is a ternary condition meant to return 0 when no date is chosen. JetFormBuilder passes an empty field into the formula as an empty string, though, so with an empty Departure the expression can break and show NaN, a blank or a misleading 0. The Conditional Block is what keeps that state hidden.
  • %departure% returns the chosen option's CALCULATE value: that departure's price_adult.
  • (%adults% + %children% * %META::child_factor%) counts each adult as 1 and each child as the child share. Two adults and one child at 0.7 make 2.7 adult prices.
  • %single_rooms% * %META::single_supplement% adds the supplement once per single room.
  • %extras% * %META::extras_price% * (%adults% + %children%) adds the extras package per person when yes (1) is selected.

Test both states on a real tour page. Load the page without choosing a date and check that the total stays hidden. Then pick a date. With price_adult 1200, child_factor 0.7 and extras_price 150, booking 2 adults, 1 child, no single rooms and the extras package gives 1200 × 2.7 + 150 × 3 = 3690, shown as $3,690.00.

Defaults alone don't prevent NaN. If a visitor clears adults, children or single_rooms while editing, the empty value goes into the formula until the field is filled again. Required and min values only block an empty submission.

Variations and deposits

  • Fixed child price: remove %children% * %META::child_factor% from the brackets and add + %children% * %META::child_price% as its own term.
  • Per-booking extra: add another Radio field (e.g. transfer, calc 0 or 1) and append + %transfer% * %META::transfer_price% before the :.
  • Per-group price: use %departure% without the traveller multiplier.
  • Deposit: add a second Calculated Field. Wrap the whole total formula from above in brackets, then multiply. The ternary binds more loosely than multiplication, so without brackets the deposit math only applies to the final 0.
( …the total formula from above… ) * %META::deposit_percent% / 100

With a deposit_percent of 20, the example booking shows a deposit of $738.00. For more on reading post meta into a formula, see our tip on using post meta in a JetFormBuilder calculated field, and the Calculated Field overview for every formatting option.

Show the price on the tour page and use it in the submission

On the single Tour template and in the Tours Listing Grid, show price_from with a Dynamic Field, labeled From $1,200. It's entered by hand, so update it when departure prices change.

Include total_price in the Send Email message so the client and the traveller see the quoted total. If you store bookings as posts, map total_price, departure, adults and children to fields in an Insert/Update Post action.

Before charging money: treat the submitted total as a quote. Recompute it from the stored tour and departure prices, or have staff confirm it. Also check that the submitted departure ID belongs to the tour and is a future date: it comes from the browser and can be changed.

When to use JetBooking instead

The pattern above suits fixed departures and enquiry-style bookings. Switch to JetBooking when a tour needs date-range availability, real inventory, or a WooCommerce checkout tied to the booking.

  • Set the booking post type in WordPress Dashboard > Bookings > Settings > General (Booking instance post type).
  • Each post gets a Pricing Settings block with Price per 1 day/night (numbers only), Add rates, Add weekend and Seasonal prices.
  • For per-traveller pricing, use %ADVANCED_PRICE::_check_in_out% * %FIELD::travellers%, where travellers is your own Select field with numbers in CALCULATE. ADVANCED_PRICE returns the price for the chosen dates with rates and seasons applied.
  • In the booking action, set WOOCOMMERCE PRICE FIELD to your Calculated Field. If it's empty, WooCommerce charges the Price per 1 day/night value. The same re-check of browser totals applies.

Crocoblock's JetBooking price calculator guide documents its extra-services formula for Plain Booking Mode. For time-based setups, see our JetBooking hourly booking tip.

Troubleshooting

  • The departure dropdown is empty. Check that the right query is selected in QUERY ID and that its preview returns items for a real tour with future departures. Then check that ID, post_title and price_adult match the properties the preview shows. In a hand-typed pipe string, the first part must be the numeric ID from the query's edit URL, which was the answer in GitHub issue #324.
  • The dropdown lists other tours' dates. The current-tour filter isn't resolving to the tour where the form sits. Recheck the filter, and whether Enable Auto-Update is on for this field.
  • The total shows 0, NaN or a blank with no date chosen. The empty Departure value is breaking the formula. Wrap total_price in the Conditional Block described above.
  • The total stays at 0 after picking a date. The options have no calculated value: CALCULATED FIELD must hold the meta field name price_adult, not its label, and the departure needs a saved price.
  • A price shows before any date is picked. The Select has no placeholder, so its first option is selected. Add one if your version offers it, and make the chosen date clearly visible.
  • The total shows NaN or goes blank after editing. Look for a cleared Number field, a price stored with a currency sign or comma, a tour missing one of its price fields, or a form placed outside the single Tour template.
  • The total uses post IDs as prices. VALUE TYPE is set to as String, which uses each option's VALUE. Switch to as Number.
  • Auto-Update won't trigger from a Checkbox. Checkbox fields can't be a TRIGGER FIELD. Use a Select or Radio field.
  • WooCommerce charges the base price. Set WOOCOMMERCE PRICE FIELD in the JetBooking action to the Calculated Field.

Once the departures query and the formula check out on a real tour, adding a new tour only means entering its meta fields and departure posts. If you'd rather have this booking flow built for you, post a request on BoostMyCroco.

Frequently Asked Questions

Why does my JetFormBuilder Select show post IDs instead of dates?
The option label is probably set to ID or left empty. Set OPTION LABEL to post_title and title each Departure post as its date, such as 12 May 2027. Keep the option value as ID so the submission still records which departure was chosen.
Can I put the tour booking form on a regular page instead of the tour template?
Not with this formula. The META macros read fields from the post where the form is placed, so on a generic page they find no tour and the total breaks. Insert the form into the single Tour template so each tour page supplies its own prices.
Can I limit how many travellers book one departure?
You can cap each traveller field, and the Numbers range by meta value generator lets that cap follow max_travellers on each tour. A combined adults plus children limit needs your own validation, so check the head count before confirming. If you need live seat availability per departure, look at JetBooking.
Do I need JetBooking for a tour website?
Not for fixed departure dates with a per-person price and enquiry-style bookings. JetEngine and JetFormBuilder cover that with a query and a Calculated Field. JetBooking makes sense when you need date-range availability, inventory or a WooCommerce checkout tied to the booking.

This guide answers a question from our community: Tour operator functionality

Sources

  1. Multi-Optional Field Source Settings (jetformbuilder.com)
  2. Calculated Field Overview — JetFormBuilder (jetformbuilder.com)
  3. How to Set Automatic Price Calculator for Bookings — JetBooking (crocoblock.com)
  4. BUG / NOT WORKING - SELECT field -> Generate Dynamically > Get values list from JetEngine Query · Issue #324 · Crocoblock/jetformbuilder · GitHub (github.com)

Related tips