---
title: "Dreamforce 2026 - Book a session"
source: https://storeconnect.com/c/df-26
type: page
format: markdown
site: StoreConnect — Customer Commerce built natively on Salesforce
site_index: https://storeconnect.com/llms.txt
docs_index: https://support.storeconnect.com/llms.txt
note: Append .md to any page or article URL on this site to get its Markdown form.
---
# Dreamforce 2026 - Book a session

# Join us at Dreamforce in San Francisco, September 15-17, 2026

Reserve a focused 20-minute session to see how StoreConnect unifies payments, storefronts, subscriptions, inventory and service natively inside Salesforce.


<div
class=”df26”
data-df26
data-product-id=”01tRE00000EbWKFYA3”
data-availability-url=”/async/availabilities”
data-checkout-url=”/checkout”
data-range-start=”2026-09-15”
data-range-end=”2026-09-17”data-maps-key=”AIzaSyA6RzlGR7-gXR8ZOIPHsYS8cY5-rl8XDF4”
><header class="df26-hero">

When
15–17 September 2026

Where
Campground booth 317
Trailblazer Forest booth 10

Length
20 minutes

Cost
Free

</header><section class="df26-board" aria-labelledby="df26-board-title">
<div class="df26-board__head">
<h2 class="df26-board__title" id="df26-board-title">Pick a time</h2>

open / taken

</div><div class="df26-days" role="tablist" aria-label="Choose a day" data-df26-days hidden>
Tue 15
Wed 16
Thu 17
</div>

<div class="df26-board__rails" data-df26-rails aria-live="polite" aria-busy="true">
<p class="df26-loading">Loading available times…</p>
</div>

<noscript>
<p class="df26-empty">
Choosing a time needs JavaScript.
<a href="/products/2026-dreamforce-booth-booking">Open the standard booking page</a> instead.
</p>
</noscript>

</section><section class="df26-register" data-df26-register aria-labelledby="df26-register-title" hidden>
<div class="df26-register__inner">
<div class="df26-chosen" data-df26-chosen-wrap>
<p class="df26-chosen__label">Your session</p>
<p class="df26-chosen__value" data-df26-chosen></p>
Change time
</div>

<h2 class="df26-register__title" id="df26-register-title">Just your details</h2>
<p class="df26-register__lede">
Nothing to pay and no account needed. We only ask for what we need to meet you
properly.
</p>

<div class="df26-errors" data-df26-errors role="alert" hidden></div><form class="df26-form" data-df26-form="true" action="/products/01tRE00000EbWKFYA3/add" accept-charset="UTF-8" method="post"><input type="hidden" name="authenticity_token" value="zhEvNUMCJEtfxDCuPYhQY5UlvGZP3KBc9ogf8Mn1NKXcaKRc-HK8yzNhm5rqigGhv0ZNJ_X4wDMAlImvp9maCQ" /><input value="441ee201a30b090f40d836c7de3228cedb49e1d13768e74cb9f8bb0a3cc357ee" type="hidden" name="_ufid" id="_ufid" /><input type="hidden" name="product_bookable_location_id" data-df26-field="location" />
<input type="hidden" name="booking_start" data-df26-field="start" />
<input type="hidden" name="booking_end" data-df26-field="end" />
<input type="hidden" name="quantity" value="1" />

<div class="df26-grid">
<p class="df26-field df26-field--wide">
<label for="df26-name">Full name <span class="df26-req" aria-hidden="true">*</span></label>
<input type="text" id="df26-name" name="df26_full_name" required
autocomplete="name" enterkeyhint="next" />
</p>

<p class="df26-field">
<label for="df26-email">Email <span class="df26-req" aria-hidden="true">*</span></label>
<input type="email" id="df26-email" name="df26_email" required
autocomplete="email" inputmode="email" />
</p>

<p class="df26-field">
<label for="df26-phone">Phone <span class="df26-optional">optional</span></label>
<input type="tel" id="df26-phone" name="df26_phone" autocomplete="tel" inputmode="tel" />
</p>

<p class="df26-field df26-field--wide">
<label for="df26-company">Company <span class="df26-req" aria-hidden="true">*</span></label>
<input type="text" id="df26-company" name="df26_company" required
autocomplete="organization" />
</p><div class="df26-field df26-field--wide df26-address" data-df26-address>
<label for="df26-address-search">
Company address <span class="df26-req" aria-hidden="true">*</span>
</label>
<input type="text" id="df26-address-search" data-df26-address-search
placeholder="Start typing your company address…"
autocomplete="off" aria-describedby="df26-address-help" />
<span class="df26-hint" id="df26-address-help">
Pick a suggestion, or
<button type="button" class="df26-linkbtn" data-df26-address-manual>
enter it manually
</button>.
</span>

