Additional Checkout Fields API. #42995
Replies: 45 comments 113 replies
-
In a perfect world. this should be done with the previous PHP hooks, like "woocommerce_checkout_fields", "woocommerce_billing_fields" and "woocommerce_shipping_fields" and the core should take care of converting that into React. |
Beta Was this translation helpful? Give feedback.
-
In one use case I have in an extension which I develop, a checkout field I add has these properties:
Another extension I develop has a custom field which has a time/date picker, though, the ultimate value for the field is the text (and the picker is a JavaScript library that updates that). This one also validates using the values of other fields from the checkout.
I would have agreed with this before I began coding for the block-based checkout, but now having done so I'm not so sure. These PHP hooks require developers to code further JavaScript in order to fully implement the behaviour of their fields for fields that have custom validation or other behaviour like the above, and that JavaScript is tied to the implementation details of the shortcode-based checkout. Trying to make the same filters work on both checkouts could be a recipe for trouble. I think it'd be better to design a system that fits in the block-based world rather than trying to retro-fit an old system. |
Beta Was this translation helpful? Give feedback.
-
Is it possible to create a new block variation for the WooCommerce checkout block and replace inner blocks with custom blocks that have custom fields? Am I on the right track with this approach?
|
Beta Was this translation helpful? Give feedback.
-
We will soon start working on adding custom fields to Checkout block. With this iteration, we hope to add a more complete solution, that interpolates well with Store API, and is backward-compatible to some extent. The implementation would render fields in Checkout block, sync them between billing and shipping forms if needed, render them in the order screen and other screens, and accept them via Store API, and possibly more. Pseudo-code API
register_checkout_field(
array(
'namespace' => 'my_plugin',
'id' => 'gift_text',
'label' => __( 'Gift message', 'my_plugin' ),
'type' => 'text',
'sections' => [ 'additional_fields' ],
'required' => false
)
);
add_action(
'woocommerce_store_api_validate_my_plugin_gift_text_field',
function( $value, $order ) {
if ( is_invalid( $value ) ) {
throw WP_Error( 'my_plugin_invalid_field', __( 'Gift field is invalid', 'my_plugin' ) );
}
return true;
}
);
register_checkout_field(
array(
'namespace' => 'germanized_woo',
'id' => 'vat_id',
'label' => __('VAT ID', 'germanized_woo'),
'type' => 'text',
'sections' => [ 'billing_fields' ],
'validation' => '[Dd][Ee][0-9]{9}',
'country_limitation' => [ 'DE' ]
)
);
GoalThe goal of this is to allow developers to register custom fields without fiddling around with React, or figuring out the whole lifecycle of a field. Our second goal is to make those fields globally accessible, this means someone placing an order via Store API (like WooPay) can know which fields you need, collect them, and submit them. The third goal is to set the stepping stone for adding custom fields directly to the editor. This means a merchant can add a field from the template editor, and have them work the same way as fields created pragmatically. What we're not doing right nowFor now, we will only target custom fields, core fields that are locale dependent will not be touched in this iteration. Such fields are still accessible to edit and manage via existing core filters. Backward compatibilityIdeally, we don't want to break a lot of code, fields created via the To use the new APIs as well as Under the hoodBeyond the pseudocode. The idea we have is that custom fields will be registered alongside core fields, all in the default addresses object and locale fields. This would allow us to provide first-degree support for rendering them in Checkout. Existing fields settingsAs with this, existing fields settings for the Company, Second Address, and Phone fields, will all move to the same place logically. Fields visibilityBy default, all registered fields will be visible in the order screen, the order confirmation screen, and confirmation emails. Frontend handlingWhile not something we're highly supporting, custom fields would have the possibility to register custom frontend handlers, this would allow extra validation in the frontend. We will also look into introducing the possibility to register custom render handlers, this would allow you to render your own component, it's, however, not recommended to do this, and would ideally, support this on our side in new types. Conditional renderingDepending on where you register your field, it will be conditionally rendered. Fields registered in both Billing and Shipping will show up in both, fields registered into one section while the other is set to be synced might show up in the visible form regardless. This is still under-research to see if it's something we want to do or not, and might start as an opt-in at the beginning. Fields can also be rendered conditionally depending on the current country selected. This would align them with how locale field sets are rendered and shown. For now, this will be the only conditional rendering we would offer. ValidationFor now, fields will continuously be pushed to the server when they're edited, and persisted to meta, this means you can run server validation on updates, not on order placement only. We will consider adding an option for fields that don't need server validation and can be validated when the order is placed. StorageFor now, all custom fields will be stored in metadata, we will not expand the core set of fields right now. We might provide helpers in the Order object to fetch custom fields, but this is still unknown. Initial types of fields supportedAs a start, we will roll out a collection of field types that should cover the most common use cases:
If there are any other fields you think we should support, let us know. Open questionsWe want to hear your feedback about this. If you're a plugin developer who bundles custom fields into Checkout, we would love to hear about them. If your fields are dynamic in frontend, we would love to hear about how dynamic they're, and in what way they behave. If you have any other questions or requests, do let us know. |
Beta Was this translation helpful? Give feedback.
-
@DavidAnderson684 I'd love to hear more use cases that doesn't explicitly involve a VAT field, as it seems the most complicated one. For me, I don't see having a requirement on company field being filled as a requirement. As a reminder that you can still do server side validation and reject VAT values if no company value is present, given both fields are optional in your case. For the Local Pickup case, I'd love if you can walk me through it more. I understand that taxation has to be passed on exchange location (In this case, the local pickup address). I understand here that visibility might be problematic but validation can still work fine server side to reject VAT codes that don't match the Local Pickup address country. For such advanced use cases, you can still use CSS to hide fields if needed. We might expand the conditional rendering rules but for us it's essential that the list is static (so you get the same rules regardless of the current cart), make sense, and isn't too dependent on a specific implementation. We're still very early on the code phase and each feedback we hear is essentially for us to test the limits here. Also, keep in mind that each time you have to hide a custom field with CSS, we would love to hear about your use case, because whatever you did won't show up in a third-party Checkout like WooPay (the field would always be visible), and ideally we can expand conditional rendering rules in a meaningful way. |
Beta Was this translation helpful? Give feedback.
-
Hello everyone! A list of issues has been created for this project, feel free to follow the issue that interests you the most, let us know if an issue is missing. If you have feedback about a specific issue, it's best to have it there (in that issue). If it's not covered, feel free to have it here. https://github.com/woocommerce/woocommerce-blocks/issues/11584 |
Beta Was this translation helpful? Give feedback.
-
As I'm working on fields locations, I'm trying to figure out if the following reasoning make sense. This is an open question for the Woo team and third party developers. LocationsWe will allow fields to be inserted into 3 places:
Storing
QuestionsWhat are the question fields you're interested in? |
Beta Was this translation helpful? Give feedback.
-
@senadir I don't think that VAT Numbers or gov. ids are fields that are related to the (billing) address. They might be dependent on the data from the (billing) address, but they are not part of the information that identifies a shopper's address. I am not a taxation expert but we might be confusing Billing information with Billing address. In the Checkout Block, the address display logic is different depending on cart content and user choices, and we risk having to deal with visibility issues for the VAT fields. For example, the EU VAT Number extension adds a new checkout section that holds only VAT number. Structurally that would make more sense to me, especially since other fields might make sense contextually to be present there as well. Merchants can move that inner block wherever they want in the Checkout block.
For now, in the Checkout block, the Company is part of the address, but after reading this I wonder if this should be reevaluated. Looking at all these comments I think it's time to reevaluate what we are trying to achieve with this project. The intention was to allow merchants to collect additional fields useful to their order system. We've started this iteration with simple use cases in mind: special shopper notes, gift wraps, delivery preferences, CIF codes to generate the invoice. But the discussion evolved to a really specific, pertinent and complex use case. I wonder if we shouldn't handle this one as a separate effort. Because, the way I see it Additional Fields, the other possibility for Location we are offering, isn't fit for the Billing information fields. I wonder if Contact Info would be better? Thinking about the whole Checkout block UX, the context of the custom fields, and the context of VAT information we need to evaluate if it's better to simply create a new billing information section and create custom fields targeting it exclusively. |
Beta Was this translation helpful? Give feedback.
-
Another discussion started by @passatgt kinda hints in the direction that billing information is separate from billing address |
Beta Was this translation helpful? Give feedback.
-
Let's try not to go out of focus here and get a first iteration of the functionality, or else we'll never going to have it: allow 3rd party developers to add custom checkout fields easily. Maybe allow them for now in 4 locations: contact, billing, shipping and additional fields. If possible in a retro-compatible way with the existing woocommerce_checkout_fields filter, but definitely without having to do React. The idea of being able to create additional sections, like @ralucaStan mentioned about the EU VAT Number extension seems like an awesome functionality (and I REALLY want it), but I don't think it should be considered for a first iteration of this. |
Beta Was this translation helpful? Give feedback.
-
Should we allow different fields in Shipping and Billing?Note: this is not a technical problem, but purely a UX one. I'd appreciate some product/design input into those proposals (@elizaan36 @pmcpinto), as well as examples of why a field would need to be in a single form and not both, and why that field can't live somewhere else. Part of the additional fields project is deciding where we allow fields to be inserted. We have a set of initial (soft) agreements that we're going with:
Allowing different fields while honoring the "Use same address for billing" raises a UX problem, how do you surface fields from a hidden form? Up to now, every field that exists in the Shipping form also exists in the Billing form.
If we now start introducing a bunch of fields only in shipping or only in billing, we risk creating a confusing experience or a disjointed/incomplete one. I propose a bunch of possible solutions to this: Forgo the distinction between Shipping and BillingFor this solution, we don't introduce the possibility to have fields uniquely in shipping or billing, and only allow adding fields to both (Address). New fields would behave the same way as other address fields, and can be synced between billing and shipping, and also surfaced and filled differently if the forms are not synced. This would simplify the flow and create a predictable user experience. Pros:
Cons:
Remove the "Use same address for billing" when the forms differWhen we detect that the set of visible fields in Shipping is different from Billing, we show the billing form expanded as well. Pros:
Cons:
Bring hidden fields to the other form.In this solution, fields registered to a hidden form will be brought up to the other visible form, making sure they're always filled. If a customer unsync the form, the field is removed from the shipping form for example and taking to the billing one. Pros:
Cons:
Add a new step (Additional Billing|Shipping information)In this solution, we will keep Shipping and billing the behaving the same now. If a field is registered to a hidden form. We render a minimal form called Additional X information that only contains the extra fields. Pros:
Cons:
|
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
I think this would work well with the default fields, but when you start adding custom fields it could get worse. i.e. why would a single entity need to enter 2 VAT numbers for both addresses?
The merchant can control the new section title so this would work. Additionally or alternatively, the "contact information" section could be reused to labelled more generically so it could contain other fields.
@senadir Rather than bring hidden fields, could we not hide only the address fields? So Billing and Shipping Address (or information?) blocks are always visible. We move the "use same address for x" checkbox to the Billing Address Block. If toggled on, no address fields show. If toggled off, the address fields show. Custom fields appear below everything else. This negates the need to move fields between sections as both sections can be visible at all times at the cost of a slightly longer form (extra heading). |
Beta Was this translation helpful? Give feedback.
-
One concern I have with |
Beta Was this translation helpful? Give feedback.
-
I think that this presentation....
... has some fundamental problems that have no good way to resolve them, that are becoming evident through the above discussion. We can leave out the third, the "additional fields" - it's the other two that are the main problem. These issues come from the fact that this (firstly contact, secondly shipping, thirdly and only potentially billing) does not map cleanly onto the three things which the checkout has to ascertain, and also from the fact that now the "billing" fields are being treated as secondary or dependent, when they have to be primary because they identify the legal purchaser. So, here's what I mean by that. When a shop is making a sale, there are three fundamental things to be communicated to the store, for which there is a clean division between those things:
In the new block scheme, the customer is first asked how to contact (by email) the purchaser, and is then asked who is receiving the order, and then finally is asked who is being billed. In the resulting division in the user interface, part of the first question (who is the legal purchaser?) is asked first, then possibly the rest of the next question is being asked together with the third in the "shipping" section, but that section also includes a "use same details?" checkbox which, if not checked, instead means that some of the first issue is actually covered in the third section instead. This is to say: notice that if the billing and shipping addresses are different, then this means that in the user experience, the legal entity doing the purchasing - which is in the user interface as the "billing first name", "billing last name" and "billing company" fields - comes late in the flow; and it's separated from the email address of that purchaser, which is asked for first. This then makes things quite confusing if more information needs to be requested about the purchaser's identity - for example, a business registration number, a government ID, a VAT number. This information is a "who is the legal purchaser?" question.... but it's not a "contact information" question (your government registration ID is not your contact information), and it's not a shipping question either. So, if your billing and shipping addresses are the same, then these questions don't belong clearly in either of the two displayed sections, so you'll end up having to create an entirely new section instead that comes after the others - an ugly experience in which the questions being asked go back and forwards between different areas (e.g. we'll ask you for the name of the purchaser in one section, and then for the purchaser's legal number in a different section). The experience is that the legal entity and their email address, is identified partly in section 1 (contact), partly in 3 (billing first/last names, company name) and partly in 4 (extra details) - that's ugly. Compare this to the previous situation, which worked better. Previously, in the shortcode checkout, the billing first name, billing last name and billing company (and their legal country) are presented first. These fields are all effectively "who is the buyer?" questions. Thus, the identification of the purchaser came first, which was quite natural. The "billing address" section was effectively a combined "who is purchasing, and how are they paying?" section; and it was then natural for any further "who?" questions to be added as billing fields (e.g. goverment IDs for the company). But now, by making shipping primary, those two things have been divided, and the result is confusion over where different details should go - things that belong together are split. Consequently new sections will get invented instead, splitting them further. So, I think putting shipping ahead of billing is simply a mistake, because it then splits the identification of the buyer over multiple parts, whereas before that was kept together. But if shipping must be put ahead of billing, then the fields previously (under the shortcode checkout) in "billing" that were also "who is the buyer?" fields should be moved up into 1., the "contact" section, and it should be relabelled to something like "Who is buying?" so that it is the logical place for all fields identifying the buyer (thus meaning no extra sections are needed). |
Beta Was this translation helpful? Give feedback.
-
What is missing in the Otherwise, this is absolutely a step in the right direction and I'm already using |
Beta Was this translation helpful? Give feedback.
-
Backward compatibilityI recently pushed work to move additional fields from a global meta key to be a per meta key data, the new structure will save fields into 1 or 2 meta keys depending on their location:
I also added some docs here about reading the data. We're aiming to release this with WooCommerce 8.9.0, in which the API is most likely to be graduated to stable. When it comes to backward compatibility, I'm still looking into the best way to support that, but the good news is that there are some existing hooks you can use to preload data and react to saving data: Reacting to saving a fieldWhen a field is saved, WooCommerce will trigger add_action(
'updated_customer_meta',
function ( $id, $customer_id, $key, $value ) {
if ( '_wc_billing/my-plugin-namespace/vat-id' === $key || '_wc_shipping/my-plugin-namespace/vat-id' === $key ) {
// Do something with the value
}
},
10,
4
); Same for orders: add_action(
'updated_order_meta',
function ( $id, $order_id, $key, $value ) {
if ( '_wc_billing/my-plugin-namespace/vat-id' === $key || '_wc_shipping/my-plugin-namespace/vat-id' === $key ) {
// Do something with the value
}
},
10,
4
); Providing an alternate valueWhen a field is being loaded from meta, WooCommerce will trigger a namespaced filter for that meta key, you can hook into it and return your value, each filter will only cover one area, billing, shipping, or additional: add_filter(
'woocommerce_customer_get__wc_shipping/my-plugin-namespace/vat-id',
function ( $value, $customer ) {
if ( '' === $value ) {
// Read value from somewhere else
$value = $customer->get_meta( '_vat_billing_id' );
}
return $value;
},
10,
2
); |
Beta Was this translation helpful? Give feedback.
-
Thanks for working on this @senadir If I understand correctly, we'll be able to store Address fields in single meta (instead of the current serialized array) but Contact and Additional fields will remain to be stored only on the serialized array. Did I get it correctly? If so, what's the reasoning behind the decision on treating them differently? |
Beta Was this translation helpful? Give feedback.
-
🙏
Where is this? |
Beta Was this translation helpful? Give feedback.
-
Renaming Additional fieldsWith the latest update, and exposing the concept of groups up to developers, I think we ended up creating some naming confusing, mainly around the work additional, to give an overview, we have:
Possible naming suggestion:
|
Beta Was this translation helpful? Give feedback.
-
In my plugin I'm adding Fiscal code and VAT code fields, and I would like to add a checkbox to toggle show/hide logic of these fields.
When checkbox is ticked In the additional section the customer would see Fiscal code and VAT code fields. It could be better if you may set 'required' as true only when fields are shown |
Beta Was this translation helpful? Give feedback.
-
Thank you for the hard work on this. Is there going to be support for the date type field as there was in the old api? |
Beta Was this translation helpful? Give feedback.
-
Another scenario. I added this field:
I'd like to add a validation logic linked to the content of billing_country field. I tried this:
but I only get values of fields added in additional location. How may I get the content of billing_country in that context? |
Beta Was this translation helpful? Give feedback.
-
Hello, I am probably late in expressing my needs. But here is mine. I am currently using in my applications, based on Woocommerce Core filters, an extension of WC addressing to pass from 2 lines to 4 lines. For a transposition to WC checkout blocks, I will need to be able to choose the position of the 2 added fields and the placeholder ('priority' and 'placeholder' attr). |
Beta Was this translation helpful? Give feedback.
-
Visibility rules for fieldsOne common pattern that surfaced here was the need to have conditional rendering for fields, this is something that is currently possible statically, in 2 ways:
Those are currently possible using core filters, we're looking to expand this so that you can:
For this, we're going to work on a filtering engine, that works both on Checkout block (JS) and Store API (PHP), you get to provide a rule or several rules, that will decide whether this field should be collected or not, and if it should be validated on Store API or not. Anatomy of a ruleWhen registering a field, you will be able to pass in an array of rules, that consist of the following: [ source, operator, target ] Sources and operators will be a provided, built-in list of options that you can pick from, and target is a value (string, number, boolean, or an array of them), for example, the following field will only be visible if the selected shipping method is my_cod_method: [ 'cart.selected_shipping', 'is', 'my_code_method' ] The following field will only render if the current cart contains a specific product with id 35 [ 'cart.items', 'contains', 35 ] And the following would only render if cart contains a subscription product [ 'cart.item_types', 'contains', 'subscription' ] Possible operatorsWe're going to start with a limited set of operators that should behave like logical operators on databases and other systems:
Possibles sourcesWe will start with a set of possible sources, sources have a hierarchy and the top categories include:
Those would allow built in values + arbitrary values that already exist on them, including other additional fields and extensions fields. cartNote that values in brackets are dynamic, and will be validated at run time.
customer
addressThis is useful if you want visibility rule per shipping and billing, with same field, for example VAT and company + country.
order
Self comparisonWe're unlikely to allow self comparison (a field including a visibility rule that targets itself), because the UX of that would be junky, but we're going to support that in a different way. Future usageOnce we have this in place, we hope we can expand it further to also include core fields and maybe blocks. FeedbackFeedback is welcome to see if we're missing any values or operators. |
Beta Was this translation helpful? Give feedback.
-
Wow. Really nice. |
Beta Was this translation helpful? Give feedback.
-
Thanks for the quick progress on this. We have a plugin that separates the normal "address" fields into individual fields for a street and housenumber. With the |
Beta Was this translation helpful? Give feedback.
-
API marked StableHey everyone! Additional fields API has been marked as stable (#46805), and will come out in WooCommerce 8.9 sometime mid May. With that release comes some changes and features, some of them are not backward compatible, but overall, your code will continue to function just fine:
For existing fields, assuming you had a field named add_filter(
'woocommerce_get_default_value_for_namespace/my-field',
function ( $value, $group, $wc_object ) {
if ( 'shipping' === $group ) {
$key = '_additional_shipping_fields';
} elseif ( 'billing' === $group ) {
$key = '_additional_billing_fields';
} else {
$key = '_additional_fields';
}
$meta_data = $wc_object->get_meta( $key, true );
if ( is_array( $meta_data ) && isset( $meta_data['namespace/my-field'] ) ) {
return $meta_data['namespace/my-field'];
}
return $value;
},
10,
3
); So to summarize, from the experimental to stable, what's backward compatible is all the code, that will continue to function as is. But the data is not backward compatible, we didn't want to provide automatic data converting because of how risky it can be for a feature that was in experimental phase. Alternatively, instead of running the code above, you can write a task that moves from the old meta key to the new one. We would love to hear your feedback and have you test the API, WC 8.9 beta will be released April 30th. |
Beta Was this translation helpful? Give feedback.
-
Frontend validationThe Product Editor team are looking to create a new mechanism that allows you to write validation rules once, and have them run in the backend and frontend at the same time, without needing to write any javascript. We're most likely going to use this as well for Additional Fields API. If you have questions, opinions, or concerns about how validation should happen server and client side, let us know in this thread or the product editor discussion. One thing I'm mostly interested in knowing is:
|
Beta Was this translation helpful? Give feedback.
-
Removing FieldsI much prefer the new block-based system so I want to keep using it. Alternatively I would also be fine with making some fields that are now required, optional and then just hiding them using css |
Beta Was this translation helpful? Give feedback.
-
In WooCommerce Core, there is a filter which enables the fields displayed in the checkout form to be modified. Using this filter enables merchants and extension developers to add custom fields to their form to collect specific information that is not usually collected, e.g. VAT Number.
We plan to implement a solution which will allow merchants to create additional checkout fields in the Checkout block.
This GitHub discussion will serve as the main discussion point for this specific feature.
Update 1: Nadir shared below an RFC of what we will add and possible features.
Update 2: Call for testing: Additional Fields is now public under experimental prefix, please test it against your use case.
Update 3: API is now stable.
Requirements
Beta Was this translation helpful? Give feedback.
All reactions