Skip to main content

Registration/Booking widget

This widget is used to place the registration / booking form on your website. You can place as many instances of this widget as you wish, there is no limit on this. Each form can have many of its settings overridden per instance. This allows you to pre-filter the classes that are listed in the form, or change its appearance and behaviour based on the use case you're solving.

Installation

WordPress

When your WordPress plugin is installed, just head to Settings > Zooza and from the dropdown of pages, select a page where you want the form to appear.

Shortcodes

You can also use shortcodes to place the form anywhere within the page. More configuration options for shortcodes is described below in their respective sections.

[zooza type="registration"]

Wix

In Wix editor, click on Zooza widget. In the Settings panel, enter the api key and as a widget choose Registration.

Embed code

Place the following snippet directly into the <body> of your page, where you want the booking form to appear.

PlaceholderDescriptionExample Value
YOUR_API_KEYReplace with the API key found in the application under Publish > Widget. Appears twice.abc123xyz
ZOOZA_API_URLReplace with the Zooza API URL for your region: Europe: https://api.zooza.app, UK: https://uk.api.zooza.app, UAE: https://asia.api.zooza.apphttps://api.zooza.app
<script data-version='v1' data-widget-id='zooza' id='YOUR_API_KEY' type='text/javascript'>
( function() {
function async_load(){
document.body.setAttribute('data-zooza-api-url', 'ZOOZA_API_URL');
var s = document.createElement('script'); s.type = 'text/javascript'; s.async = true;
s.src = document.body.getAttribute('data-zooza-api-url') +
'/widgets/v1/?type=registration&ref=' + encodeURIComponent( window.location.href );
var embedder = document.getElementById( 'YOUR_API_KEY' );
embedder.parentNode.insertBefore( s, embedder );
}
if ( window.attachEvent ) {
window.attachEvent( 'onload', async_load );
} else {
window.addEventListener( 'load', async_load, false );
}
} )();
</script>

Settings

These settings are managed within the Zooza's main application Publish > Widget > Registration Form.

URL

Probably the most important setting there is. This will let Zooza know where your widget resides so that it can redirect your customers to it when necessary.

Availability

This will affect how the availability information is shown for classes.

ValueDescriptionExample Value
Do not showAvailability information will be hiddenn/a
Current statusThis will show exact real-time numbered value, thus revealing how many users are already booked out of available capacity10/10
Text information (Default)This will show text version of the aboveLast spot available

Submit button

ValueDescriptionExample Value
Register (Default)Default optionRegister
Register with obligation to payMore intensive version of the default valueRegister with obligation to pay
Custom text

If you need custom text, refer to the translations section of this reference.

Price display for payment schedules

When the widget displays a list of available classes in this overview, it also lists the class price. If you sell courses that require recurring billing, you often don't want to show your customers the total price of the class at this point as it may be irrelevant at this stage. You can turn it off with this setting.

ValueDescriptionExample Value
Total course price (default)Default option300 €
Hide priceIf class has payment schedule enabled the price won't be listedn/a

Course list display

By default, the widget renders the list of available courses as a <select> dropdown. If you have a small number of courses and want to make them more visually prominent, you can switch to a built-in tile grid. The grid is unstyled beyond a minimal layout — your site's CSS controls the look.

ValueDescriptionExample Value
Dropdown (Default)Courses appear as a single <select>. Best when you have many courses or want the most compact form.Course A / Course B / …
GridCourses appear as tiles in a configurable column grid. Each tile shows the course name, the first 5 lines of its description, and a Select button.2 × 2 grid of tiles
Per-page override

The setting above is the tenant-wide default. Individual pages can override it via window.ZOOZA or URL query — useful when one landing page wants a grid and another wants the dropdown without flipping the tenant setting.

<script>
window.ZOOZA = {
course_list_display: 'grid'
}
</script>

Accepted values: 'select', 'grid'. Anything else is ignored and the tenant setting is used.

Need a fully custom layout?

If the built-in grid doesn't match your design, register the render_course_tile callback to take over rendering of each tile. The widget falls back to the value of this setting whenever the callback isn't registered. Registering render_course_tile forces grid mode regardless of the tenant setting or any per-page override.

Course grid columns

