Getting to Know Woo: Extensibility in the New Product Editor

For the past few months, we have been busy building out a brand-new product creation experience. You may have read about it previously, and hopefully even tried it out yourself. If not, don’t worry… we will explain how to do so later in this post!

The product creation process has been redesigned from the ground up to focus on commerce, providing users with a seamless experience for crafting a wide range of products, from simple ones to highly customized options. This experience is still under active development, and we aim to reach feature parity early in 2024. Built with extensibility as a core principle, the new product editor provides developers with straightforward tools to integrate their unique features into the product creation process.

In fact, we are using these mechanisms to develop the new product editor itself!

We are approaching this project with the following guiding principles:

  • Provide a low complexity API that can be used via PHP, as we have heard from many developers that this is preferred.
  • Provide tooling to help set up the development environment.
  • Provide backwards compatibility when possible.
  • Use GitHub Discussions for brainstorming and decision-making, inviting contributions and feedback from the development community.

About Product Editor Extensibility

The design

The new product editor is organized into clearly defined tabs and sections, reflecting our research that finds that merchants think in terms of tasks and goals while creating new products in WooCommerce. 

The new product editor

You can help ensure your extension makes the most of this new design by reading our extensibility guidelines, which will help you determine where you should add new features to the product editor.

The implementation

The new product editor is implemented using the same Blocks API that powers the post and site editors in WordPress. However, whereas a “regular” block editor allows the end user to add blocks anywhere and rearrange those blocks, the product editor offers a more familiar, structured form-based experience, which is more suitable for the data entry and modification that is at the heart of product management.

As such, we have developed a simple PHP-based API, targeting our PHP developers in the WooCommerce community, which allows developers to easily extend the product editor’s form.

Extensibility features

Through a PHP-based API, developers are able to:

  • Add new blocks to the form; we supply a number of generic blocks that can be used out of the box, including:
    • Checkbox
    • Pricing
    • Radio
    • Text
    • Toggle
  • Remove blocks from the form
  • Conditionally hide and disable blocks on the form based on properties of the edited product, such as the product type

If an extension requires interactivity not available via our generic blocks, developers can easily implement a custom block using JavaScript and React.

Getting started

Try out the new product editor

Note: Since WooCommerce 7.9 (mid-July 2023), a subset of new stores have the new product editor auto-enabled as part of tests that Woo is running to help shape the new product creation experience. Users can easily opt-out of the new product editor and return to the classic product editor if they need functionality and extensions that are not yet available in the new product editor.

If you haven’t already, try out the new product editor so you can become familiar with how the new experience feels.

Go to WooCommerce > Settings > Advanced > Features and enable Try the new product editor (Beta).

Enabling the new product editor

Next, go to Products > Add New to try out the new editor.

The new product editor

Try adding a new block to the editor

There are a number of examples in the documentation on how to use the PHP-based API. Here is a simple example that adds a new field after the product name field. Add this to a test plugin and reload the product editor page.

<pre class="wp-block-syntaxhighlighter-code"><?php
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
if ( ! function_exists( 'YOUR_PREFIX_add_block' ) ) {
	/**
	 * Add a new block to the template after the product name field.
	 *
	 * @param BlockInterface $product_name_field The product name block.
	 */
	function YOUR_PREFIX_add_block( BlockInterface $product_name_field ) {
		$parent = $product_name_field->get_parent();
		if ( ! method_exists( $parent, 'add_block' ) ) {
			return;
		}
		$parent->add_block(
			[
				'id'         => 'example-block',
				'order'      => $product_name_field->get_order() + 5,
				'blockName'  => 'woocommerce/product-text-field',
				'attributes' => [
					'property' => 'meta_data.example_block_property',
					'label'    => __( 'Example block', 'YOUR_TEXT_DOMAIN' ),
				],
			]
		);
	}
	add_action(
		'woocommerce_block_template_area_product-form_after_add_block_product-name',
		'YOUR_PREFIX_add_block'
	);
}
</pre>

Here is the result:

The new “Example block” field appears under the “Name” field

Note that the block’s input will also be persisted automatically in the metadata of the product. There is no other configuration necessary!

Explore the extensibility guidelines

Now that you have a feel for the new product editor, start thinking about how to best bring your extension’s functionality to merchants using it. Read the extensibility guidelines for tips and best practices.

Let us know what you think