<div class="df26-address__detail" data-df26-address-detail hidden>
<p class="df26-field df26-field--wide">
<label for="df26-address">Street address</label>
<input type="text" id="df26-address" name="df26_address"
autocomplete="street-address" />
</p>
<p class="df26-field">
<label for="df26-city">City</label>
<input type="text" id="df26-city" name="df26_city" autocomplete="address-level2" />
</p>
<p class="df26-field">
<label for="df26-state">State / Province</label>
<input type="text" id="df26-state" name="df26_state" autocomplete="address-level1" />
</p>
<p class="df26-field">
<label for="df26-postcode">Postcode / ZIP</label>
<input type="text" id="df26-postcode" name="df26_postcode" autocomplete="postal-code" />
</p>
<p class="df26-field">
<label for="df26-country">Country</label><select id="df26-country" name="df26_country" autocomplete="country">
<option value="">Select a country</option><option value="AF">Afghanistan</option><option value="AL">Albania</option><option value="DZ">Algeria</option><option value="AS">American Samoa</option><option value="AD">Andorra</option><option value="AO">Angola</option><option value="AI">Anguilla</option><option value="AQ">Antarctica</option><option value="AG">Antigua and Barbuda</option><option value="AR">Argentina</option><option value="AM">Armenia</option><option value="AW">Aruba</option><option value="AU">Australia</option><option value="AT">Austria</option><option value="AZ">Azerbaijan</option><option value="BS">Bahamas (the)</option><option value="BH">Bahrain</option><option value="BD">Bangladesh</option><option value="BB">Barbados</option><option value="BY">Belarus</option><option value="BE">Belgium</option><option value="BZ">Belize</option><option value="BJ">Benin</option><option value="BM">Bermuda</option><option value="BT">Bhutan</option><option value="BO">Bolivia (Plurinational State of)</option><option value="BQ">Bonaire, Sint Eustatius and Saba</option><option value="BA">Bosnia and Herzegovina</option><option value="BW">Botswana</option><option value="BV">Bouvet Island</option><option value="BR">Brazil</option><option value="IO">British Indian Ocean Territory (the)</option><option value="BN">Brunei Darussalam</option><option value="BG">Bulgaria</option><option value="BF">Burkina Faso</option><option value="BI">Burundi</option><option value="CV">Cabo Verde</option><option value="KH">Cambodia</option><option value="CM">Cameroon</option><option value="CA">Canada</option><option value="KY">Cayman Islands (the)</option><option value="CF">Central African Republic (the)</option><option value="TD">Chad</option><option value="CL">Chile</option><option value="CN">China</option><option value="CX">Christmas Island</option><option value="CC">Cocos (Keeling) Islands (the)</option><option value="CO">Colombia</option><option value="KM">Comoros (the)</option><option value="CD">Congo (the Democratic Republic of the)</option><option value="CG">Congo (the)</option><option value="CK">Cook Islands (the)</option><option value="CR">Costa Rica</option><option value="HR">Croatia</option><option value="CU">Cuba</option><option value="CW">Curaçao</option><option value="CY">Cyprus</option><option value="CZ">Czechia</option><option value="CI">Côte d&#39;Ivoire</option><option value="DK">Denmark</option><option value="DJ">Djibouti</option><option value="DM">Dominica</option><option value="DO">Dominican Republic (the)</option><option value="EC">Ecuador</option><option value="EG">Egypt</option><option value="SV">El Salvador</option><option value="GQ">Equatorial Guinea</option><option value="ER">Eritrea</option><option value="EE">Estonia</option><option value="SZ">Eswatini</option><option value="ET">Ethiopia</option><option value="FK">Falkland Islands (the) [Malvinas]</option><option value="FO">Faroe Islands (the)</option><option value="FJ">Fiji</option><option value="FI">Finland</option><option value="FR">France</option><option value="GF">French Guiana</option><option value="PF">French Polynesia</option><option value="TF">French Southern Territories (the)</option><option value="GA">Gabon</option><option value="GM">Gambia (the)</option><option value="GE">Georgia</option><option value="DE">Germany</option><option value="GH">Ghana</option><option value="GI">Gibraltar</option><option value="GR">Greece</option><option value="GL">Greenland</option><option value="GD">Grenada</option><option value="GP">Guadeloupe</option><option value="GU">Guam</option><option value="GT">Guatemala</option><option value="GG">Guernsey</option><option value="GN">Guinea</option><option value="GW">Guinea-Bissau</option><option value="GY">Guyana</option><option value="HT">Haiti</option><option value="HM">Heard Island and McDonald Islands</option><option value="VA">Holy See (the)</option><option value="HN">Honduras</option><option value="HK">Hong Kong</option><option value="HU">Hungary</option><option value="IS">Iceland</option><option value="IN">India</option><option value="ID">Indonesia</option><option value="IR">Iran (Islamic Republic of)</option><option value="IQ">Iraq</option><option value="IE">Ireland</option><option value="IM">Isle of Man</option><option value="IL">Israel</option><option value="IT">Italy</option><option value="JM">Jamaica</option><option value="JP">Japan</option><option value="JE">Jersey</option><option value="JO">Jordan</option><option value="KZ">Kazakhstan</option><option value="KE">Kenya</option><option value="KI">Kiribati</option><option value="KP">Korea (the Democratic People&#39;s Republic of)</option><option value="KR">Korea (the Republic of)</option><option value="KW">Kuwait</option><option value="KG">Kyrgyzstan</option><option value="LA">Lao People&#39;s Democratic Republic (the)</option><option value="LV">Latvia</option><option value="LB">Lebanon</option><option value="LS">Lesotho</option><option value="LR">Liberia</option><option value="LY">Libya</option><option value="LI">Liechtenstein</option><option value="LT">Lithuania</option><option value="LU">Luxembourg</option><option value="MO">Macao</option><option value="MG">Madagascar</option><option value="MW">Malawi</option><option value="MY">Malaysia</option><option value="MV">Maldives</option><option value="ML">Mali</option><option value="MT">Malta</option><option value="MH">Marshall Islands (the)</option><option value="MQ">Martinique</option><option value="MR">Mauritania</option><option value="MU">Mauritius</option><option value="YT">Mayotte</option><option value="MX">Mexico</option><option value="FM">Micronesia (Federated States of)</option><option value="MD">Moldova (the Republic of)</option><option value="MC">Monaco</option><option value="MN">Mongolia</option><option value="ME">Montenegro</option><option value="MS">Montserrat</option><option value="MA">Morocco</option><option value="MZ">Mozambique</option><option value="MM">Myanmar</option><option value="NA">Namibia</option><option value="NR">Nauru</option><option value="NP">Nepal</option><option value="NL">Netherlands (the)</option><option value="NC">New Caledonia</option><option value="NZ">New Zealand</option><option value="NI">Nicaragua</option><option value="NE">Niger (the)</option><option value="NG">Nigeria</option><option value="NU">Niue</option><option value="NF">Norfolk Island</option><option value="MK">North Macedonia</option><option value="MP">Northern Mariana Islands (the)</option><option value="NO">Norway</option><option value="OM">Oman</option><option value="PK">Pakistan</option><option value="PW">Palau</option><option value="PS">Palestine, State of</option><option value="PA">Panama</option><option value="PG">Papua New Guinea</option><option value="PY">Paraguay</option><option value="PE">Peru</option><option value="PH">Philippines (the)</option><option value="PN">Pitcairn</option><option value="PL">Poland</option><option value="PT">Portugal</option><option value="PR">Puerto Rico</option><option value="QA">Qatar</option><option value="RO">Romania</option><option value="RU">Russian Federation (the)</option><option value="RW">Rwanda</option><option value="RE">Réunion</option><option value="BL">Saint Barthélemy</option><option value="SH">Saint Helena, Ascension and Tristan da Cunha</option><option value="KN">Saint Kitts and Nevis</option><option value="LC">Saint Lucia</option><option value="MF">Saint Martin (French part)</option><option value="PM">Saint Pierre and Miquelon</option><option value="VC">Saint Vincent and the Grenadines</option><option value="WS">Samoa</option><option value="SM">San Marino</option><option value="ST">Sao Tome and Principe</option><option value="SA">Saudi Arabia</option><option value="SN">Senegal</option><option value="RS">Serbia</option><option value="SC">Seychelles</option><option value="SL">Sierra Leone</option><option value="SG">Singapore</option><option value="SX">Sint Maarten (Dutch part)</option><option value="SK">Slovakia</option><option value="SI">Slovenia</option><option value="SB">Solomon Islands</option><option value="SO">Somalia</option><option value="ZA">South Africa</option><option value="GS">South Georgia and the South Sandwich Islands</option><option value="SS">South Sudan</option><option value="ES">Spain</option><option value="LK">Sri Lanka</option><option value="SD">Sudan (the)</option><option value="SR">Suriname</option><option value="SJ">Svalbard and Jan Mayen</option><option value="SE">Sweden</option><option value="CH">Switzerland</option><option value="SY">Syrian Arab Republic (the)</option><option value="TW">Taiwan (Province of China)</option><option value="TJ">Tajikistan</option><option value="TZ">Tanzania, the United Republic of</option><option value="TH">Thailand</option><option value="TL">Timor-Leste</option><option value="TG">Togo</option><option value="TK">Tokelau</option><option value="TO">Tonga</option><option value="TT">Trinidad and Tobago</option><option value="TN">Tunisia</option><option value="TR">Turkey</option><option value="TM">Turkmenistan</option><option value="TC">Turks and Caicos Islands (the)</option><option value="TV">Tuvalu</option><option value="UG">Uganda</option><option value="UA">Ukraine</option><option value="AE">United Arab Emirates (the)</option><option value="GB">United Kingdom of Great Britain and Northern Ireland (the)</option><option value="UM">United States Minor Outlying Islands (the)</option><option value="US">United States of America (the)</option><option value="UY">Uruguay</option><option value="UZ">Uzbekistan</option><option value="VU">Vanuatu</option><option value="VE">Venezuela (Bolivarian Republic of)</option><option value="VN">Viet Nam</option><option value="VG">Virgin Islands (British)</option><option value="VI">Virgin Islands (U.S.)</option><option value="WF">Wallis and Futuna</option><option value="EH">Western Sahara*</option><option value="YE">Yemen</option><option value="ZM">Zambia</option><option value="ZW">Zimbabwe</option><option value="AX">Åland Islands</option></select>
</p>
</div>
</div><p class="df26-field df26-field--wide">
<label for="df26-q-a4lRE00000khaNVYAY">
What would you like to talk to us about? <span class="df26-req" aria-hidden="true">*</span></label><textarea id="df26-q-a4lRE00000khaNVYAY" rows="3"
name="answers[a4lRE00000khaNVYAY][answer]"
required
placeholder="A sentence is plenty — it just helps us bring the right person."></textarea></p></div>