Only applies when Course list display is set to Grid. Picks the number of columns the grid renders. Use 1 for a single-column list, 2–4 for a wider layout. The setting persists even when you switch the display back to Dropdown, so toggling between modes doesn't lose your column preference.

ValueDescriptionExample Value
1 (Default)Single column — equivalent to a vertical list.Stacked tiles
2Two columns.2 × N grid
3Three columns.3 × N grid
4Four columns.4 × N grid
Per-page override

The setting above is the tenant-wide default. Individual pages can override it via window.ZOOZA or URL query.

<script>
window.ZOOZA = {
course_list_columns: 3
}
</script>

Accepted values: 1, 2, 3, 4. Values outside this range fall back to 1.

Transfer the website visitor to the form

This is useful if the registration form is not on the top of the page and users will need to scroll to see it. By default this is turned off.

Hide field for discount codes

This will hide the discount code form field in the booking form. By default this is turned on.

Use CSS

This will load default Zooza styling. By default this is turned on. Typically you only want to override couple of styles but if you want, you can turn this off and create your own styling from scratch. However we recommend downloading the default styling and go from there, instead of building everything from scratch.

You can download the default css from this URL:

API_URL/widgets/v1/css/?widget=YOUR_API_KEY&type=registration

See valid options for API_URL and YOUR_API_KEY above.

Initialisation options

In general, you can customise how the widget is initialized either via JavaScript or via URL query parameters. Due to legacy reasons, some of the parameters are defined into document.zooza and some into window.ZOOZA. It is always shown along each property which way to use. However the concept stays the same: insert the script tag before the embed code.

Customising the course and schedule lists

The widget gives you three ways to control how courses and classes appear:

  1. Admin Settings (with per-page override)Course list display and Course grid columns. Tenant-wide defaults, also overridable per page via window.ZOOZA / URL.
  2. course_list_collapse_on_select / schedule_list_collapse_on_select — JS or URL-query flags for keeping all tiles visible after a selection.
  3. render_course_tile / render_schedule_tile callbacks — return the inside of a single tile; the widget keeps owning the shell, click delegation, and collapse behaviour.

Callbacks always win over admin settings — registering render_course_tile forces grid mode even if Course list display is set to Dropdown.

filter_courses

Type: Array, String

This will allow you to limit which courses are shown in the booking form.

ValueDescriptionExample Value
YOUR_COURSE_IDArray of course ids.[ 123, 1234 ] For WordPress see note in its tab.
<script>
document.zooza = {
filter_courses: [ YOUR_COURSE_ID ]
}
</script>

course_ids

Type: Array, String

This is the same as filter_courses but instead it is defined like this:

<script>
document.zooza = {
course_ids: [ YOUR_COURSE_ID ]
}
</script>

course_id

Type: Integer

Use this in URL query to filter classes by course. Only single course ID is allowed. You can combine this with schedule_id and place_id to preselect concrete class.

ValueDescriptionExample Value
YOUR_COURSE_IDCourse ID.123
https://sample-site.com/registration?course_id=YOUR_COURSE_ID

filter_places

Type: Array, String

This will limit the displayed classes based on given location(s). This filter uses place id thus doesn't allow for filtering to a classroom level.

ValueDescriptionExample Value
YOUR_PLACE_IDArray of place ids.[ 123, 1234 ] For WordPress see note in its tab.
<script>
document.zooza = {
filter_places: [ YOUR_PLACE_ID ]
}
</script>

place_id

Type: String

Use this in URL query to filter classes by place. Only single place ID is allowed. You can combine this with schedule_id and course_id to preselect concrete class.

ValueDescriptionExample Value
YOUR_PIDPID consists of place_id and a room_id. They are concatenated using underscore. If you don't want to specify room, just use zero (123_0)123_123
https://sample-site.com/registration?place_id=YOUR_PID

schedule_id

Type: Integer

Use this in URL query to filter classes by their ID. Only single class ID is allowed. You NEED to combine this with place_id and course_id to preselect concrete class. All three parameters need to be present if you want to select concrete class.

ValueDescriptionExample Value
YOUR_CLASS_IDClass id123
https://sample-site.com/registration?schedule_id=YOUR_SCHEDULE_ID

labels_in

