Guide

Frontend JavaScript hooks

The configurator is a Backbone application. These hooks let you follow its lifecycle, change the data each view renders from, substitute views entirely, and shape the payload that eventually reaches the cart.

All of them use wp.hooks and need a namespace as the second argument. See the hook reference index for the mechanics.

Lifecycle

HookTypeArgumentsPurpose
PC.fe.initactionproductId, parentIdThe configurator is initialising
PC.fe.init.modalactionviewThe modal view was created
PC.fe.before_openaction(none)Before the configurator opens
PC.fe.openactionviewIt opened
PC.fe.startactionviewIt is running and the data is in place
PC.fe.start.loadingimagesactionviewImage preloading began
PC.fe.closeactionviewIt closed
PC.fe.reset_configuratoraction(none)The configuration was reset
PC.fe.reset_productaction(none)The product was reset
mkl_pc.product_data.loadingactionproductIdProduct data is being fetched
mkl_pc.product_data.loadedactionproductIdIt arrived
variation_content_loadedaction{ response, product_id }A variation's content loaded
PC.fe.setContent.parse.beforeactionresponseBefore a content response is parsed
PC.fe.reset.on.startfiltertrueWhether to reset when the configurator starts

Layers

HookTypeArgumentsPurpose
PC.fe.layer.renderactionviewA layer rendered
PC.fe.layer.beforeRenderChoicesactionviewBefore its choices are built
PC.fe.layer.activateactionviewIt became active
PC.fe.layer.deactivateactionviewIt stopped being active
PC.fe.layer.showactionviewIt became visible
PC.fe.layer.hideactionviewIt was hidden
PC.fe.layer.self_hidefiltertrue, viewWhether a layer may hide itself
PC.fe.configurator.layer_datafilterattributesTemplate data for a layer
PC.fe.layers_list.openactionview, modelThe layers list opened
PC.fe.layers_list.closeactionview, modelIt closed
PC.fe.layers_list.layers.addedactionviewLayers were added to it
PC.fe.layers_list_item.initactionviewA layers-list item was created
PC.fe.html_layer.renderactionviewA custom HTML layer rendered

Choices

HookTypeArgumentsPurpose
PC.fe.choice.initactionviewA choice view was created
PC.fe.configurator.choice-item.before.renderactionviewBefore it renders
PC.fe.configurator.choice-item.render.after-templateactionviewAfter the template ran
PC.fe.configurator.choice-item.renderactionviewAfter it rendered
PC.fe.choice.set_choiceactionmodel, viewA choice was selected
PC.fe.choice.activateactionviewIt became active
PC.fe.choice.deactivateactionviewIt stopped being active
PC.fe.choice.changeactionmodelA choice's value changed
PC.fe.configurator.choice_datafilterattributesTemplate data for a choice
PC.fe.configurator.template_choice_datafilterdataThe same, immediately before rendering
PC.fe.choices.add_onefiltertrue, modelWhether to render a choice
PC.fe.choices.add_one.afteractionview, newChoiceAfter one was added
PC.fe.add.choicesaction$el, viewThe choices list was added to the DOM
PC.fe.choices.wherefilterwhere, viewWhere the choices list is inserted
PC.fe.close_choices_after_selectionfilterclose, modelWhether selecting closes the list
PC.fe.set.selected_choiceactionnames, viewThe selected-choice label was set
PC.fe.selected_choice.namefiltername, itemThat label's text
PC.fe.choice-img.initactionviewA choice image view was created
PC.fe.choice-custom-html.initactionviewA custom HTML choice view was created
PC.choices.canSelectChoicefiltertrue, choice, collection, contextWhether a choice may be selected

Viewer

HookTypeArgumentsPurpose
PC.fe.viewer.main_viewfilterViewThe viewer view class
PC.fe.viewer.render.beforeactionviewBefore the viewer renders
PC.fe.viewer.renderactionviewAfter it does
PC.fe.viewer.item.viewfilterView, model, viewerThe view class for one layer image
PC.fe.viewer.item.tagfilter'img'The tag a layer image renders as
PC.fe.viewer.item.addedactionlayer, viewerA layer was added to the viewer
PC.fe.viewer.item.render.empty.imagesfilterfalse, modelRender a layer with no image
PC.fe.viewer.layer.renderactionviewA viewer layer rendered
PC.fe.viewer.layer.render.afteractionviewAfter it did
PC.fe.viewer.layer.classesfilterclasses, viewClasses on a viewer layer
PC.fe.viewer.layer.preload.completeactionviewOne layer finished preloading
PC.fe.viewer.layers.preload.completeactionviewAll of them did
PC.fe.viewer.static_layer.renderactionviewA static layer rendered
PC.fe.viewer.static_layer.classesfilterclasses, viewClasses on it
PC.fe.viewer.html_item.addedactionhtmlLayer, viewerAn HTML layer was added
PC.fe.viewer_3d.registeredactionViewThe 3D viewer view was registered
PC.fe.config.bg_imagefilterurl, viewThe viewer background image
PC.fe.angle_view.initactionviewAn angle view was created
PC.fe.configurator.angle_datafilterattributesTemplate data for an angle