<button type="submit" class="df26-submit" data-df26-submit>
<span class="df26-submit__label">Confirm my session</span>
<span class="df26-submit__spin" aria-hidden="true"></span>
</button>

<p class="df26-fineprint">
By confirming you agree to our
<a href="/privacy-policy">privacy policy</a>.
We will email you a calendar invite straight away.
</p></form></div>

</section><div class="df26-steps" data-df26-steps hidden aria-hidden="true"><form data-df26-step="customer" action="/checkout/customer_information" accept-charset="UTF-8" method="post">

</form><form data-df26-step="terms" action="/checkout/accept_terms" accept-charset="UTF-8" method="post"></form><form data-df26-step="pay" action="/checkout/payment_information" accept-charset="UTF-8" method="post"></form></div>

</div>

var root = document.querySelector(‘[data-df26]’)
if (!root) return

var railsEl = root.querySelector(‘[data-df26-rails]’)
var daysEl = root.querySelector(‘[data-df26-days]’)
var registerEl = root.querySelector(‘[data-df26-register]’)
var chosenEl = root.querySelector(‘[data-df26-chosen]’)
var errorsEl = root.querySelector(‘[data-df26-errors]’)
var formEl = root.querySelector(‘[data-df26-form]’)
var submitEl = root.querySelector(‘[data-df26-submit]’)
var stepsEl = root.querySelector(‘[data-df26-steps]’)