Type: Array of strings, or pipe-delimited string

Show only courses tagged with at least one of the listed labels. Values are label names as shown in the admin UI — case-sensitive, including spaces and punctuation (e.g. "Summer 2026", "Kids — beginners"). Label ids are not accepted here; the legacy id-based label filter is a separate, untouched mechanism.

Composes with labels_not_in (AND), and with course_ids / filter_places / schedule_id (also AND). An empty array, empty string, or omitted value means no filter.

This option works alongside the tile-grid course list and the render_course_tile callback — label filtering is applied first, then the surviving courses are rendered in whatever display mode you've chosen.

Private labels are silently invisible

Private (admin-only) labels never match for embedders. There is no client-visible difference between "label doesn't exist" and "label exists but is private" — both produce zero matches. If a label is unpublished after the widget is embedded, the courses tagged with it will silently disappear from the list.

<script>
window.ZOOZA = {
labels_in: [ "Summer 2026", "Kids — beginners" ]
}
</script>

labels_not_in

Type: Array of strings, or pipe-delimited string

Hide courses tagged with any of the listed labels. Values are label names as shown in the admin UI — case-sensitive, including spaces and punctuation. Label ids are not accepted.

Composes with labels_in (AND), and with course_ids / filter_places / schedule_id. An empty array, empty string, or omitted value means no filter.

<script>
window.ZOOZA = {
labels_not_in: [ "Archived", "Internal" ]
}
</script>

metadata_in

Type: Object — per-key value is an array of strings or pipe-delimited string

Show only courses whose metadata matches at least one of the listed values for each listed key. Keys and values are matched verbatim against the company's metadata catalogue — case-sensitive, including whitespace and punctuation. Multiple keys compose as AND across keys (a course must satisfy every listed key); within a single key the listed values match as OR (the course's value for that key must equal one of them).

Composes with metadata_not_in (AND), with labels_in / labels_not_in, and with the existing id-based filters course_ids / filter_places / schedule_id (also AND). An empty per-key value drops that key from the wire; an empty top-level config means no metadata filter is applied.

This option works alongside the tile-grid course list and the render_course_tile callback — metadata filtering is applied first, then the surviving courses are rendered. The same data is exposed on course.metadata inside the per-tile callback, so you can both filter and display from the same source.

JavaScript form is safe for special characters; URL form is raw

The widget URL-encodes metadata keys and values when constructing the api request from the JavaScript form, so &, =, +, #, spaces, and punctuation are all safe to use in window.ZOOZA.metadata_in. The URL Query form is not auto-encoded — if you write the URL by hand (or template it server-side), encode &, =, #, and + yourself, the same way you would for any query parameter.

Private metadata keys are silently invisible

Private (admin-only) metadata keys never match for embedders. There is no client-visible difference between "key doesn't exist" and "key exists but is private" — both produce zero matches for that key. If a key is unpublished after the widget is embedded, the courses tagged with it will silently fall out of the list.

<script>
window.ZOOZA = {
metadata_in: {
color: [ "red", "blue" ],
age_band: "6-8"
}
}
</script>

metadata_not_in

Type: Object — per-key value is an array of strings or pipe-delimited string

Hide courses whose metadata matches any of the listed values for the listed keys. Match rules are identical to metadata_in — keys and values verbatim, case-sensitive, multiple keys compose as AND (a course must avoid every match), values within a single key match as OR.

Composes with metadata_in (AND), with labels_in / labels_not_in, and with the existing id-based filters course_ids / filter_places / schedule_id. An empty per-key value drops that key from the wire; an empty top-level config means no metadata filter is applied.

<script>
window.ZOOZA = {
metadata_not_in: {
archived: [ "true" ]
}
}
</script>

course_list_collapse_on_select

Type: Boolean

By default, when a customer picks a course from the grid, the other tiles are hidden so the form can focus on the next step. The selected tile gets a "back to all courses" affordance the customer can use to change their mind. Set this to false to keep every tile visible at all times — useful when you want customers to compare courses side by side as they progress.

Only meaningful when Course list display is set to Grid. In Dropdown mode the setting has no effect.