Are there things you love about extending the new product editor? We sure hope so! Let us know.

Even more importantly, let us know what documentation and functionality is missing that you need in order to successfully bring your extension to the new product editor.

Share your thoughts with us in our discussion forum and in Slack:

Where to find more information

We are actively working on providing documentation for new product editor development:


13 responses to “Getting to Know Woo: Extensibility in the New Product Editor”

  1. This is absolutely going in the right direction! Thanks team!

    I wish this sort of extensibility was considered for Cart/Checkout blocks as well, but… our team is super happy we have this coming in the new product editing experience.

    What would be great is the ability to also add a new tab of our own as well, via this PHP extensibility API.

    I sincerely hope that can make it in before launch as we’ll use it for sure 🙂

    1. Hey Josh!

      I wish this sort of extensibility was considered for Cart/Checkout blocks as well

      We’re following a path where any extensibility API that make sense to be PHP would be in PHP, but I’d still love to hear what you had in mind existing APIs, are non of them meeting your demand, or do you think some of them are needlessly JS?

      1. Hey Senadir,

        I think Cart/Checkout blocks did reinvent a few things that didn’t need reinventing.

        Just two examples off the top of my head:
        – Printing of notices (yes, it picks up registered notices but wp_print_notice was something 3rd party devs often used adhoc)
        – woocommerce_form_field for third party fields we wanted to register
        – Ability to filter and remove fields conditionally and easily via a PHP filter

        But we’ve made do and nearly fully adapted.

        Don’t get me wrong, I definitely wasn’t having a dig at Cart/Checkout blocks, the implementation is great and works beautifully now that we’re past most of the teething issues.

        But I’m just saying there’s a lot of PHP devs in the community being left behind and could have been made a lot easier on those not so deeply familiar with React. Some things (custom fields in particular) are a lot harder these days unless you have that deep React/JS knowledge.

        1. Thank you for the reply Josh 😀

          But I’m just saying there’s a lot of PHP devs in the community being left behind and could have been made a lot easier on those not so deeply familiar with React. Some things (custom fields in particular) are a lot harder these days unless you have that deep React/JS knowledge.

          I understand that and our goal isn’t to make life harder, is that just sometimes, not all existing extensibility methods make sense for Checkout, either because it’s a new flow, or because it’s a single page, SPA model.

          To answer some of your points:

          Printing of notices (yes, it picks up registered notices but wp_print_notice was something 3rd party devs often used adhoc)

          I’m not sure I fully understand this. Checkout operates via a JSON API, it doesn’t make sense to all printing functions to run in such requests, still, if you’re printing notices at page load, they should work fine with the notice block that’s added by default.

          Calling wc_print_notices in a life cycle request isn’t ideal and you should use wc_add_notice which also supports minimal HTML and can be sent back via a request and rendered, this would be the same as throwing an exception.

          woocommerce_form_field for third party fields we wanted to register

          Because there’s more than just rendering a field (it has to be picked up by our JS and sent and persisted somewhere in backend), we didn’t launch with this.

          But you might be delighted to know that a PHP API to add additional fields is actively being worked on, which should come with much greater power than what comes with woocommerce_checkout_fields and make the devex easier to manage.

          You can read the discussion here.

          https://github.com/woocommerce/woocommerce-blocks/discussions/11173

          And you can follow the main issue here
          https://github.com/woocommerce/woocommerce-blocks/issues/11584

          Ability to filter and remove fields conditionally and easily via a PHP filter

          This is still somehow doable using locale filters, but for me, it doesn’t make sense to give much power to a third party plugin, as the default fields are needed and expected in a lot of places, and are most importantly, locale dependent. So you can use the woocommerce_get_country_locale filter and it should work fine in hiding a field for a country, but we’re unlikely to allow general deletion of address fields from all countries at once.

          Do please let us know with anything you have in mind regarding extensibility, as we’re working toward closing the gap there and bringing sensible, stable extensibility points.

          You can also read our approach about extensibility here:
          https://github.com/woocommerce/woocommerce/discussions/41304#top

    2. Matt Sherman Avatar
      Matt Sherman

      Hi Josh,

      What would be great is the ability to also add a new tab of our own as well, via this PHP extensibility API.

      This is actually possible already! Adding a new top-level group (tab) is something that should be carefully considered, as things can quickly get out of control if a merchant has a few extensions installed that all decide to create new groups for their options.

      For a custom product types (support coming soon), creating new groups will be necessary. If extending a built-in product type (such as simple products), I would encourage you to think about whether your extension’s options would better fit into one of the existing built-in groups.

      Please reach out to us (here or in the Slack or GH discussions linked above) and we can help you to determine where your extension’s options best fit. Determining the best practices for extensibility in the new product editor is something we are still working through and use cases like your extension will help us and the entire community come up with clear guidelines.

      All that said, here is example code to add a new group to any product form that has the general group!

      <?php
      
      use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
      use Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates\GroupInterface;
      use Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates\ProductFormTemplateInterface;
      
      add_action(
      	'woocommerce_block_template_area_product-form_after_add_block_general',
      	function ( BlockInterface $general_group ) {
      		$template = $general_group->get_root_template();
      
      		/*
      		 * Why is this check necessary?
      		 * - Allow VSCode to provide autocomplete for the specific product form template methods
      		 */
      		if ( ! $template instanceof ProductFormTemplateInterface ) {
      			return;
      		}
      
      		$my_group = $template->add_group(
      			array(
      				'id'         => 'your-prefix-example-group',
      				'attributes' => array(
      					'title' => __( 'My Group', 'YOUR_TEXT_DOMAIN' ),
      				),
      			)
      		);
      
      		/*
      		 * Why is this check necessary?
      		 * - Because of current bug in the add_group() docblock (https://github.com/woocommerce/woocommerce/issues/41587)
      		 */
      		if ( ! $my_group instanceof GroupInterface ) {
      			return;
      		}
      
      		$section_1 = $my_group->add_section(
      			array(
      				'id'         => 'your-prefix-example-section-1',
      				'attributes' => array(
      					'title' => __( 'My Section One', 'YOUR_TEXT_DOMAIN' ),
      				),
      			)
      		);
      
      		$section_1->add_block(
      			array(
      				'id'         => 'your-prefix-example-block-1',
      				'blockName'  => 'woocommerce/product-text-field',
      				'attributes' => array(
      					'label'    => __( 'My Block One', 'YOUR_TEXT_DOMAIN' ),
      					'property' => 'meta_data.your_prefix_example_field_1',
      				),
      			)
      		);
      
      		$section_2 = $my_group->add_section(
      			array(
      				'id'         => 'your-prefix-example-section-2',
      				'attributes' => array(
      					'title' => __( 'My Section Two', 'YOUR_TEXT_DOMAIN' ),
      				),
      			)
      		);
      
      		$section_2->add_block(
      			array(
      				'id'         => 'your-prefix-example-block-2',
      				'blockName'  => 'woocommerce/product-checkbox-field',
      				'attributes' => array(
      					'label'    => __( 'My Block Two', 'YOUR_TEXT_DOMAIN' ),
      					'property' => 'meta_data.your_prefix_example_field_2',
      				),
      			)
      		);
      	}
      );

      1. This is fantastic Matt! Thank you for sharing the full code snippet 🙂

  2. Thanks, happy to see the extensibility via PHP for those of use that don’t use React.

  3. backcourtdev Avatar
    backcourtdev

    If an extension requires interactivity not available via our generic blocks, developers can easily implement a custom block using JavaScript and React.

    I’m not sure I would call this “Easy” 🙂 when there’s no docu for it yet. Looking forward to that example soon…. and also hoping you will make the Grouped product’s product selector block re-usable.

    1. Matt Sherman Avatar
      Matt Sherman

      Hi backcourtdev,

      > I’m not sure I would call this “Easy” 🙂 when there’s no docu for it yet. Looking forward to that example soon

      In case you haven’t seen it yet, we recently published the second post in the new product editor development series, which covers creating a custom block.

      https://developer.woocommerce.com/2024/01/17/getting-to-know-woo-extending-the-new-product-editor-with-react/

      > and also hoping you will make the Grouped product’s product selector block re-usable.

      I have added this to our development backlog so that we can consider this.

  4. backcourtdev Avatar
    backcourtdev

    If an extension requires interactivity not available via our generic blocks, developers can easily implement a custom block using JavaScript and React.

    I am not sure I would refer to this as easy where there isn’t any docu on it yet 🙂 I look forward to that addition to the tutorial.

  5. This is interesting. A couple of questions come to mind:
    1. How does one add a row with multiple fields, like this one? https://prnt.sc/689zgM88wjCz. Or this, for subscriptions: https://prnt.sc/Keq98IssrGwe.
    2. How does the editor work with variations? The question above applies to them as well.
    3. Is there an action, or filter, to intercept the save and load operations? The main issue I need to face is that, for backward compatibility, I need some meta to be saved not as separate entries, but as a single meta, with a specific structure. That is, not separate meta like “my_field_1”, “my_field_2”, but just “my_field”, with the meta structured in a certain way. The data can then be “unpacked” on load. I know, it’s not a database normal form, but that has to work for the moment.
    4. The guide mentions “hiding blocks conditionally”, but it doesn’t explain how. For example, some blocks apply to Simple products, others to Subscriptions, other to variations. How does one handle that? Your documentation shows some examples with something like “editedProduct.regular_price < 10”, but it doesn’t describe what editedProduct is (it looks like a JavaScript variable), nor how it can be used, apart from the two examples.

  6. It looks like my comment was removed again, because it contained a link. Unfortunately, there’s no other way to illustrate an issue other than adding a link to a screenshot. The antispam filter should be relaxed quite a bit.

  7. Matt Sherman Avatar
    Matt Sherman

    1. How does one add a row with multiple fields, like this one? https://prnt.sc/689zgM88wjCz. Or this, for subscriptions: https://prnt.sc/Keq98IssrGwe.

    You can use the `core/columns` and `core/column` blocks to create a row with multiple fields.

    You can find an example where we do this in the implementation of the core layout template for simple products.

    https://github.com/woocommerce/woocommerce/blob/0064abbd0fc0380807080b8d9ee899e4c108f453/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/SimpleProductTemplate.php#L244

    2. How does the editor work with variations? The question above applies to them as well.

    Variations have their form defined with a separate layout template: `product-variation`.

    You can see the implementation of this layout template here:

    https://github.com/woocommerce/woocommerce/blob/0064abbd0fc0380807080b8d9ee899e4c108f453/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/ProductVariationTemplate.php

    3. Is there an action, or filter, to intercept the save and load operations? The main issue I need to face is that, for backward compatibility, I need some meta to be saved not as separate entries, but as a single meta, with a specific structure. That is, not separate meta like “my_field_1”, “my_field_2”, but just “my_field”, with the meta structured in a certain way. The data can then be “unpacked” on load. I know, it’s not a database normal form, but that has to work for the moment.

    When using the property attribute to link blocks to the underlying product data, the blocks assume/require that the product data object returned from the REST API has those properties on it.

    If you wanted to customize/configure the saving and retrieval of custom product properties, you would want to use hooks to accomplish that. The two hooks that are used are:

    woocommerce_rest_prepare_product_object
    woocommerce_rest_insert_product_object

    I’m not finding any documentation on these right now (I’m making a note that we need documentation for these), but you can see where the hooks are applied in https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php

    4. The guide mentions “hiding blocks conditionally”, but it doesn’t explain how. For example, some blocks apply to Simple products, others to Subscriptions, other to variations. How does one handle that? Your documentation shows some examples with something like “editedProduct.regular_price < 10”, but it doesn’t describe what editedProduct is (it looks like a JavaScript variable), nor how it can be used, apart from the two examples.

    editedProduct is a simple object that contains the product data that is being edited.

    I agree that the documentation is lacking here.

    You can see what the editedProduct object looks like running the following code in the browser console:

    wp.data
    .select('core')
    .getEditedEntityRecord('postType', 'product', PRODUCT_ID_GOES_HERE);

    Note that if dealing with a variation, you will need to use `product_variation` instead of `product` in the above code.

    On all non-variation products, there is a `type` property that can be used to conditionally hide blocks. For example you could do something like:

    editedProduct.type === 'simple'

    In addition to editedProduct, you can also use postType in expressions. For all non-variation products, the postType is product. For variations, the postType is product_variation.

    We have’t documented it yet, since it is very bare-bones, but if you install the WooCommerce Beta Tester, you will find a new “Show developer tools” option in the product editor’s more menu (…) that will allow you to inspect blocks and the edited product data.

    Note that the WooCommerce Beta Tester should only be installed on non-production development sites.

    https://github.com/woocommerce/woocommerce/releases/tag/wc-beta-tester-2.3.0

Leave a Reply

Your email address will not be published. Required fields are marked *