var CHECKOUT = root.dataset.checkoutUrl | 
  | 
‘/checkout’ | 

function csrf() {
var tag = document.querySelector(‘meta[name=”csrf-token”]’)
return tag ? tag.content : ‘’
}

function setField(name, value) {
var el = formEl.querySelector(‘[data-df26-field=”’ + name + ‘”]’)
if (el) el.value = value
}

// —————————————————————— board

function availabilityEndpoint() {
// The real route is /async/availabilities; current_store.booking_availabilities_path
// supplies it, and this fallback only matters if that ever renders blank.
var base = root.dataset.availabilityUrl || ‘/async/availabilities’
return base +
‘?product_id=’ + encodeURIComponent(root.dataset.productId) +
‘&start_date=’ + encodeURIComponent(root.dataset.rangeStart) +
‘&end_date=’ + encodeURIComponent(root.dataset.rangeEnd)
}

function loadBoard() {
fetch(availabilityEndpoint(), { headers: { Accept: ‘text/html’ } })
.then(function (res) {
if (!res.ok) throw new Error(res.statusText)
return res.text()
})
.then(function (html) {
railsEl.innerHTML = html
railsEl.setAttribute(‘aria-busy’, ‘false’)
wireSlots()
wireDays()
revealStagger()
})
.catch(function () {
railsEl.setAttribute(‘aria-busy’, ‘false’)
railsEl.innerHTML =
‘<p class="df26-empty">We could not load the times just now. ‘ +
‘Try again</p>’
var retry = railsEl.querySelector(‘[data-df26-retry]’)
if (retry) retry.addEventListener(‘click’, loadBoard)
})
}