ValueDescriptionExample Value
true (Default)After a customer picks a course, other tiles are hidden until they pick "back to all courses".Focused single-tile view
falseAll tiles stay visible after a selection; the chosen tile is highlighted with a selected class.Side-by-side comparison
<script>
window.ZOOZA = {
course_list_collapse_on_select: false
}
</script>

schedule_list_collapse_on_select

Type: Boolean

By default, after the customer picks a class from the schedule list, the other classes are hidden and the chosen one stays on screen with a "change" link. Set this to false to keep all classes visible — useful when customers should be able to scan the full list as they fill in the form, or when the form sits beside a fixed schedule overview.

ValueDescriptionExample Value
true (Default)After a class is picked, the other tiles collapse. The chosen tile shows a "change" link.Focused single-tile view
falseAll tiles remain visible after a selection.Full schedule stays on screen
<script>
window.ZOOZA = {
schedule_list_collapse_on_select: false
}
</script>

ps (Payment schedule type)

Type: String

Use this setting either in combination with f or as a standalone preset to preselect first instance of available payment schedules by type.

Allowed values:

ValueLabel
single_paymentSingle payment
in_advanceIn advance
by_attendanceBy attendance
pay_as_you_goPay as you go
<script>
window.ZOOZA = {
ps: 'pay_as_you_go'
}
</script>

f (Payment schedule frequency)

Type: String

Use this setting either in combination with ps or as a standalone preset to preselect first instance of available payment schedules by frequency.

Allowed values:

ValueLabel
monthlyMonthly
quarterlyQuarterly
half_yearlyHalf yearly
yearlyYearly
after_eventsAfter events
absoluteAbsolute
segmentsBlocks
<script>
window.ZOOZA = {
f: 'yearly'
}
</script>

multi_step_registration

Type: Bool

By default the registration form appears as a single step form, that reveals its parts as user navigates through a selection. You can turn this form into multiple steps:

  • Course/Class selection
  • Personal information
  • Product options (optional)
  • Payment options
<script>
window.ZOOZA = {
multi_step_form: true
}
</script>

preferred_currency

Type: String (Three letter ISO 4217 code)

This settings will override default currency shown in the form which is based on your Zooza account's region. Even though the form will display prices for your currency, in order for this to work, your currency also needs to be set up on the course level for every course shown in the booking form. This setting can be done in: Courses > COURSE > Settings > Price settings > Additional currencies

When to use this

This enables you to sell your classes in different currency than defined by your region. If you want to collect money for all bookings in the same currency consider changing the region. This setting is intended for cases where you want to allow dual currency regime on a subset of your products.

ValueDescriptionExample Value
CODEThree letter ISO 4217 currency codeCZK
<script>
window.ZOOZA = {
preferred_currency: CODE
}
</script>

Type: Bool

This will print additional debug information to the browser's console. It is especially useful for tracking translations if you want to replace some of the default texts.

<script>
window.ZOOZA = {
print_debug: true|false
}
</script>

translations

Type: Object

If you want to replace any of the text used in the booking form, you can do that by providing your own custom translations.

When to use this

Within Zooza's course settings there are plenty of customisation options especially for input fields regarding personal information. You can change them directly in the Courses > COURSE > Settings > Online registration or Courses > COURSE > Settings > Additional fields without any need for coding.

On top of that, please bear in mind that by providing custom translations you are changing the text value for all products/texts displayed on this widget's instance. Therefore if possible, stay away from changing the meaning of the text or omitting useful information as this may hinder your conversion rates.

PlaceholderDescriptionExample Value
KEYTranslation key that appears in the booking formregistration.step_select_course
TRANSLATIONYour custom text that will replace the default translationSelect course
<script>
window.ZOOZA = {
translations: {
KEY : TRANSLATION
}
}
</script>
How to find KEY

Enable print_debug mode and open your browser's console. You will see printouts like this: Text Key: notifications.title Translation: Notifications. The Text key is the key used by application and Translation is its default translation. Notice that keys appear as you progress through the form. You can identify each key by searching for the Translation in the console.

Example

<script>
window.ZOOZA = {
translations: {
'registration.step_select_course' : 'Select course',
'overview.your_discounts' : 'Your discounts',
'course_detail.accordion_free_events' : 'Free lessons',
}
}
</script>

registration_display_mode

Type: Enum