Saving and add to cart

HookTypeArgumentsPurpose
PC.fe.save_data.before_save.promisesfilter[]Promises awaited before the payload is built
PC.fe.save_data.choicesfilterchoicesThe finished payload
PC.fe.save_data.parse_choices.add_choicefiltertrue, choiceWhether a choice is included
PC.fe.save_data.parse_choices.added_choicefilterchoiceData, choiceOne choice's saved data
PC.fe.save_data.parse_choices.add_layer_groupfiltertrue, modelWhether a group layer is included
PC.fe.save_data.parse_choices.added_group_layerfilterdata, modelA group layer's saved data
PC.fe.save_data.parse_choices.angle_idfilterangleIdThe angle recorded with a choice
PC.fe.save_data.parse_choices.image_orderfilterimageOrder, modelThe stacking order recorded with a choice
PC.fe.save_data.parse_choices.afteractionmodel, saveDataAfter a layer is parsed
PC.fe.validate_configurationfiltererrorsThe validation errors
PC.fe.validation.summary_placementfilterplacement, contextWhere the error summary goes
PC.fe.add_to_cart.beforeactionviewBefore adding to cart
PC.fe.trigger_add_to_cartfiltertrue, $cartWhether the request is sent
PC.fe.add_to_cart.append_ajax_request_bodyactionbody, viewAdd fields to the AJAX request
PC.fe.add_to_cart.ajax_request_finallyactionviewAfter the request settles
PC.fe.cart_redirect_after_addfiltersettingWhether to redirect after adding
PC.fe.cart_redirect_urlfilterurlWhere to redirect
PC.fe.reset.on.ajax_add_to_cartfiltersetting, viewReset after an AJAX add
PC.fe.reset_after_ajax_add_to_cartactionviewAfter that reset
PC.fe.add_to_quote.beforeactionviewBefore adding to a quote
PC.fe.render_formactionviewThe cart form rendered
PC.fe.qty_changedactionqtyThe quantity changed

Configuration loading

HookTypeArgumentsPurpose
PC.fe.setConfig.beforeactionconfigItemsBefore a configuration is applied
PC.fe.setConfig.setItemactionconfigItem, layerContentAs each item is applied
PC.fe.setConfigactionconfigItemsAfter all of them

Steps, summary and navigation

HookTypeArgumentsPurpose
PC.fe.steps.display_stepactionviewA step was displayed
PC.fe.steps.display_breadcrumbfiltertrueWhether the breadcrumb is shown
PC.fe.steps.display_markerfiltertrueWhether the progress marker is shown
PC.fe.steps_positionfilternull, $navWhere the steps nav is inserted
PC.fe.breadcrumb_positionfilternull, breadcrumbWhere the breadcrumb is inserted
PC.fe.configurator.summary.renderactionviewThe summary rendered
PC.fe.configurator.summary-item.render.after-templateactionviewA summary row rendered
PC.fe.summary_item.attributesfilterattributes, modelAttributes on a summary row
hide_in_configurator-also-hides-in-summaryfiltertrue, itemWhether hidden items are hidden in the summary too
PC.fe.setup_keyboard_navigationfiltertrueWhether keyboard navigation is bound

Tooltips

HookTypeArgumentsPurpose
PC.fe.tooltip.optionsfilteroptions, viewTippy.js options for any configurator tooltip
PC.fe.choice.tooltip.optionsfilteroptions, viewThe same, for a choice's tooltip only
PC.fe.tooltip.add_all_textfilterboolWhether the tooltip repeats the full text

3D

PC.3d.postprocessingPasses, PC.3d.createPostprocessingLayer, PC.3d.postprocessingEnabled and the PC.fe.viewer.runtime.* events are documented in The 3D runtime API.


PC.fe.start

The one to reach for by default. It fires once the configurator is open, its data is loaded and its views exist — everything on PC.fe is safe to touch.