function wireSlots() {
railsEl.querySelectorAll(‘[data-df26-slot]’).forEach(function (input) {
input.addEventListener(‘change’, function () {
if (input.disabled) return
setField(‘location’, input.dataset.location)
setField(‘start’, input.dataset.start)
setField(‘end’, input.dataset.end)
chosenEl.textContent = input.dataset.label || ‘’
if (registerEl.hidden) {
initAddress()
scValidate.live(validationRules())
}
registerEl.hidden = false
registerEl.scrollIntoView({ behavior: ‘smooth’, block: ‘start’ })
var first = formEl.querySelector(‘#df26-name’)
if (first) window.setTimeout(function () { first.focus({ preventScroll: true }) }, 400)
})
})
}

function wireDays() {
var rails = Array.prototype.slice.call(railsEl.querySelectorAll(‘[data-df26-rail]’))
if (!rails.length || !daysEl) return

// Tabs only carry meaning at the width where rails collapse to one column.
var narrow = window.matchMedia('(max-width: 60rem)')
var buttons = Array.prototype.slice.call(daysEl.querySelectorAll('[data-df26-day]'))

function show(date) {
rails.forEach(function (rail) { rail.hidden = rail.dataset.date !== date })
buttons.forEach(function (b) {
b.setAttribute('aria-selected', String(b.dataset.df26Day === date))
})
}

function showAll() {
rails.forEach(function (rail) { rail.hidden = false })
}

function sync() {
if (narrow.matches) {
daysEl.hidden = false
var active = buttons.filter(function (b) { return b.getAttribute('aria-selected') === 'true' })[0]
show(active ? active.dataset.df26Day : rails[0].dataset.date)
} else {
daysEl.hidden = true
showAll()
}
}

buttons.forEach(function (b) {
b.addEventListener('click', function () { show(b.dataset.df26Day) })
})

if (narrow.addEventListener) narrow.addEventListener('change', sync)
else narrow.addListener(sync)
sync() }

function revealStagger() {
if (window.matchMedia(‘(prefers-reduced-motion: reduce)’).matches) return
railsEl.querySelectorAll(‘.df26-rail’).forEach(function (rail, i) {
rail.animate(
[{ opacity: 0, transform: ‘translateY(0.75rem)’ }, { opacity: 1, transform: ‘none’ }],
{ duration: 420, delay: i * 90, easing: ‘cubic-bezier(.2,.7,.3,1)’, fill: ‘backwards’ }
)
})
}

// —————————————————————- address

// Loaded lazily on first slot selection, not at page load: most visitors read
// the board and leave, and Places is billed per session.
var mapsPromise = null

function loadMaps() {
if (mapsPromise) return mapsPromise
var key = root.dataset.mapsKey
if (!key) return (mapsPromise = Promise.reject(new Error(‘no key’)))

mapsPromise = new Promise(function (resolve, reject) {
// Resolve from Google's own callback, not from the script's onload.
//
// loading=async defers API bootstrap past the load event, so at onload
// window.google and google.maps both exist but google.maps.importLibrary
// is still undefined - measured on the live store, it appears ~1s later.
// Resolving on onload therefore ran the guard below too early every time,
// threw, and dropped every visitor to manual entry. callback= is the
// documented signal that the API is actually initialised.
var name = 'df26MapsReady'
var done = false

var finish = function (fn, arg) {
if (done) return
done = true
try { delete window[name] } catch (e) { window[name] = undefined }
fn(arg)
}

window[name] = function () { finish(resolve) }

var s = document.createElement('script')
s.src = 'https://maps.googleapis.com/maps/api/js?libraries=places&loading=async' +
'&callback=' + name +
'&key=' + encodeURIComponent(key)
s.async = true
s.onerror = function () { finish(reject, new Error('maps failed')) }
document.head.appendChild(s)

// If the callback never fires - blocked key, offline, ad blocker eating the
// request without an error event - fall through to manual entry rather than
// leaving a search box that silently does nothing.
window.setTimeout(function () { finish(reject, new Error('maps timeout')) }, 8000)
})
return mapsPromise }

function addressParts(place) {
var out = { street: ‘’, city: ‘’, state: ‘’, postcode: ‘’, country: ‘’ }
var number = ‘’, route = ‘’
;(place.address_components || []).forEach(function (c) {
var t = c.types
if (t.indexOf(‘street_number’) > -1) number = c.long_name
else if (t.indexOf(‘route’) > -1) route = c.long_name
else if (t.indexOf(‘postal_town’) > -1 && !out.city) out.city = c.long_name
else if (t.indexOf(‘locality’) > -1) out.city = c.long_name
else if (t.indexOf(‘administrative_area_level_1’) > -1) out.state = c.short_name
else if (t.indexOf(‘postal_code’) > -1) out.postcode = c.long_name
else if (t.indexOf(‘country’) > -1) out.country = c.short_name
})
// Fall back to the locality-level name when a place has no street component.
out.street = [number, route].filter(Boolean).join(‘ ‘) || (place.name || ‘’)
return out
}

function setAddress(parts) {
var set = function (id, v) {
var el = formEl.querySelector(‘#’ + id)
if (el && v) el.value = v
}
set(‘df26-address’, parts.street)
set(‘df26-city’, parts.city)
set(‘df26-state’, parts.state)
set(‘df26-postcode’, parts.postcode)

var country = formEl.querySelector('#df26-country')
if (country && parts.country) country.value = parts.country
showAddressDetail() }

function showAddressDetail() {
var detail = root.querySelector(‘[data-df26-address-detail]’)
if (detail) detail.hidden = false
}

function initAddress() {
var search = root.querySelector(‘[data-df26-address-search]’)
var manual = root.querySelector(‘[data-df26-address-manual]’)
if (manual) manual.addEventListener(‘click’, showAddressDetail)
if (!search) return

// Enter in the search box means "pick a suggestion", never "submit the form".
search.addEventListener('keydown', function (e) {
if (e.key === 'Enter') e.preventDefault()
})

loadMaps()
.then(function () {
// With loading=async the script's onload fires before the places namespace
// exists, so checking google.maps.places here always failed and dropped
// straight to manual entry. importLibrary is the supported wait.
if (!(window.google && google.maps && google.maps.importLibrary)) {
throw new Error('no maps')
}
return google.maps.importLibrary('places')
})
.then(function () {
if (!(google.maps.places && google.maps.places.Autocomplete)) {
throw new Error('no places')
}
var ac = new google.maps.places.Autocomplete(search, {
fields: ['address_components', 'name'],
types: ['geocode', 'establishment']
})
ac.addListener('place_changed', function () {
var place = ac.getPlace()
if (place && place.address_components) setAddress(addressParts(place))
else showAddressDetail()
})
})
.catch(function () {
// No key, blocked, or the API rejected us: fall back to plain manual entry
// rather than leaving someone with a search box that does nothing.
search.hidden = true
var help = root.querySelector('#df26-address-help')
if (help) help.hidden = true
var label = root.querySelector('label[for="df26-address-search"]')
if (label) label.hidden = true
showAddressDetail()
}) }

// ———————————————————— validation

function field(id) {
return formEl.querySelector(‘#’ + id)
}

// Phone stays optional; everything else is needed for checkout to accept the
// order. Country is a , so presence is the only meaningful check.
function validationRules() {
return [
{ el: field(‘df26-name’), type: ‘text’, required: true, min: 2, label: ‘full name’ },
{ el: field(‘df26-email’), type: ‘email’, required: true, label: ‘email address’ },
{ el: field(‘df26-phone’), type: ‘phone’, required: false, label: ‘phone number’ },
{ el: field(‘df26-company’), type: ‘text’, required: true, label: ‘company name’ },
{ el: field(‘df26-address’), type: ‘text’, required: true, label: ‘street address’ },
{ el: field(‘df26-city’), type: ‘text’, required: true, label: ‘city’ },
{ el: field(‘df26-state’), type: ‘text’, required: true, label: ‘state or province’ },
{ el: field(‘df26-postcode’), type: ‘postcode’, required: true, label: ‘postcode’ },
{ el: field(‘df26-country’), type: ‘text’, required: true, label: ‘country’ }
]
}

function validatedElements() {
return validationRules().map(function (r) { return r.el })
}

// ———————————————————— express run

function showErrors(message, list) {
errorsEl.hidden = false
errorsEl.innerHTML =
‘’ + message + ‘’ +
(list && list.length
? ‘<ul>’ + list.map(function (e) { return ‘<li>’ + e + ‘</li>’ }).join(‘’) + ‘</ul>’
: ‘’)
errorsEl.scrollIntoView({ behavior: ‘smooth’, block: ‘center’ })
}

function busy(state) {
submitEl.disabled = state
submitEl.classList.toggle(‘is-busy’, state)
submitEl.querySelector(‘.df26-submit__label’).textContent =
state ? ‘Confirming…’ : ‘Confirm my session’
}

function post(form, mutate) {
var data = new FormData(form)
if (mutate) mutate(data)
return fetch(form.action, {
method: ‘POST’,
headers: { ‘X-CSRF-Token’: csrf(), Accept: ‘application/json’ },
body: new URLSearchParams(data),
credentials: ‘same-origin’
}).then(function (res) {
return res.json().catch(function () { return {} }).then(function (body) {
return { ok: res.ok, body: body }
})
})
}

// A redirect alone does NOT mean the step succeeded. Every checkout step
// redirects on failure too - back to itself, carrying an alert - so treating
// “has redirect” as success made a rejected address look like progress and the
// chain then failed confusingly at the following step. The alert is the signal.
function advanced(result) {
if (!result.ok || !result.body || !result.body.redirect) return false
return !result.body.alert
}

// Whatever the server actually complained about, so the visitor sees the real
// reason rather than our guess at it.
function reason(result, fallback) {
return (result && result.body && (result.body.alert || result.body.notice)) || fallback
}

function stepForm(name) {
return stepsEl.querySelector(‘[data-df26-step=”’ + name + ‘”]’)
}

// Which hidden form answers a given checkout URL.
function stepFor(url) {
if (!url) return null
if (url.indexOf(‘customer_information’) > -1) return ‘customer’
if (url.indexOf(‘accept_terms’) > -1) return ‘terms’
if (url.indexOf(‘payment_information’) > -1) return ‘pay’
return null
}

/*
Walk the checkout by following the server’s own redirects rather than
assuming customer -> terms -> payment. Which steps exist is per-store
configuration: this store sends customer_information straight to
payment_information, so posting accept_terms in between simply failed. A
store that turns terms back on, or enables shipping, then works unchanged.
*/
function follow(redirect, depth) {
if (depth > 6) return bail(‘The booking took too many steps to finish.’)

// Any order URL means the checkout completed.
if (/\/(checkout\/order|order|orders)\b/.test(redirect)) {
window.location.href = redirect
return
}

var name = stepFor(redirect)
// An unrecognised step (shipping, say) is one this page has no form for, so
// hand the visitor over rather than guessing.
if (!name) return bail('This booking needs a couple more details.')

var form = stepForm(name)
if (!form) return bail('This booking needs a couple more details.')

return post(form).then(function (res) {
if (!advanced(res)) return bail(reason(res, 'We could not finish the booking.'))
return follow(res.body.redirect, depth + 1)
}) }

function fillCustomer() {
var form = stepForm(‘customer’)
if (!form) return null

var read = function (id) {
var el = formEl.querySelector('#' + id)
return el ? el.value.trim() : ''
}

var name = read('df26-name')
var values = {
full_name: name,
email: read('df26-email'),
phone: read('df26-phone'),
company: read('df26-company'),
address: read('df26-address'),
city: read('df26-city'),
state: read('df26-state'),
postcode: read('df26-postcode'),
country: read('df26-country')
}

// Stores configured for separate name fields need the split done here.
var cut = name.lastIndexOf(' ')
values.first_name = cut > 0 ? name.slice(0, cut) : name
values.last_name = cut > 0 ? name.slice(cut + 1) : ''

form.querySelectorAll('[data-df26-map]').forEach(function (input) {
input.value = values[input.dataset.df26Map] || ''
})
return form }

function bail(reason) {
// The slot is already held in the cart, so handing over to the standard
// checkout is always safe and never costs the visitor their time.
busy(false)
showErrors(
reason,
[‘Your time slot is held in your cart. Taking you to checkout to finish.’]
)
window.setTimeout(function () { window.location.href = CHECKOUT }, 1800)
}

formEl.addEventListener(‘submit’, function (event) {
event.preventDefault()
errorsEl.hidden = true

if (!formEl.querySelector('[data-df26-field="start"]').value) {
showErrors('Choose a time first.', [])
return
}
if (!formEl.reportValidity()) return

// The address inputs cannot use `required` (they start hidden, and the browser
// refuses to submit when a required control is not focusable), so they and the
// format-sensitive fields go through the shared validator instead.
var problems = scValidate.check(validationRules())
scValidate.clear(validatedElements())
if (problems.length) {
showAddressDetail()
scValidate.mark(problems)
showErrors('Please check the highlighted fields.', problems.map(function (p) {
return p.message
}))
problems[0].el.focus()
return
}

busy(true)

post(formEl)
.then(function (added) {
if (!advanced(added)) {
busy(false)
showErrors(
'That time could not be held.',
[(added.body && added.body.alert) || 'It may have just been taken. Please pick another.']
)
loadBoard()
throw new Error('add-to-cart')
}

var customer = fillCustomer()
if (!customer) throw new Error('no-customer-step')
return post(customer)
})
.then(function (res) {
// A rejected address is the visitor's to fix, so keep them here with the
// server's own wording instead of bouncing them to checkout.
if (!advanced(res)) {
busy(false)
showAddressDetail()
showErrors(
'We could not save your details.',
[reason(res, 'Please check your name, email and company address.')]
)
throw new Error('customer')
}
return follow(res.body.redirect, 0)
})
.catch(function (err) {
// These two already reported themselves and left the visitor on the page.
if (err && (err.message === 'add-to-cart' || err.message === 'customer')) return
bail('Something went wrong confirming your session.')
}) })

root.querySelectorAll(‘[data-df26-change]’).forEach(function (btn) {
btn.addEventListener(‘click’, function () {
registerEl.hidden = true
railsEl.scrollIntoView({ behavior: ‘smooth’, block: ‘start’ })
})
})

loadBoard()
})()
</script>

---

## Follow StoreConnect

- [Email Newsletter](https://storeconnect.com/c/lp-newsletter)
- [LinkedIn Newsletter](https://www.linkedin.com/build-relation/newsletter-follow?entityUrn=7444956928444862464)
- [YouTube](https://www.youtube.com/channel/UCngKdP2x8l1wcbAKW3tvU8g)
- [LinkedIn](https://www.linkedin.com/company/storeconnect)
- [X / Twitter](https://x.com/storeconnecthq)

## Popular Links

- [Partners](https://storeconnect.com/partners)
- [Become a Partner](https://storeconnect.com/become-a-partner)
- [News](https://storeconnect.com/articles/news)
- [Events](https://storeconnect.com/articles/events)
- [Live Events](https://storeconnect.com/live-events)
- [Feature Comparison](https://storeconnect.com/how-we-compare)
- [Download a free trial](https://appexchange.salesforce.com/appxListingDetail?listingId=a0N3A00000FMkeKUAT)
- [Book a Demo](https://storeconnect.com/contact)

## Documentation

- [Help documentation](https://support.storeconnect.com/help-documentation)
- [Videos & tutorials](https://support.storeconnect.com/videos-tutorials)
- [Developer reference](https://support.storeconnect.com/developer-reference)
- [Release notes](https://support.storeconnect.com/release-notes)
- [Troubleshooting](https://support.storeconnect.com/troubleshooting)
- [Trust Center](https://trust.getstoreconnect.com/)
- [Status Page](https://status.storeconnect.com/)

## Contact

- info@getstoreconnect.com
- US +1 415 745 3230
- AUS +61 2 8365 2308

100 S Ashley Dr, Suite 600-2461
Tampa FL 33602-600 USA

Level 22, Sydney Place
180 George Street
Sydney, NSW, 2000, AUS

## Machine-readable

- [Site index for agents](https://storeconnect.com/llms.txt): curated map of this site in llms.txt format
- [Documentation index for agents](https://support.storeconnect.com/llms.txt): full technical and product documentation map

Every page and article on this site has a Markdown rendering: append `.md` to its URL.

Continue in Markdown: [Home](https://storeconnect.com/home.md) · [How we compare](https://storeconnect.com/how-we-compare.md) · [Partners](https://storeconnect.com/partners.md) · [Become a partner](https://storeconnect.com/become-a-partner.md)

---

StoreConnect — https://storeconnect.com/c/df-26