By default, if class supports more registration modes (e.g. Trial registrations or Segments), registration widget will present customer with all available options. If you want to limit this, you can do that using this property.

Example usages:

  • Your class contains blocks and you only want your customers to select the blocks
  • You offer trial lessons on a landing page for new customers. And you place a second instance of the widget in the members area where you'll offer more options
ValueDescriptionExample Value
REGISTRATION_DISPLAY_MODEEnum of your choicetrials_only
<script>
window.ZOOZA = {
registration_display_mode: REGISTRATION_DISPLAY_MODE
}
</script>

lang

Type: String (BCP 47 locale code)

Language of the widget is defined by your Zooza account. You can change this language in Settings > General > Language. However, you can override widget's language by setting its lang property to one of supported languages.

ValueDescriptionExample Value
LANGUAGE_CODELanguage code of your choiceen-EN
https://sample-site.com/registration?lang=LANGUAGE_CODE
How display language is determined

Although the widget's language is mainly determined by language set in your account, it is not the only determinant. The widget's language is determined in this order:

  1. URL Query lang parameter
  2. lang parameter read from browser's LocalStorage() (this is set when user opts for a language using language selector at the bottom of the widget)
  3. lang attribute of html tag
  4. language set in the Zooza
Multilingual websites

If you have multilingual website and you are using a content management system (such as WordPress) then you don't need to set up anything as these systems will typically set current page's language html attribute and the widget will automatically use this prior selecting the language defined in your account (as described in box above).

Analytics

Registration widget will trigger following events for Google Tag Manager's DataLayer and Meta's Pixel:

EventDescriptionEvent data
zooza_event_form_loadedTriggered when the registration form is displayed to the userzooza_form_loaded: true
zooza_event_form_submit_startTriggered immediately after the submit button is clicked, before the request is sentzooza_total_price: Total value of the booking (including future payments); zooza_currency: Currency; zooza_pay_now: Amount to pay now
zooza_event_form_submit_doneTriggered after the response is received from the serverzooza_event_form_submit_done: true
zooza_event_form_errorTriggered if the response contains any errors
zooza_event_form_submit_thank_youTriggered if registration is successful and no further steps are neededzooza_form_submit_thank_you: true
zooza_event_form_submit_go_to_paymentTriggered if the user is taken to the payment stepzooza_form_submit_go_to_payment: true
zooza_event_payment_responseTriggered when the user returns from the payment gatewayzooza_payment_response (string) ok or fail
zooza_event_confirm_registrationTriggered when the user clicks the confirmation link in the emailzooza_registration_id (int)
zooza_event_enroll_startedTriggered when the user proceeds after a trial lessonzooza_enroll_started: true
zooza_event_reenroll_startedTriggered when the user proceeds after a trial lessonzooza_reenroll_started: true
zooza_event_accept_waitlistTriggered when a user accepts a spot from a waitlist notification (for make-up lessons)zooza_accept_waitlist: true
zooza_event_turn_off_notificationsTriggered when the user disables notifications in Full courseszooza_turn_off_notifications: true
zooza_event_turn_off_upcoming_events_notificationsTriggered when the user disables reminders in Open courseszooza_turn_off_upcoming_events_notifications: true
zooza_event_cancel_eventTriggered when the user cancels a session via the email linkzooza_cancel_event: true
zooza_event_form_submit_errorTriggered when form on submit returns an error (e.g. booking failed due to an error)zooza_event_form_submit_error (bool)
zooza_event_share_link_openedTriggered when Share link is usedzooza_share_link_opened (bool)

Each event will also pass event data, which is an object with one or multiple properties. The one that will likely interest you most is the zooza_event_form_submit_start event, which also sends the value of the order to analytics.

Guides

URL Hashtag-Based Tracking (For Registration Confirmation)

Besides event-based triggers, Zooza also supports conversion tracking via URL fragments (hashtags).

You'll see these appear after key steps in the registration flow. They can be used in Google Analytics, Meta Ads, or other platforms for measuring conversion milestones.

First Phase – Submitted Form

HashtagDescription
#doneRegistration email sent. At this point, the user is recorded in Zooza and this counts as a conversion.
#registration_failedForm submission failed.

Second Phase – Confirmed Email