javascript custom.js
wp.hooks.addAction( 'PC.fe.start', 'my-plugin/configurator', function ( view ) {	console.log( PC.fe.layers.length, 'layers' );	console.log( PC.fe.currentProductData.product_info );} );
PC.fe.start.

PC.fe.init fires much earlier, before the data has arrived. Add-ons commonly register at priority 30 here to run after core's own setup.

PC.fe.configurator.choice_data and PC.fe.configurator.layer_data

Filter the attributes a template renders from. This is the right place to compute a display value without changing the underlying model.

javascript custom.js
wp.hooks.addFilter(	'PC.fe.configurator.choice_data',	'my-plugin/configurator',	function ( attributes ) {		attributes.badge = attributes.stock > 0 ? '' : 'Made to order';		return attributes;	});
PC.fe.configurator.choice_data and PC.fe.configurator.layer_data.

Anything you add is available in the template as data.badge — pair it with tmpl-pc-configurator-choice-item to print it. template_choice_data runs after this, immediately before rendering, and is where WPML-style translation is applied.

Do not mutate the model here. These filters run on every render; setting model attributes from them causes render loops.

PC.fe.choice.set_choice

Fires whenever a choice is selected, with the model and the view. This is the main "customer did something" signal.

javascript custom.js
wp.hooks.addAction( 'PC.fe.choice.set_choice', 'my-plugin/configurator', function ( model, view ) {	myAnalytics.track( 'choice', {		layer:  model.get( 'layerId' ),		choice: model.get( 'name' ),	} );} );
PC.fe.choice.set_choice.

Conditional Logic hooks this to re-evaluate its rules, so by the time your callback runs the rest of the configurator may not have caught up yet. If you need the settled state, use mkl_checked_conditions from the Conditional Logic add-on, or defer a frame.

PC.choices.canSelectChoice

Return false to refuse a selection. This is a real veto — the choice is not activated and the configurator logs the refusal.

javascript custom.js
wp.hooks.addFilter(	'PC.choices.canSelectChoice',	'my-plugin/configurator',	function ( canSelect, choice, collection, context ) {		if ( choice.get( 'requires_login' ) && ! myUserIsLoggedIn ) {			return false;		}		return canSelect;	});
PC.choices.canSelectChoice.

Give the customer some feedback when you refuse, or the click appears to do nothing.

PC.fe.save_data.choices and the parse filters

The payload sent to the cart is built by walking every layer. Four filters shape it:

  1. PC.fe.save_data.parse_choices.add_choice — include this choice at all?
  2. PC.fe.save_data.parse_choices.added_choice — its data
  3. PC.fe.save_data.parse_choices.add_layer_group / …added_group_layer — the same for groups
  4. PC.fe.save_data.choices — the finished array

Two more record the context a choice was made in, so the saved configuration can be rendered later without re-reading the product: …parse_choices.angle_id for the angle, and …parse_choices.image_order for the layer's position in the merged image. The latter defaults to the layer's image_order, or to its order where none is set, which is what keeps a saved configuration stacking correctly after the product's layers are reordered.

javascript custom.js
wp.hooks.addFilter(	'PC.fe.save_data.parse_choices.added_choice',	'my-plugin/configurator',	function ( choiceData, choice ) {		choiceData.my_reference = choice.get( 'my_reference' );		return choiceData;	});
PC.fe.save_data.choices and the parse filters.

Anything you add here reaches the server. It must survive mkl_pc_db_fields — a key that is not on that allowlist is dropped on save.

What you add is also what a cart item or an order line reads back later, rather than looking the value up on the product again — see mkl_pc/choice/source.

PC.fe.save_data.before_save.promises

Push a promise onto the array and add-to-cart waits for it before building the payload. This is how Text Overlay exports its rendered PNG and how the 3D viewer captures its screenshot.

javascript custom.js
wp.hooks.addFilter(	'PC.fe.save_data.before_save.promises',	'my-plugin/configurator',	function ( promises ) {		promises.push( myAsyncExport() );		return promises;	});
PC.fe.save_data.before_save.promises.

Every promise here delays add-to-cart, so keep them fast and make sure they always settle — a promise that never resolves hangs the button for good.

PC.fe.validate_configuration

Filters the array of validation errors before the configurator decides whether to submit. Push an entry to block add-to-cart with your own message.

javascript custom.js
wp.hooks.addFilter( 'PC.fe.validate_configuration', 'my-plugin/configurator', function ( errors ) {	if ( ! myRequirementMet() ) {		errors.push( { message: 'Please choose a finish before continuing.' } );	}	return errors;} );
PC.fe.validate_configuration.

