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
| Hook | Type | Arguments | Purpose |
|---|---|---|---|
PC.fe.init | action | productId, parentId | The configurator is initialising |
PC.fe.init.modal | action | view | The modal view was created |
PC.fe.before_open | action | (none) | Before the configurator opens |
PC.fe.open | action | view | It opened |
PC.fe.start | action | view | It is running and the data is in place |
PC.fe.start.loadingimages | action | view | Image preloading began |
PC.fe.close | action | view | It closed |
PC.fe.reset_configurator | action | (none) | The configuration was reset |
PC.fe.reset_product | action | (none) | The product was reset |
mkl_pc.product_data.loading | action | productId | Product data is being fetched |
mkl_pc.product_data.loaded | action | productId | It arrived |
variation_content_loaded | action | { response, product_id } | A variation's content loaded |
PC.fe.setContent.parse.before | action | response | Before a content response is parsed |
PC.fe.reset.on.start | filter | true | Whether to reset when the configurator starts |
Layers
| Hook | Type | Arguments | Purpose |
|---|---|---|---|
PC.fe.layer.render | action | view | A layer rendered |
PC.fe.layer.beforeRenderChoices | action | view | Before its choices are built |
PC.fe.layer.activate | action | view | It became active |
PC.fe.layer.deactivate | action | view | It stopped being active |
PC.fe.layer.show | action | view | It became visible |
PC.fe.layer.hide | action | view | It was hidden |
PC.fe.layer.self_hide | filter | true, view | Whether a layer may hide itself |
PC.fe.configurator.layer_data | filter | attributes | Template data for a layer |
PC.fe.layers_list.open | action | view, model | The layers list opened |
PC.fe.layers_list.close | action | view, model | It closed |
PC.fe.layers_list.layers.added | action | view | Layers were added to it |
PC.fe.layers_list_item.init | action | view | A layers-list item was created |
PC.fe.html_layer.render | action | view | A custom HTML layer rendered |
Choices
| Hook | Type | Arguments | Purpose |
|---|---|---|---|
PC.fe.choice.init | action | view | A choice view was created |
PC.fe.configurator.choice-item.before.render | action | view | Before it renders |
PC.fe.configurator.choice-item.render.after-template | action | view | After the template ran |
PC.fe.configurator.choice-item.render | action | view | After it rendered |
PC.fe.choice.set_choice | action | model, view | A choice was selected |
PC.fe.choice.activate | action | view | It became active |
PC.fe.choice.deactivate | action | view | It stopped being active |
PC.fe.choice.change | action | model | A choice's value changed |
PC.fe.configurator.choice_data | filter | attributes | Template data for a choice |
PC.fe.configurator.template_choice_data | filter | data | The same, immediately before rendering |
PC.fe.choices.add_one | filter | true, model | Whether to render a choice |
PC.fe.choices.add_one.after | action | view, newChoice | After one was added |
PC.fe.add.choices | action | $el, view | The choices list was added to the DOM |
PC.fe.choices.where | filter | where, view | Where the choices list is inserted |
PC.fe.close_choices_after_selection | filter | close, model | Whether selecting closes the list |
PC.fe.set.selected_choice | action | names, view | The selected-choice label was set |
PC.fe.selected_choice.name | filter | name, item | That label's text |
PC.fe.choice-img.init | action | view | A choice image view was created |
PC.fe.choice-custom-html.init | action | view | A custom HTML choice view was created |
PC.choices.canSelectChoice | filter | true, choice, collection, context | Whether a choice may be selected |
Viewer
| Hook | Type | Arguments | Purpose |
|---|---|---|---|
PC.fe.viewer.main_view | filter | View | The viewer view class |
PC.fe.viewer.render.before | action | view | Before the viewer renders |
PC.fe.viewer.render | action | view | After it does |
PC.fe.viewer.item.view | filter | View, model, viewer | The view class for one layer image |
PC.fe.viewer.item.tag | filter | 'img' | The tag a layer image renders as |
PC.fe.viewer.item.added | action | layer, viewer | A layer was added to the viewer |
PC.fe.viewer.item.render.empty.images | filter | false, model | Render a layer with no image |
PC.fe.viewer.layer.render | action | view | A viewer layer rendered |
PC.fe.viewer.layer.render.after | action | view | After it did |
PC.fe.viewer.layer.classes | filter | classes, view | Classes on a viewer layer |
PC.fe.viewer.layer.preload.complete | action | view | One layer finished preloading |
PC.fe.viewer.layers.preload.complete | action | view | All of them did |
PC.fe.viewer.static_layer.render | action | view | A static layer rendered |
PC.fe.viewer.static_layer.classes | filter | classes, view | Classes on it |
PC.fe.viewer.html_item.added | action | htmlLayer, viewer | An HTML layer was added |
PC.fe.viewer_3d.registered | action | View | The 3D viewer view was registered |
PC.fe.config.bg_image | filter | url, view | The viewer background image |
PC.fe.angle_view.init | action | view | An angle view was created |
PC.fe.configurator.angle_data | filter | attributes | Template data for an angle |
Saving and add to cart
| Hook | Type | Arguments | Purpose |
|---|---|---|---|
PC.fe.save_data.before_save.promises | filter | [] | Promises awaited before the payload is built |
PC.fe.save_data.choices | filter | choices | The finished payload |
PC.fe.save_data.parse_choices.add_choice | filter | true, choice | Whether a choice is included |
PC.fe.save_data.parse_choices.added_choice | filter | choiceData, choice | One choice's saved data |
PC.fe.save_data.parse_choices.add_layer_group | filter | true, model | Whether a group layer is included |
PC.fe.save_data.parse_choices.added_group_layer | filter | data, model | A group layer's saved data |
PC.fe.save_data.parse_choices.angle_id | filter | angleId | The angle recorded with a choice |
PC.fe.save_data.parse_choices.image_order | filter | imageOrder, model | The stacking order recorded with a choice |
PC.fe.save_data.parse_choices.after | action | model, saveData | After a layer is parsed |
PC.fe.validate_configuration | filter | errors | The validation errors |
PC.fe.validation.summary_placement | filter | placement, context | Where the error summary goes |
PC.fe.add_to_cart.before | action | view | Before adding to cart |
PC.fe.trigger_add_to_cart | filter | true, $cart | Whether the request is sent |
PC.fe.add_to_cart.append_ajax_request_body | action | body, view | Add fields to the AJAX request |
PC.fe.add_to_cart.ajax_request_finally | action | view | After the request settles |
PC.fe.cart_redirect_after_add | filter | setting | Whether to redirect after adding |
PC.fe.cart_redirect_url | filter | url | Where to redirect |
PC.fe.reset.on.ajax_add_to_cart | filter | setting, view | Reset after an AJAX add |
PC.fe.reset_after_ajax_add_to_cart | action | view | After that reset |
PC.fe.add_to_quote.before | action | view | Before adding to a quote |
PC.fe.render_form | action | view | The cart form rendered |
PC.fe.qty_changed | action | qty | The quantity changed |
Configuration loading
| Hook | Type | Arguments | Purpose |
|---|---|---|---|
PC.fe.setConfig.before | action | configItems | Before a configuration is applied |
PC.fe.setConfig.setItem | action | configItem, layerContent | As each item is applied |
PC.fe.setConfig | action | configItems | After all of them |
Steps, summary and navigation
| Hook | Type | Arguments | Purpose |
|---|---|---|---|
PC.fe.steps.display_step | action | view | A step was displayed |
PC.fe.steps.display_breadcrumb | filter | true | Whether the breadcrumb is shown |
PC.fe.steps.display_marker | filter | true | Whether the progress marker is shown |
PC.fe.steps_position | filter | null, $nav | Where the steps nav is inserted |
PC.fe.breadcrumb_position | filter | null, breadcrumb | Where the breadcrumb is inserted |
PC.fe.configurator.summary.render | action | view | The summary rendered |
PC.fe.configurator.summary-item.render.after-template | action | view | A summary row rendered |
PC.fe.summary_item.attributes | filter | attributes, model | Attributes on a summary row |
hide_in_configurator-also-hides-in-summary | filter | true, item | Whether hidden items are hidden in the summary too |
PC.fe.setup_keyboard_navigation | filter | true | Whether keyboard navigation is bound |
Tooltips
| Hook | Type | Arguments | Purpose |
|---|---|---|---|
PC.fe.tooltip.options | filter | options, view | Tippy.js options for any configurator tooltip |
PC.fe.choice.tooltip.options | filter | options, view | The same, for a choice's tooltip only |
PC.fe.tooltip.add_all_text | filter | bool | Whether 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.
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.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.
wp.hooks.addFilter( 'PC.fe.configurator.choice_data', 'my-plugin/configurator', function ( attributes ) { attributes.badge = attributes.stock > 0 ? '' : 'Made to order'; return attributes; });
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.
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' ), } );} );
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.
wp.hooks.addFilter( 'PC.choices.canSelectChoice', 'my-plugin/configurator', function ( canSelect, choice, collection, context ) { if ( choice.get( 'requires_login' ) && ! myUserIsLoggedIn ) { return false; } return canSelect; });
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:
PC.fe.save_data.parse_choices.add_choice— include this choice at all?PC.fe.save_data.parse_choices.added_choice— its dataPC.fe.save_data.parse_choices.add_layer_group/…added_group_layer— the same for groupsPC.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.
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; });
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.
wp.hooks.addFilter( 'PC.fe.save_data.before_save.promises', 'my-plugin/configurator', function ( promises ) { promises.push( myAsyncExport() ); return 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.
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.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:
wp.hooks.addFilter( 'PC.fe.viewer.main_view', 'my-plugin/viewer', function ( View ) { return PC.fe.currentProductData.my_flag ? MyViewerView : View;} );
item.view swaps the view for an individual layer image, and receives the model, so you can
substitute per layer:
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.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:
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, } ); });
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.
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.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.
wp.hooks.addAction( 'PC.fe.setConfig', 'my-plugin/configurator', function ( configItems ) { myRestoreExtras( configItems );} );
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.
wp.hooks.addFilter( 'PC.fe.tooltip.options', 'my-plugin/tooltips', function ( options, view ) { options.theme = 'my-theme'; options.delay = [ 200, 0 ]; return 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.
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.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.
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.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.