HashtagDescription
#confirm_registrationTriggered when the user confirms their registration via email.
#confirm_registration_thank_youTriggered when the user reaches the final "Thank You" screen (registration complete).

Notes on hashtag tracking

  • Dynamic URLs: Zooza registration links often include query parameters like ?course_id=123&place_id=456. Always match using URL contains #YOUR_HASHTAG.
  • Use Regex for Flexibility: For more robust tracking across locations or courses, use regular expressions in your rules.
  • Tracking per Location: Hashtags don't carry location data. If you need to track conversions per branch or subpage, combine hashtags with UTM parameters or URL path logic.
  • Thank You Page Tip: #confirm_registration_thank_you only appears when the registration is fully completed. This helps distinguish between users who just clicked the email vs. those who reached the final step.

Events and callbacks

You can hook into widget lifecycle events using the callback property.

schedule_registration_options_loaded

This callback is called when registration options are loaded and allows you to manipulate the DOM or widget state. This event happens immediately after a class is selected and before any further actions. Although it is possible to manipulate the DOM, it is generally better and safer to intercept the data model.

Params

The params object contains following attributes:

AttributeDescription
eljQuery element with registration options (.zooza_trial_registration_wrapper)
ask_for_trialBool. Whether currently selected class does contain trial sessions
ask_for_segmentBool. Whether currently selected class does contain Blocks
window.ZOOZA = {
callback: {
schedule_registration_options_loaded: ( params ) => {
console.info( 'registration options loaded', params );
},
},
};

schedule_list_rendered

Fires immediately after the widget renders the schedule list and appends it to the DOM. Use this hook when you need to run your own DOM work over the rendered tile collection — adding badges or section separators, attaching analytics, observing visibility, anything that needs the tiles to already be on the page.

The callback fires on every render of the schedule list, including re-renders that follow a place change. Make sure your handler is idempotent — guard against double-attaching listeners or duplicating injected nodes (the practical example below shows the pattern).

Params

The callback receives a single object argument with the following attributes:

AttributeDescription
elThe .zooza_schedules container as a plain DOM Element (not a jQuery object). Holds the prepended filter and all rendered .zooza_schedules_schedule[data-schedule_id] tiles.
schedulesArray of schedule objects in render order. The array order lines up 1:1 with the rendered tiles, so schedules[ i ] matches the i-th tile inside el. Each entry exposes the same stable members documented for render_schedule_tile's schedule arg (id, get_date_formatted(), get_start_formatted(), get_end_formatted(), get_price(), get_capacity_formatted()).
courseThe currently selected course. Same stable shape as the course arg in render_course_tile (id, name, description, course_type, registration_type, metadata).
el is a plain DOM Element

Unlike schedule_registration_options_loaded (which passes el as a jQuery object for legacy reasons), schedule_list_rendered passes el as a plain DOM Element. Embedders without jQuery on their page can use it directly — querySelectorAll, insertBefore, classList, etc. If you do have jQuery available, wrap with $( el ) if you prefer.

Minimal example — log every render
window.ZOOZA = {
callback: {
schedule_list_rendered: ( { el, schedules, course } ) => {
const tiles = el.querySelectorAll( '.zooza_schedules_schedule' );
console.info( `Rendered ${ tiles.length } schedules for course "${ course.name }"` );
},
},
};
Practical example — week separators between schedule groups

A common pattern: the schedule list contains several weekly groups whose names start with a numeric prefix ("1. 22 JUNE - 3 JULY 2026 // 09.00-10.10", "1. 22 JUNE - 3 JULY 2026 // 10.30-11.40", …, "2. 06 JULY - 17 JULY 2026 // 09.00-10.10", …). The example below walks the rendered tiles, detects when the leading group number changes, and inserts a separator before the first tile of each new group.

The handler is idempotent: it removes any separators it injected on a previous render before walking the tiles, so a re-render after a place change does not stack duplicates.