PC.fe.viewer.main_view and PC.fe.viewer.item.view

Substitute view classes. main_view swaps the whole viewer — this is how the 3D viewer replaces the 2D one:

javascript custom.js
wp.hooks.addFilter( 'PC.fe.viewer.main_view', 'my-plugin/viewer', function ( View ) {	return PC.fe.currentProductData.my_flag ? MyViewerView : View;} );
PC.fe.viewer.main_view and PC.fe.viewer.item.view.

item.view swaps the view for an individual layer image, and receives the model, so you can substitute per layer:

javascript custom.js
wp.hooks.addFilter(	'PC.fe.viewer.item.view',	'my-plugin/viewer',	function ( View, model, viewer ) {		return 'video' === model.get( 'my_media_type' ) ? MyVideoLayerView : View;	});
PC.fe.viewer.main_view and PC.fe.viewer.item.view.

PC.fe.layer.beforeRenderChoices

Fires just before a layer builds its choices, which makes it the point at which a layer type can substitute its own choices view. Both Multiple Choice and Text Overlay work this way:

javascript custom.js
wp.hooks.addAction(	'PC.fe.layer.beforeRenderChoices',	'my-plugin/layers',	function ( layerView ) {		if ( 'my_type' !== layerView.layer_type ) return;		layerView.choices = new MyChoicesView( {			content: PC.fe.getLayerContent( layerView.model.id ),			model:   layerView.model,		} );	});
PC.fe.layer.beforeRenderChoices.

Pair it with mkl_pc_layer_types so the type is selectable in the admin.

PC.fe.add_to_cart.before and …append_ajax_request_body

before fires as add-to-cart starts. append_ajax_request_body receives the request body so you can add fields — note it is an action: mutate the object, do not return it.

javascript custom.js
wp.hooks.addAction(	'PC.fe.add_to_cart.append_ajax_request_body',	'my-plugin/cart',	function ( body, view ) {		body.append( 'my_field', document.querySelector( '#my-field' ).value );	});
PC.fe.add_to_cart.before and …append_ajax_request_body.

PC.fe.trigger_add_to_cart can cancel the request outright by returning false.

PC.fe.setConfig and friends

Fire when a stored configuration is applied — restoring a cart item, an order, or a saved design. setConfig.before runs before the reset, setConfig.setItem per item, setConfig at the end.

javascript custom.js
wp.hooks.addAction( 'PC.fe.setConfig', 'my-plugin/configurator', function ( configItems ) {	myRestoreExtras( configItems );} );
PC.fe.setConfig and friends.

PC.fe.is_setting_config is true throughout, which is how you tell a restored selection from a customer's click inside a set_choice handler.

PC.fe.tooltip.options

Every configurator tooltip passes through here as a Tippy.js options object.

javascript custom.js
wp.hooks.addFilter( 'PC.fe.tooltip.options', 'my-plugin/tooltips', function ( options, view ) {	options.theme = 'my-theme';	options.delay = [ 200, 0 ];	return options;} );
PC.fe.tooltip.options.

Add-ons layer their own filter on top — PC.fe.choice.tooltip.options, PC.fe.advanced-description.tooltip.options, PC.fe.text-overlay.tooltip.options — so this one is the place for a change that should apply everywhere.

PC.fe.choices.where and PC.fe.close_choices_after_selection

Layout behaviour. where decides where a layer's choices list is inserted ('in' or 'out'); close_choices_after_selection decides whether picking one closes the list, defaulting to the matching plugin setting.

javascript custom.js
wp.hooks.addFilter(	'PC.fe.close_choices_after_selection',	'my-plugin/configurator',	function ( close, model ) {		return 'multiple' === model.collection.layer_type ? false : close;	});
PC.fe.choices.where and PC.fe.close_choices_after_selection.

PC.fe.viewer.layer.classes and PC.fe.viewer.item.tag

classes filters the class list on a viewer layer; item.tag changes the element a layer image renders as, 'img' by default.

javascript custom.js
wp.hooks.addFilter( 'PC.fe.viewer.layer.classes', 'my-plugin/viewer', function ( classes, view ) {	classes.push( 'layer-' + view.model.get( 'layerId' ) );	return classes;} );
PC.fe.viewer.layer.classes and PC.fe.viewer.item.tag.

PC.fe.viewer.layers.preload.complete

Fires once every layer image has preloaded — the right moment to take a screenshot, measure the viewer, or reveal something that was waiting on a complete render.

Dynamic 3D hooks

The 3D viewer mirrors each of its events onto a hook name built at runtime. Those are covered in The 3D runtime API.