window.ZOOZA = {
callback: {
schedule_list_rendered: ( { el, schedules } ) => {
// The callback fires on every render (including re-renders after a
// place change), so clear any separators we injected previously.
el.querySelectorAll( '.my-week-separator' ).forEach( ( node ) => node.remove() );

const tiles = el.querySelectorAll( '.zooza_schedules_schedule' );
let last_group = null;

tiles.forEach( ( tile, index ) => {
const schedule = schedules[ index ];
const match = schedule.name && schedule.name.match( /^(\d+)\./ );
if ( ! match ) {
return;
}

const group_number = match[ 1 ];
if ( group_number === last_group ) {
return;
}
last_group = group_number;

const separator = document.createElement( 'div' );
separator.className = 'my-week-separator';
separator.textContent = schedule.name;
tile.parentNode.insertBefore( separator, tile );
} );
},
},
};

The widget exposes schedule.name on each entry alongside the stable getters listed in render_schedule_tile — it is the human-readable label the widget itself uses on the tile.

render_course_tile

Returns the HTML that goes inside a single course tile. The widget owns everything else: the <div class="zooza_courses_course" data-course_id="<id>"> wrapper, the grid layout, the "Select" CTA, the collapse-on-select, and the "back to all courses" affordance. Register this callback when you want a different look for the courses than the built-in default — your own headings, images, marketing copy, prices, badges, anything.

Registering this callback forces the widget into grid mode even if Course list display is set to Dropdown — the hidden <select name="zooza_courses"> stays in the DOM as the source of truth, but the visible UI is your tiles.

Params

The course parameter exposes the following stable fields. Anything else is internal and may change between minor versions without a deprecation cycle.

FieldTypeNote
idNumberCourse id
nameStringDisplay name
descriptionStringMay be empty
course_typeStringMatches the API enum (course, event, online_event, photography, …)
registration_typeStringsingle, full2, or open
metadataArrayPublic metadata entries on the course (see entry shape below). Empty array if the course has no public metadata. The same keys you can filter on with metadata_in / metadata_not_in.

Each entry in course.metadata exposes:

FieldTypeNote
keyStringMetadata key as authored in admin (case-sensitive).
valueString | Number | Boolean | ObjectTyped per value_type; the api casts before serialising.
value_typeStringOne of string, int, bool, or json. Branch on this if you need exhaustive type handling.

Each course has at most one entry per key, so course.metadata.find( m => m.key === 'color' ) is safe.

Return

A string, DOM Node, or jQuery object representing the inside of the tile. Whatever you return is inserted into the widget-built tile wrapper. Anything else (e.g. undefined) is treated as "no content" and the tile gets only the auto-appended Select CTA.

Minimal example — name and description

The smallest thing that works. The widget auto-appends a default Select CTA at the bottom of the tile because the returned content does not include a .zooza_courses_course_select element — so the customer can advance with no extra wiring.

window.ZOOZA = {
callback: {
render_course_tile: ( course ) => `
<h3>${ course.name }</h3>
<p>${ ( course.description || '' ).slice( 0, 160 ) }</p>
`,
},
};
Reading metadata — typed values rendered as badges

Metadata entries on course.metadata are an array of { key, value, value_type }. A small helper that returns the value for a given key (or null if absent) keeps tile templates readable. The widget always returns a value typed per value_typestring, int, bool, or json — so no parsing on your side. See the Params table for the stable contract.

window.ZOOZA = {
callback: {
render_course_tile: ( course ) => {
const get_meta = ( key ) => {
const entry = ( course.metadata || [] ).find( ( m ) => m.key === key );
return entry ? entry.value : null;
};

const price = get_meta( 'price' ); // value_type: 'string', e.g. "from 34 EUR/month"
const size = get_meta( 'size' ); // value_type: 'int', e.g. 10

const price_badge = price !== null
? `<span class="my-badge my-badge-price">${ price }</span>`
: '';
const size_badge = size !== null
? `<span class="my-badge my-badge-size">Group size: ${ size }</span>`
: '';

return `
<h3>${ course.name }</h3>
<div class="my-meta-row">${ price_badge }${ size_badge }</div>
<p>${ ( course.description || '' ).slice( 0, 160 ) }</p>
`;
},
},
};

The same keys you read here are the ones you can filter on with metadata_in / metadata_not_in — filter to a subset of courses, then render the surviving courses' metadata in your own design language.

Custom CTA — embedder provides the Select element

If you want the Select CTA inline (different copy, different position, different styling), include any element with class="zooza_courses_course_select" in the returned HTML. The widget detects it and skips its auto-append. You don't write a click handler — the widget's delegated click on .zooza_courses_course_select reads the course id from the tile wrapper and runs the standard flow.

window.ZOOZA = {
callback: {
render_course_tile: ( course ) => `
<header>
<h3>${ course.name }</h3>
<button type="button" class="zooza_courses_course_select">Reserve this course</button>
</header>
<p class="my-course-summary">${ ( course.description || '' ).slice( 0, 200 ) }</p>
`,
},
};
The class hooks

The widget delegates click on these classes — your tile content can include any element carrying them, and the widget reads data-course_id from the closest tile wrapper, never from the click target. No event listeners on your side, no jQuery, no manual selection wiring.

ClassAction
zooza_courses_course_selectPick this course
zooza_courses_course_changeBack to all courses (collapse mode only)

If your tile content does not include zooza_courses_course_select, the widget auto-appends a default Select CTA so the customer can always advance.

render_schedule_tile

Mirror of render_course_tile for the schedule list. Returns the HTML that goes inside a single <div class="zooza_schedules_schedule" data-schedule_id="<id>"> tile. The widget owns the wrapper, the grid layout, the Choose CTA, collapse-on-select, and the change affordance.

Params

The callback receives ( schedule, course ). Anything not listed below is internal and may change between minor versions without a deprecation cycle.

schedule exposes:

MemberReturnsNote
idNumberSchedule id
get_date_formatted()StringLocale-formatted date / date range
get_start_formatted()StringStart datetime, locale-formatted
get_end_formatted()StringEnd datetime, locale-formatted
get_price()StringCurrency-formatted price (e.g. "10,00 €")
get_capacity_formatted()StringLocalised capacity copy (e.g. "Spots available", "Full")

course is the currently selected course model — same shape as the course parameter passed to render_course_tile.

Return

A string, DOM Node, or jQuery object — same as render_course_tile. Whatever you return is inserted into the widget-built tile wrapper.

Minimal example — date and price
window.ZOOZA = {
callback: {
render_schedule_tile: ( schedule, course ) => `
<strong>${ schedule.get_date_formatted() }</strong>
<span>${ schedule.get_price() }</span>
`,
},
};
Custom CTA — embedder provides the Choose element

Same pattern as courses: include class="zooza_schedules_schedule_select" on any element to take over CTA copy, position, or styling. Optionally include class="zooza_schedules_schedule_change" if you want the "change selected class" affordance inline (the widget normally injects it on the selected tile in collapse mode).

window.ZOOZA = {
callback: {
render_schedule_tile: ( schedule, course ) => `
<div class="my-schedule-info">
<strong>${ schedule.get_date_formatted() }</strong>
<span>${ schedule.get_capacity_formatted() }</span>
</div>
<button type="button" class="zooza_schedules_schedule_select">Reserve this date</button>
`,
},
};
The class hooks

The widget delegates click on these classes — your tile content can include any element carrying them, and the widget reads data-schedule_id from the closest tile wrapper, never from the click target. No event listeners on your side, no jQuery, no manual selection wiring.

ClassAction
zooza_schedules_schedule_selectPick this class
zooza_schedules_schedule_changeChange selected class (collapse mode only)

If your tile content does not include zooza_schedules_schedule_select, the widget auto-appends a default Choose CTA so the customer can always advance.

Data model

Registration widget exposes its model within document.ZOOZA.model property. This allows you to override some logical steps decided by the application.

Advanced usage

Remember that when you override default behaviour of the model, you can break the widget's functionality. On top of that, the validations that are performed on the backend will not respect if you override something in the model.

Most common/useful model methods:

MethodDescription
document.ZOOZA.model.print()Will print current state of the model
document.ZOOZA.model.selected_course()Will return currently selected course data
document.ZOOZA.model.selected_schedule()Will return currently selected class data

Examples

Multi step registration and filtered courses with custom translation

window.ZOOZA = {
course_ids: [948], // Array of course IDs to filter or preselect
multi_step_form: true, // Use a single-step or multi-step registration form
translations: {
'registration.step_select_course': 'Select a program',
},
};

Customer journey within registration form

If booking contains online payment

If booking does not contain online payment