# WooCommerce Documentation - Complete This file contains the complete content of all documentation files combined. ## Getting Started with WooCommerce APIs *Source: apis/README.md* # Getting Started with WooCommerce APIs WooCommerce provides a number of programmatic APIs to interact with WooCommerce store data. ## WC REST API The WC REST API is a powerful part of WooCommerce which lets you read and write various parts of WooCommerce data such as orders, products, coupons, customers, and shipping zones. It based on the [WordPress REST API](https://developer.wordpress.org/rest-api/). Explore the [WC REST API](./rest-api/README.md) documentation. ## Store API The Store API provides public REST API endpoints for the development of customer-facing cart, checkout, and product functionality. In contrast to the WooCommerce REST API, the Store API is unauthenticated and does not provide access to sensitive store data or other customer information. Explore the [Store API](./store-api/README.md) documentation. ## Other Resources Beyond the powerful REST APIs, WooCommerce offers a suite of PHP-based APIs designed for developers to deeply integrate and extend the core functionality of their store. These APIs allow for direct interaction with WooCommerce classes, enabling custom behaviors for settings, payment gateways, shipping methods, and more. ### Settings API The Settings API is used by extensions to display, save, and load settings. Explore the [Settings API](/docs/extensions/settings-and-config/settings-api) documentation. ### Payment Gateway API The Payment Gateway API is used by extensions to interact with the payment gateway. Explore the [Payment Gateway API](/docs/features/payments/payment-gateway-api/) documentation. ### Shipping Method API The Shipping Method API is used by extensions to extend shipping methods and add their own rates. Explore the [Shipping Method API](/docs/features/shipping/shipping-method-api/) documentation. ### Payment Token API The Payment Token API is used for storing and managing payment tokens for gateways. Explore the [Payment Token API](/docs/features/payments/payment-token-api/) documentation. ### WooCommerce Code Reference The WooCommerce Code Reference is a comprehensive documentation of the WooCommerce API. It is a great resource for developers to learn about the WooCommerce API and how to use it. The WooCommerce Code Reference is a comprehensive documentation of the internal WooCommerce Classes, API, and functions. It is a great resource for developers to learn about the WooCommerce functionality and how to extend it. Explore the [WooCommerce Code Reference](https://developer.wordpress.org/reference/classes/woocommerce/) documentation. --- ## WooCommerce REST API *Source: apis/rest-api/README.md* # WooCommerce REST API The [REST API](https://woocommerce.github.io/woocommerce-rest-api-docs/#introduction) is a powerful part of WooCommerce which lets you read and write various parts of WooCommerce data such as orders, products, coupons, customers, and shipping zones. ## Requirements In order to access the REST API using the standard endpoint URI structure (e.g. `wc/v3/products`), you must have your WordPress permalinks configured to something other than "Plain". Go to **Settings > Permalinks** and choose an option. ![Permalinks options](https://developer.woocommerce.com/wp-content/uploads/2023/12/permalinks.webp) ## API reference [WooCommerce REST API Docs](https://woocommerce.github.io/woocommerce-rest-api-docs/) provides technical details and code samples for each API endpoint. ## Authentication Authentication is usually the part most developers get stuck on, so this guide will cover a quick way to test that your API is working on your server and you can authenticate. We'll use both [Postman](https://www.getpostman.com/) and [Insomnia](https://insomnia.rest/) clients in these examples. Both are free and will help you visualise what the API offers. Before proceeding, please read the [REST API docs on authentication which covers the important parts concerning API Keys and Auth](https://woocommerce.github.io/woocommerce-rest-api-docs/#authentication). We're only covering connecting over HTTPS here since it's the simplest and most secure method. You should avoid HTTP if possible. ## Generate keys To start using REST API, you first need to generate API keys. 1. Go to *WooCommerce > Settings > Advanced* 2. Go to the *REST API* tab and click *Add key*. 3. Give the key a description for your own reference, choose a user with access to orders etc, and give the key *read/write* permissions. 4. Click *Generate api key*. 5. Your keys will be shown - do not close this tab yet, the secret will be hidden if you try to view the key again. ![Generated API Keys](https://developer.woocommerce.com/wp-content/uploads/2023/12/keys.png) ## Make a basic request The request URL we'll test is `wp-json/wc/v3/orders`. On localhost the full URL may look something like this: `https://localhost:8888/wp-json/wc/v3/orders`. Modify this to use your own site URL. In Postman, you need to set the fields for request type, request URL, and the settings on the authorization tab. For Authorization, choose *basic auth* and enter your *consumer key* and *consumer secret* keys from WooCommerce into the username and password fields Once done, hit send and you'll see the JSON response from the API if all worked well. You should see something like this: ![Generated API Keys](https://developer.woocommerce.com/wp-content/uploads/2023/12/postman.png) Insomnia is almost identical to Postman; fill in the same fields and again use basic auth. ![Insomnia](https://developer.woocommerce.com/wp-content/uploads/2023/12/insomnia.png) That's it! The API is working. If you have problems connecting, you may need to disable SSL verification - see the connection issues section below. ## Common connection issues ### Connection issues with localhost and self-signed SSL certificates If you're having problems connecting to the REST API on your localhost and seeing errors like this: ![SSL Error](https://developer.woocommerce.com/wp-content/uploads/2023/12/sslerror.png) You need to disable SSL verification. In Postman you can find this in the settings: ![Postman settings](https://developer.woocommerce.com/wp-content/uploads/2023/12/postman-ssl.png) Insomnia also has this setting the preferences area: ![Insomnia settings](https://developer.woocommerce.com/wp-content/uploads/2023/12/insomnia-ssl.png) ### 401 Unauthorized Your API keys or signature is wrong. Ensure that: - The user you generated API keys for actually has access to those resources. - The username when authenticating is your consumer key. - The password when authenticating is your consumer secret. - Make a new set of keys to be sure. If your server utilizes FastCGI, check that your [authorization headers are properly read](https://web.archive.org/web/20230330133128/https://support.metalocator.com/en/articles/1654091-wp-json-basic-auth-with-fastcgi). ### Consumer key is missing Occasionally servers may not parse the Authorization header correctly (if you see a "Consumer key is missing" error when authenticating over SSL, you have a server issue). In this case, you may provide the consumer key/secret as query string parameters instead. Example: ```text https://local.wordpress.dev/wp-json/wc/v2/orders?consumer_key=XXXX&consumer_secret=XXXX ``` ### Server does not support POST/DELETE/PUT Ideally, your server should be configured to accept these types of API request, but if not you can use the [`_method` property](https://developer.wordpress.org/rest-api/using-the-rest-api/global-parameters/#_method-or-x-http-method-override-header). --- ## WooCommerce Store API *Source: apis/store-api/README.md* # WooCommerce Store API **The Store API provides public Rest API endpoints for the development of customer-facing cart, checkout, and product functionality. It follows many of the patterns used in the [WordPress REST API](https://developer.wordpress.org/rest-api/key-concepts/).** In contrast to the WooCommerce REST API, the Store API is unauthenticated and does not provide access to sensitive store data or other customer information. Example of a valid API request using cURL: ```sh curl "https://example-store.com/wp-json/wc/store/v1/products" ``` Possible uses of the Store API include: 1. Obtaining a list of products to display that can be searched or filtered 2. Adding products to the cart and returning an updated cart object for display 3. Obtaining shipping rates for a cart 4. Converting a customer’s cart to an Order, collecting addresses, and then facilitating payment ## Requirements and limitations * This is an unauthenticated API. It does not require API keys or authentication tokens for access. * All API responses return JSON-formatted data. * Data returned from the API is reflective of the current user (customer). Customer sessions in WooCommerce are cookie-based. * Store API cannot be used to look up other customers and orders by ID; only data belonging to the current user. * Likewise, Store API cannot be used to write store data e.g. settings. For more extensive access, use the authenticated [WC REST API.](https://woocommerce.github.io/woocommerce-rest-api-docs/#introduction) * Endpoints that do allow writes, for example, updating the current customer address, require a [nonce-token](https://developer.wordpress.org/plugins/security/nonces/). * Store API is render-target agnostic and should not make assumptions about where content will be displayed. For example, returning HTML would be discouraged unless the data type itself is HTML. ## Store API Namespace Resources in the Store API are all found within the `wc/store/v1` namespace, and since this API extends the WordPress API, accessing it requires the `/wp-json/` base. Currently, the only version is `v1`. If the version is omitted, `v1` will be served. Examples: ```http GET /wp-json/wc/store/v1/products GET /wp-json/wc/store/v1/cart ``` The API uses JSON to serialize data. You don’t need to specify `.json` at the end of an API URL. ## Resources and endpoints Available resources in the Store API are listed below, with links to more detailed documentation. | Resource | Methods | Endpoints | | :----------------------------------------------------------- | :----------------------------- | --------------------------------------------------------------------------------------------- | | [`Cart`](/docs/apis/store-api/resources-endpoints/cart) | `GET` | [`/wc/store/v1/cart`](/docs/apis/store-api/resources-endpoints/cart#get-cart) | | | `POST` | [`/wc/store/v1/cart/add-item`](/docs/apis/store-api/resources-endpoints/cart#add-item) | | | `POST` | [`/wc/store/v1/cart/remove-item`](/docs/apis/store-api/resources-endpoints/cart#remove-item) | | | `POST` | [`/wc/store/v1/cart/update-item`](/docs/apis/store-api/resources-endpoints/cart#update-item) | | | `POST` | [`/wc/store/v1/cart/apply-coupon`](/docs/apis/store-api/resources-endpoints/cart#apply-coupon) | | | `POST` | [`/wc/store/v1/cart/remove-coupon`](/docs/apis/store-api/resources-endpoints/cart#remove-coupon) | | | `POST` | [`/wc/store/v1/cart/update-customer`](/docs/apis/store-api/resources-endpoints/cart#update-customer) | | | `POST` | [`/wc/store/v1/cart/select-shipping-rate`](/docs/apis/store-api/resources-endpoints/cart#select-shipping-rate) | | [`Cart Items`](/docs/apis/store-api/resources-endpoints/cart-items) | `GET`, `POST`, `DELETE` | [`/wc/store/v1/cart/items`](/docs/apis/store-api/resources-endpoints/cart-items#list-cart-items) | | | `GET`, `POST`, `PUT`, `DELETE` | [`/wc/store/v1/cart/items/:key`](/docs/apis/store-api/resources-endpoints/cart-items#single-cart-item) | | [`Cart Coupons`](/docs/apis/store-api/resources-endpoints/cart-coupons) | `GET`, `POST`, `DELETE` | [`/wc/store/v1/cart/coupons`](/docs/apis/store-api/resources-endpoints/cart-coupons#list-cart-coupons) | | | `GET`, `DELETE` | [`/wc/store/v1/cart/coupon/:code`](/docs/apis/store-api/resources-endpoints/cart-coupons#single-cart-coupon) | | [`Checkout`](/docs/apis/store-api/resources-endpoints/checkout) | `GET`, `POST`, `PUT` | [`/wc/store/v1/checkout`](/docs/apis/store-api/resources-endpoints/checkout) | | [`Checkout order`](/docs/apis/store-api/resources-endpoints/checkout-order) | `POST` | [`/wc/store/v1/checkout/:id`](/docs/apis/store-api/resources-endpoints/checkout-order) | | [`Order`](/docs/apis/store-api/resources-endpoints/order) | `GET` | [`/wc/store/v1/order/:id`](/docs/apis/store-api/resources-endpoints/order) | | [`Products`](/docs/apis/store-api/resources-endpoints/products) | `GET` | [`/wc/store/v1/products`](/docs/apis/store-api/resources-endpoints/products#list-products) | | | `GET` | [`/wc/store/v1/products/:id`](/docs/apis/store-api/resources-endpoints/products#single-product-by-id) | | [`Product Collection Data`](/docs/apis/store-api/resources-endpoints/product-collection-data) | `GET` | [`/wc/store/v1/products/collection-data`](/docs/apis/store-api/resources-endpoints/product-collection-data) | | [`Product Attributes`](/docs/apis/store-api/resources-endpoints/product-attributes) | `GET` | [`/wc/store/v1/products/attributes`](/docs/apis/store-api/resources-endpoints/product-attributes#list-product-attributes) | | | `GET` | [`/wc/store/v1/products/attributes/:id`](/docs/apis/store-api/resources-endpoints/product-attributes#single-product-attribute) | | [`Product Attribute Terms`](/docs/apis/store-api/resources-endpoints/product-attribute-terms) | `GET` | [`/wc/store/v1/products/attributes/:id/terms`](/docs/apis/store-api/resources-endpoints/product-attribute-terms) | | [`Product Categories`](/docs/apis/store-api/resources-endpoints/product-categories) | `GET` | [`/wc/store/v1/products/categories`](/docs/apis/store-api/resources-endpoints/product-categories) | | [`Product Brands`](/docs/apis/store-api/resources-endpoints/product-brands) | `GET` | [`/wc/store/v1/products/brands`](/docs/apis/store-api/resources-endpoints/product-brands) | | [`Product Reviews`](/docs/apis/store-api/resources-endpoints/product-reviews) | `GET` | [`/wc/store/v1/products/reviews`](/docs/apis/store-api/resources-endpoints/product-reviews) | | [`Product Tags`](/docs/apis/store-api/resources-endpoints/product-tags) | `GET` | [`/wc/store/v1/products/tags`](/docs/apis/store-api/resources-endpoints/product-tags) | ## Pagination If collections contain many results, they may be paginated. When listing resources you can pass the following parameters: | Parameter | Description | | :--------- | :------------------------------------------------------------------------------------- | | `page` | Current page of the collection. Defaults to `1`. | | `per_page` | Maximum number of items to be returned in result set. Defaults to `10`. Maximum `100`. | In the example below, we list 20 products per page and return page 2. ```sh curl "https://example-store.com/wp-json/wc/store/v1/products?page=2&per_page=20" ``` Additional pagination headers are also sent back with extra information. | Header | Description | | :---------------- | :------------------------------------------------------------------------ | | `X-WP-Total` | The total number of items in the collection. | | `X-WP-TotalPages` | The total number of pages in the collection. | | `Link` | Contains links to other pages; `next`, `prev`, and `up` where applicable. | ## Status codes The following table gives an overview of how the API functions generally behave. | Request type | Description | | :----------- | :---------------------------------------------------------------------------------------------------------- | | `GET` | Access one or more resources and return `200 OK` and the result as JSON. | | `POST` | Return `201 Created` if the resource is successfully created and return the newly created resource as JSON. | | `PUT` | Return `200 OK` if the resource is modified successfully. The modified result is returned as JSON. | | `DELETE` | Returns `204 No Content` if the resource was deleted successfully. | The following table shows the possible return codes for API requests. | Response code | Description | | :----------------------- | :------------------------------------------------------------------------------------------------------------------------------------------ | | `200 OK` | The request was successful, the resource(s) itself is returned as JSON. | | `204 No Content` | The server has successfully fulfilled the request and that there is no additional content to send in the response payload body. | | `201 Created` | The POST request was successful and the resource is returned as JSON. | | `400 Bad Request` | A required attribute of the API request is missing. | | `403 Forbidden` | The request is not allowed. | | `404 Not Found` | A resource could not be accessed, for example it doesn't exist. | | `405 Method Not Allowed` | The request is not supported. | | `409 Conflict` | The request could not be completed due to a conflict with the current state of the target resource. The current state may also be returned. | | `500 Server Error` | While handling the request something went wrong server-side. | ## Contributing There are 3 main parts to each route in the Store API: 1. Route - Responsible for mapping requests to endpoints. Routes in the Store API extend the `AbstractRoute` class; this class contains shared functionality for handling requests and returning JSON responses. Routes ensure a valid response is returned and handle collections, errors, and pagination. 2. Schema - Routes do not format resources. Instead we use _Schema_ classes that represent each type of resource, for example, a Product, a Cart, or a Cart Item. Schema classes in the Store API should extend the `AbstractSchema` class. 3. Utility - In more advanced cases where the Store API needs to access complex data from WooCommerce core, or where multiple routes need access to the same data, routes should use a Controller or Utility class. For example, the Store API has an Order Controller and a Cart Controller for looking up order and cart data respectfully. Typically, routes handle the following types of requests: * `GET` requests to read product, cart, or checkout data. * `POST` and `PUT` requests to update cart and checkout data. * `DELETE` requests to remove cart data. * `OPTIONS` requests to retrieve the JSON schema for the current route. Please review the [Store API Guiding principles](/docs/apis/store-api/guiding-principles). This covers our approach to development, and topics such as versioning, what data is safe to include, and how to build new routes. ## Extensibility The approach to extensibility within the Store API is to expose certain routes and schema to the ExtendSchema class. [Documentation for contributors on this can be found here](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/client/blocks/docs/internal-developers/rest-api/extend-rest-api-new-endpoint.md). If a route includes the extensibility interface, 3rd party developers can use the shared `ExtendSchema::class` instance to register additional endpoint data and additional schema. This differs from the traditional filter hook approach in that it is more limiting, but it reduces the likelihood of a 3rd party extension breaking routes and endpoints or overwriting returned data which other apps may rely upon. If new schema is required, and any of the following statements are true, choose to _extend_ the Store API rather than introducing new schema to existing Store API schemas: * The data is part of an extension, not core * The data is related to a resource, but not technically part of it * The data is difficult to query (performance wise) or has a very narrow or niche use-case If the data is sensitive (for example, a core setting that should be private), or not related to the current user (for example, looking up an order by order ID), [choose to use the authenticated WC REST API](https://woocommerce.github.io/woocommerce-rest-api-docs/#introduction). If you're looking to add _new routes and endpoints_, rather than extending the Store API _schema_, extending the Store API is not necessary. You can instead utilize core WordPress functionality to create new routes, choosing to use the same pattern of Store API if you wish. See: * [`register_rest_route()`](https://developer.wordpress.org/reference/functions/register_rest_route/) * [Rest API Permissions Callbacks](https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-custom-endpoints/#permissions-callback) --- ## Cart Tokens *Source: apis/store-api/cart-tokens.md* # Cart Tokens Cart tokens can be used instead of cookies based sessions for headless interaction with carts. When using a `Cart-Token` a [Nonce Token](/docs/apis/store-api/nonce-tokens) is not required. ## Obtaining a Cart Token Requests to `/cart` endpoints return a `Cart-Token` header alongside the response. This contains a token which can later be sent as a request header to the Store API Cart and Checkout endpoints to identify the cart. The quickest method of obtaining a Cart Token is to make a GET request `/wp-json/wc/store/v1/cart` and observe the response headers. You should see a `Cart-Token` header there. ## How to use a Cart-Token To use a `Cart-Token`, include it as a header with your request. The response will contain the current cart state from the session associated with the `Cart-Token`. **Example:** ```sh curl --header "Cart-Token: 12345" --request GET https://example-store.com/wp-json/wc/store/v1/cart ``` The same method will allow you to checkout using a `Cart-Token` on the `/checkout` route. --- ## Store API Guiding principles *Source: apis/store-api/guiding-principles.md* # Store API Guiding principles The following principles should be considered when extending, creating, or updating endpoints in the Store API. ## Routes must include a [well-defined JSON schema](https://developer.wordpress.org/rest-api/extending-the-rest-api/schema/) Each route/endpoint requires a particular structure of input data and should return data using a defined and predictable structure. This is defined in the JSON schema, which contains a comprehensive list of all of the properties the API can return and which input parameters it can accept. Well-defined schema also provides a layer of security, as it enables us to validate and sanitize the requests being made to the API. When defining schema, take note of the [WordPress REST API handbook](https://developer.wordpress.org/rest-api/extending-the-rest-api/schema/) which documents available properties and types, as well as the [JSON schema standard](http://json-schema.org/). In addition to this: * Properties should use snake_case 🐍 * Ambiguous terms should be avoided, and property names should try to use understandable language, rather than "WooCommerce" terminology or setting names * Properties should be defined using US English, but the descriptions of fields should be localized * Multiple types are permitted, for example, using a `null` type if a value is not applicable * `sanitize_callback` and `validate_callback` are encouraged where possible to ensure data is received in the correct format before processing requests If you’re struggling to define a consistent schema, your approach may be flawed. A common real-world example of this would be representing something like _Post Tags_. It may be tempting to use the Slug as the property field name in the response: ```php tags: [ "my-tag": { // ...tag data }, "my-other-tag": { // ...tag data } ] ``` However, this is difficult to represent in Schema and is not predictable for the client. A better approach would be to use an array of data, with one of the properties being the Slug: ```php tags: [ { "slug": "my-tag", // ...tag data }, { "slug": "my-other-tag", // ...tag data } ] ``` ## Routes should be designed around resources with a single type of schema Routes should be designed around resources (nouns) rather than operations (verbs). Routes should also return only one type of data defined by their Schema. For example: | Route | Resource type | Expected data | | ------------------------ | ------------- | --------------------------- | | `wc/store/v1/cart` | Cart | A cart object | | `wc/store/v1/cart/items` | Cart Item | A list of cart item objects | | `wc/store/v1/products` | Product | A list of product objects | | `wc/store/v1/products/1` | Product | A product object | There are 2 notable exceptions to this rule in the Store API; _Errors_ and _Cart Operations_. ### Error Handling Errors, including validation errors, should return an error response code (4xx or 5xx) and a [`WP_Error` object](https://developer.wordpress.org/reference/classes/wp_error/). The `AbstractRoute` class will handle the conversion of the `WP_Error` object into a valid JSON response. Error messages should be localized, but do not need to be written with language aimed at customers (clients should use the given error code to create customer-facing notices as needed). Error codes should have the prefix `woocommerce_rest_`. ### Cart Operations Some endpoints are designed around operations to avoid clients needing to make multiple round trips to the API. This is purely for convenience. An example would be the `wc/store/v1/cart/add-item` endpoint which accepts a quantity and product ID, but returns a full cart object, rather than just an updated list of items. ## Exposed data must belong to the current user or be non-sensitive Resources, including customer and order data, should reflect only the current session. Do not return data for other customers as this would be a breach of privacy and security issue. Store data such as settings (for example, store currency) is permitted in responses, but _private or sensitive data_ must be avoided. To allow more extensive access to data, you must use the authenticated [WC REST API](https://woocommerce.github.io/woocommerce-rest-api-docs/#introduction). Data returned from the API should not be [escaped](https://developer.wordpress.org/themes/theme-security/data-sanitization-escaping/) (this is left to the client rendering it), but it should be sanitized. For example, HTML should be run through [`wp_kses_post`](https://developer.wordpress.org/reference/functions/wp_kses_post/). It is the client’s responsibility to properly escape data that comes from the API, but we should try to avoid returning data that is potentially unsafe. ## Collections of resources should be paginated Large volumes of data should be paginated to avoid overwhelming the server. For example, returning a collection of products. * Use the response Headers `X-WP-Total`, `X-WP-TotalPages`, and Link to indicate available resources. * Use parameters `page` and `per_page` to retrieve certain pages. * The maximum allowed value for `per_page` is 100. ## API Responses should use standard HTTP status codes When returning content, use a valid HTTP response code such as: * `200 OK` for successful responses (this is the default response code). * `201 Created` when creating a resource, for example, adding a new cart item or applying a new coupon. * `204 No Content` for successful deletes. * `400 Bad Request` when a required parameter is not set. * `403 Forbidden` when a request is not allowed, for example, if the provided security nonce is invalid. * `404 Not Found` if a resource does not exist. * `409 Conflict` if a resource cannot be updated, for example, if something in the cart is invalid and removed during the request. A note on `DELETE` requests, a common pattern in the WordPress REST API is to return the deleted object. In the case of the Store API, we opt to return an empty response with status code `204 No Content` instead. This is more efficient. [A full list of HTTP status codes can be found here.](https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml) ## Breaking changes should be avoided where possible The Store API establishes a contract between itself and API consumers via the use of Schema. This contract should not be broken unless absolutely necessary. If a breaking change were necessary, a new version of the Store API would need to be released. A breaking change is anything that changes the format of existing Schema, removes a Schema property, removes an existing route, or makes a backwards-incompatible change to anything public that may already be in use by consumers. Breaking changes can be avoided by [deprecating existing properties](http://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.3) rather than removing them, or [deprecating routes](https://datatracker.ietf.org/doc/html/rfc8594) and replacing them with a different route if major changes are needed. Non-breaking changes are always permitted without the need to increase the API version. Some examples of these include: * Adding new properties to schema * Adding new routes, endpoints, methods * Adding optional request parameters * Re-ordering response fields The version will not increase for bug fixes unless the scope of the bug causes a backwards-incompatible change. Fixes would not be rolled back to past API versions with the exception of security issues that require backporting. --- ## Nonce Tokens *Source: apis/store-api/nonce-tokens.md* # Nonce Tokens Nonces are generated numbers used to verify origin and intent of requests for security purposes. You can read more about [nonces in the WordPress codex](https://developer.wordpress.org/apis/security/nonces/). ## Store API Endpoints that Require Nonces POST requests to the `/cart` endpoints and all requests to the `/checkout` endpoints require a nonce to function. Failure to provide a valid nonce will return an error response, unless you're using [Cart Tokens](/docs/apis/store-api/cart-tokens) instead. ## Sending Nonce Tokens with requests Nonce tokens are included with the request headers. Create a request header named `Nonce`. This will be validated by the API. **Example:** ```sh curl --header "Nonce: 12345" --request GET https://example-store.com/wp-json/wc/store/v1/checkout ``` After making a successful request, an updated `Nonce` header will be sent back--this needs to be stored and updated by the client to make subsequent requests. ## Generating security nonces from WordPress Nonces must be created using the [`wp_create_nonce` function](https://developer.wordpress.org/reference/functions/wp_create_nonce/) with the key `wc_store_api`. ```php wp_create_nonce( 'wc_store_api' ) ``` There is no other mechanism in place for creating nonces. ## Disabling Nonces for Development If you want to test REST endpoints without providing a nonce, you can use the following filter: ```php add_filter( 'woocommerce_store_api_disable_nonce_check', '__return_true' ); ``` Nonce checks will be bypassed if `woocommerce_store_api_disable_nonce_check` evaluates to `true`. NOTE: This should only be done on development sites where security is not important. Do not enable this in production. --- ## Rate Limiting for Store API endpoints *Source: apis/store-api/rate-limiting.md* # Rate Limiting for Store API endpoints [Rate Limiting](https://github.com/woocommerce/woocommerce-blocks/pull/5962) is available for Store API endpoints. This is optional and disabled by default. It can be enabled by following [these instructions](#rate-limiting-options-filter). The main purpose prevent abuse on endpoints from excessive calls and performance degradation on the machine running the store. Rate limit tracking is controlled by either `USER ID` (logged in), `IP ADDRESS` (unauthenticated requests) or filter defined logic to fingerprint and group requests. It also offers standard support for running behind a proxy, load balancer, etc. This also optional and disabled by default. ## UI Control Currently, this feature is only controlled via the `woocommerce_store_api_rate_limit_options` filter. To control it via a UI, you can use the following community plugin: [Rate Limiting UI for WooCommerce](https://wordpress.org/plugins/rate-limiting-ui-for-woocommerce/). ## Checkout rate limiting You can enable rate limiting for Checkout place order and `POST /checkout` endpoint only via the UI by going to WooCommerce -> Settings -> Advanced -> Features and enabling "Rate limiting Checkout block and Store API". When enabled via the UI, the rate limiting will only be applied to the `POST /checkout` and Place Order flow for Checkout block. The limit will be a maximum of 3 requests per 60 seconds. ## Limit information A default maximum of 25 requests can be made within a 10-second time frame. These can be changed through an [options filter](#rate-limiting-options-filter). ## Methods restricted by Rate Limiting `POST`, `PUT`, `PATCH`, and `DELETE` ## Rate Limiting options filter A filter is available for setting options for rate limiting: ```php add_filter( 'woocommerce_store_api_rate_limit_options', function() { return [ 'enabled' => false, // enables/disables Rate Limiting. Default: false 'proxy_support' => false, // enables/disables Proxy support. Default: false 'limit' => 25, // limit of request per timeframe. Default: 25 'seconds' => 10, // timeframe in seconds. Default: 10 ]; } ); ``` ## Proxy standard support If the Store is running behind a proxy, load balancer, cache service, CDNs, etc. keying limits by IP is supported through standard IP forwarding headers, namely: * `X_REAL_IP`|`CLIENT_IP` _Custom popular implementations that simplify obtaining the origin IP for the request_ * `X_FORWARDED_FOR` _De-facto standard header for identifying the originating IP, [Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For)_ * `X_FORWARDED` _[Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded), [RFC 7239](https://datatracker.ietf.org/doc/html/rfc7239)_ This is disabled by default. ## Enable Rate Limit by request custom fingerprinting For more advanced use cases, you can enable rate limiting by custom fingerprinting. This allows for a custom implementation to group requests without relying on logged-in User ID or IP Address. ### Custom basic example for grouping requests by User-Agent and Accept-Language combination ```php add_filter( 'woocommerce_store_api_rate_limit_id', function() { $accept_language = isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) : ''; return md5( wc_get_user_agent() . $accept_language ); } ); ``` ## Limit usage information observability Current limit information can be observed via custom response headers: * `RateLimit-Limit` _Maximum requests per time frame._ * `RateLimit-Remaining` _Requests available during current time frame._ * `RateLimit-Reset` _Unix timestamp of next time frame reset._ * `RateLimit-Retry-After` _Seconds until requests are unblocked again. Only shown when the limit is reached._ ### Response headers example ```http RateLimit-Limit: 5 RateLimit-Remaining: 0 RateLimit-Reset: 1654880642 RateLimit-Retry-After: 28 ``` ## Tracking limit abuses This uses a modified wc_rate_limit table with an additional remaining column for tracking the request count in any given request window. A custom action `woocommerce_store_api_rate_limit_exceeded` was implemented for extendability in tracking such abuses. ### Custom tracking usage example ```php add_action( 'woocommerce_store_api_rate_limit_exceeded', function ( $offending_ip, $action_id ) { /* Custom tracking implementation */ } ); ``` --- ## Cart Coupons API *Source: apis/store-api/resources-endpoints/cart-coupons.md* # Cart Coupons API ## List Cart Coupons ```http GET /cart/coupons ``` There are no parameters required for this endpoint. ```sh curl "https://example-store.com/wp-json/wc/store/v1/cart/coupons" ``` **Example response:** ```json [ { "code": "20off", "type": "fixed_cart", "totals": { "currency_code": "GBP", "currency_symbol": "£", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "£", "currency_suffix": "", "total_discount": "1667", "total_discount_tax": "333" }, "_links": { "self": [ { "href": "http://local.wordpress.test/wp-json/wc/store/v1/cart/coupons/20off" } ], "collection": [ { "href": "http://local.wordpress.test/wp-json/wc/store/v1/cart/coupons" } ] } } ] ``` ## Single Cart Coupon Get a single cart coupon. ```http GET /cart/coupons/:code ``` | Attribute | Type | Required | Description | | :-------- | :----- | :------: | :---------------------------------------------- | | `code` | string | Yes | The coupon code of the cart coupon to retrieve. | ```sh curl "https://example-store.com/wp-json/wc/store/v1/cart/coupons/20off" ``` **Example response:** ```json { "code": "halfprice", "type": "percent", "totals": { "currency_code": "GBP", "currency_symbol": "£", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "£", "currency_suffix": "", "total_discount": "9950", "total_discount_tax": "0" } } ``` ## Add Cart Coupon Apply a coupon to the cart. Returns the new coupon object that was applied, or an error if it was not applied. ```http POST /cart/coupons/ ``` | Attribute | Type | Required | Description | | :-------- | :----- | :------: | :--------------------------------------------- | | `code` | string | Yes | The coupon code you wish to apply to the cart. | ```sh curl --request POST https://example-store.com/wp-json/wc/store/v1/cart/coupons?code=20off ``` **Example response:** ```json { "code": "20off", "type": "percent", "totals": { "currency_code": "GBP", "currency_symbol": "£", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "£", "currency_suffix": "", "total_discount": "1667", "total_discount_tax": "333" } } ``` ## Delete Single Cart Coupon Delete/remove a coupon from the cart. ```http DELETE /cart/coupons/:code ``` | Attribute | Type | Required | Description | | :-------- | :----- | :------: | :------------------------------------------------ | | `code` | string | Yes | The coupon code you wish to remove from the cart. | ```sh curl --request DELETE https://example-store.com/wp-json/wc/store/v1/cart/coupons/20off ``` ## Delete All Cart Coupons Delete/remove all coupons from the cart. ```http DELETE /cart/coupons/ ``` There are no parameters required for this endpoint. ```sh curl --request DELETE https://example-store.com/wp-json/wc/store/v1/cart/coupons ``` **Example response:** ```json [] ``` --- ## Cart Items API *Source: apis/store-api/resources-endpoints/cart-items.md* # Cart Items API ## List Cart Items ```http GET /cart/items ``` There are no extra parameters needed to use this endpoint. ```sh curl "https://example-store.com/wp-json/wc/store/v1/cart/items" ``` **Example response:** ```json [ { "key": "c74d97b01eae257e44aa9d5bade97baf", "id": 16, "quantity": 1, "type": "simple", "quantity_limits": { "minimum": 1, "maximum": 1, "multiple_of": 1, "editable": false }, "name": "Beanie", "short_description": "

This is a simple product.

", "description": "

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.

", "sku": "woo-beanie", "low_stock_remaining": null, "backorders_allowed": false, "show_backorder_badge": false, "sold_individually": true, "permalink": "https://store.local/product/beanie/", "images": [ { "id": 45, "src": "https://store.local/wp-content/uploads/2023/01/beanie-2.jpg", "thumbnail": "https://store.local/wp-content/uploads/2023/01/beanie-2-450x450.jpg", "srcset": "https://store.local/wp-content/uploads/2023/01/beanie-2.jpg 801w, https://store.local/wp-content/uploads/2023/01/beanie-2-450x450.jpg 450w, https://store.local/wp-content/uploads/2023/01/beanie-2-100x100.jpg 100w, https://store.local/wp-content/uploads/2023/01/beanie-2-600x600.jpg 600w, https://store.local/wp-content/uploads/2023/01/beanie-2-300x300.jpg 300w, https://store.local/wp-content/uploads/2023/01/beanie-2-150x150.jpg 150w, https://store.local/wp-content/uploads/2023/01/beanie-2-768x768.jpg 768w", "sizes": "(max-width: 801px) 100vw, 801px", "name": "beanie-2.jpg", "alt": "" } ], "variation": [], "item_data": [], "prices": { "price": "1800", "regular_price": "2000", "sale_price": "1800", "price_range": null, "currency_code": "USD", "currency_symbol": "$", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "$", "currency_suffix": "", "raw_prices": { "precision": 6, "price": "18000000", "regular_price": "20000000", "sale_price": "18000000" } }, "totals": { "line_subtotal": "1800", "line_subtotal_tax": "360", "line_total": "1800", "line_total_tax": "360", "currency_code": "USD", "currency_symbol": "$", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "$", "currency_suffix": "" }, "catalog_visibility": "visible", "extensions": {}, "_links": { "self": [ { "href": "https://store.local/wp-json/wc/store/v1/cart/items/c74d97b01eae257e44aa9d5bade97baf" } ], "collection": [ { "href": "https://store.local/wp-json/wc/store/v1/cart/items" } ] } }, { "key": "e03e407f41901484125496b5ec69a76f", "id": 29, "quantity": 1, "type": "variation", "quantity_limits": { "minimum": 1, "maximum": 9999, "multiple_of": 1, "editable": true }, "name": "Hoodie", "short_description": "", "description": "

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.

", "sku": "woo-hoodie-red", "low_stock_remaining": null, "backorders_allowed": false, "show_backorder_badge": false, "sold_individually": false, "permalink": "https://store.local/product/hoodie/?attribute_pa_color=red&attribute_logo=No", "images": [ { "id": 40, "src": "https://store.local/wp-content/uploads/2023/01/hoodie-2.jpg", "thumbnail": "https://store.local/wp-content/uploads/2023/01/hoodie-2-450x450.jpg", "srcset": "https://store.local/wp-content/uploads/2023/01/hoodie-2.jpg 801w, https://store.local/wp-content/uploads/2023/01/hoodie-2-450x450.jpg 450w, https://store.local/wp-content/uploads/2023/01/hoodie-2-100x100.jpg 100w, https://store.local/wp-content/uploads/2023/01/hoodie-2-600x600.jpg 600w, https://store.local/wp-content/uploads/2023/01/hoodie-2-300x300.jpg 300w, https://store.local/wp-content/uploads/2023/01/hoodie-2-150x150.jpg 150w, https://store.local/wp-content/uploads/2023/01/hoodie-2-768x768.jpg 768w", "sizes": "(max-width: 801px) 100vw, 801px", "name": "hoodie-2.jpg", "alt": "" } ], "variation": [ { "attribute": "Color", "value": "Red" }, { "attribute": "Logo", "value": "No" } ], "item_data": [], "prices": { "price": "4200", "regular_price": "4500", "sale_price": "4200", "price_range": null, "currency_code": "USD", "currency_symbol": "$", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "$", "currency_suffix": "", "raw_prices": { "precision": 6, "price": "42000000", "regular_price": "45000000", "sale_price": "42000000" } }, "totals": { "line_subtotal": "4200", "line_subtotal_tax": "840", "line_total": "4200", "line_total_tax": "840", "currency_code": "USD", "currency_symbol": "$", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "$", "currency_suffix": "" }, "catalog_visibility": "visible", "extensions": {}, "_links": { "self": [ { "href": "https://store.local/wp-json/wc/store/v1/cart/items/e03e407f41901484125496b5ec69a76f" } ], "collection": [ { "href": "https://store.local/wp-json/wc/store/v1/cart/items" } ] } } ] ``` ## Single Cart Item Get a single cart item by its key. ```http GET /cart/items/:key ``` | Attribute | Type | Required | Description | | :-------- | :----- | :------: | :------------------------------------ | | `key` | string | Yes | The key of the cart item to retrieve. | ```sh curl "https://example-store.com/wp-json/wc/store/v1/cart/items/c74d97b01eae257e44aa9d5bade97baf" ``` **Example response:** ```json { "key": "c74d97b01eae257e44aa9d5bade97baf", "id": 16, "quantity": 1, "quantity_limits": { "minimum": 1, "maximum": 1, "multiple_of": 1, "editable": false }, "name": "Beanie", "short_description": "

This is a simple product.

", "description": "

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.

", "sku": "woo-beanie", "low_stock_remaining": null, "backorders_allowed": false, "show_backorder_badge": false, "sold_individually": true, "permalink": "https://store.local/product/beanie/", "images": [ { "id": 45, "src": "https://store.local/wp-content/uploads/2023/01/beanie-2.jpg", "thumbnail": "https://store.local/wp-content/uploads/2023/01/beanie-2-450x450.jpg", "srcset": "https://store.local/wp-content/uploads/2023/01/beanie-2.jpg 801w, https://store.local/wp-content/uploads/2023/01/beanie-2-450x450.jpg 450w, https://store.local/wp-content/uploads/2023/01/beanie-2-100x100.jpg 100w, https://store.local/wp-content/uploads/2023/01/beanie-2-600x600.jpg 600w, https://store.local/wp-content/uploads/2023/01/beanie-2-300x300.jpg 300w, https://store.local/wp-content/uploads/2023/01/beanie-2-150x150.jpg 150w, https://store.local/wp-content/uploads/2023/01/beanie-2-768x768.jpg 768w", "sizes": "(max-width: 801px) 100vw, 801px", "name": "beanie-2.jpg", "alt": "" } ], "variation": [], "item_data": [], "prices": { "price": "1800", "regular_price": "2000", "sale_price": "1800", "price_range": null, "currency_code": "USD", "currency_symbol": "$", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "$", "currency_suffix": "", "raw_prices": { "precision": 6, "price": "18000000", "regular_price": "20000000", "sale_price": "18000000" } }, "totals": { "line_subtotal": "1800", "line_subtotal_tax": "360", "line_total": "1800", "line_total_tax": "360", "currency_code": "USD", "currency_symbol": "$", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "$", "currency_suffix": "" }, "catalog_visibility": "visible", "extensions": {}, "_links": { "self": [ { "href": "https://store.local/wp-json/wc/store/v1/cart/items/(?P[\\w-]{32})/c74d97b01eae257e44aa9d5bade97baf" } ], "collection": [ { "href": "https://store.local/wp-json/wc/store/v1/cart/items/(?P[\\w-]{32})" } ] } } ``` ## Add Cart Item Add an item to the cart. Returns the new cart item that was added, or an error response. ```http POST /cart/items/ ``` | Attribute | Type | Required | Description | | :---------- | :------ | :------: | :--------------------------------------------------------------------------------------------------- | | `id` | integer | Yes | The cart item product or variation ID. | | `quantity` | integer | Yes | Quantity of this item in the cart. | | `variation` | array | Yes | Chosen attributes (for variations) containing an array of objects with keys `attribute` and `value`. | ```sh curl --request POST https://example-store.com/wp-json/wc/store/v1/cart/items?id=100&quantity=1 ``` For an example response, see [Single Cart Item](#single-cart-item). If you're looking to add multiple items to the cart at once, please take a look at [batching](/docs/apis/store-api/resources-endpoints/cart#add-item). ## Edit Single Cart Item Edit an item in the cart. ```http PUT /cart/items/:key ``` | Attribute | Type | Required | Description | | :--------- | :------ | :------: | :--------------------------------- | | `key` | string | Yes | The key of the cart item to edit. | | `quantity` | integer | Yes | Quantity of this item in the cart. | ```sh curl --request PUT https://example-store.com/wp-json/wc/store/v1/cart/items/e369853df766fa44e1ed0ff613f563bd?quantity=10 ``` For an example response, see [Single Cart Item](#single-cart-item). ## Delete Single Cart Item Removes an item from the cart by its key. ```http DELETE /cart/items/:key ``` | Attribute | Type | Required | Description | | :-------- | :----- | :------: | :-------------------------------- | | `key` | string | Yes | The key of the cart item to edit. | ```sh curl --request DELETE https://example-store.com/wp-json/wc/store/v1/cart/items/e369853df766fa44e1ed0ff613f563bd ``` ## Delete All Cart Items Removes all items from the cart at once. ```http DELETE /cart/items/ ``` There are no extra parameters needed to use this endpoint. ```sh curl --request DELETE https://example-store.com/wp-json/wc/store/v1/cart/items ``` **Example response:** ```json [] ``` --- ## Cart API *Source: apis/store-api/resources-endpoints/cart.md* # Cart API The cart API returns the current state of the cart for the current session or logged in user. All POST endpoints require a [Nonce Token](/docs/apis/store-api/nonce-tokens) or a [Cart Token](/docs/apis/store-api/cart-tokens) and return the updated state of the full cart once complete. ## Get Cart ```http GET /cart ``` There are no parameters required for this endpoint. ```sh curl "https://example-store.com/wp-json/wc/store/v1/cart" ``` Returns the full cart object response (see [Cart Response](#cart-response)). ## Responses All endpoints under `/cart` (listed in this doc) return responses in the same format; a cart object which includes cart items, applied coupons, shipping addresses and rates, and non-sensitive customer data. ### Cart Response ```json { "items": [ { "key": "a5771bce93e200c36f7cd9dfd0e5deaa", "id": 38, "quantity": 1, "quantity_limits": { "minimum": 1, "maximum": 9999, "multiple_of": 1, "editable": true }, "name": "Beanie with Logo", "short_description": "

This is a simple product.

", "description": "

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.

", "sku": "Woo-beanie-logo", "low_stock_remaining": null, "backorders_allowed": false, "show_backorder_badge": false, "sold_individually": false, "permalink": "https://local.wordpress.test/product/beanie-with-logo/", "images": [ { "id": 61, "src": "https://local.wordpress.test/wp-content/uploads/2023/03/beanie-with-logo-1.jpg", "thumbnail": "https://local.wordpress.test/wp-content/uploads/2023/03/beanie-with-logo-1-450x450.jpg", "srcset": "https://local.wordpress.test/wp-content/uploads/2023/03/beanie-with-logo-1.jpg 800w, https://local.wordpress.test/wp-content/uploads/2023/03/beanie-with-logo-1-450x450.jpg 450w, https://local.wordpress.test/wp-content/uploads/2023/03/beanie-with-logo-1-100x100.jpg 100w, https://local.wordpress.test/wp-content/uploads/2023/03/beanie-with-logo-1-600x600.jpg 600w, https://local.wordpress.test/wp-content/uploads/2023/03/beanie-with-logo-1-300x300.jpg 300w, https://local.wordpress.test/wp-content/uploads/2023/03/beanie-with-logo-1-150x150.jpg 150w, https://local.wordpress.test/wp-content/uploads/2023/03/beanie-with-logo-1-768x768.jpg 768w", "sizes": "(max-width: 800px) 100vw, 800px", "name": "beanie-with-logo-1.jpg", "alt": "" } ], "variation": [], "item_data": [], "prices": { "price": "1800", "regular_price": "2000", "sale_price": "1800", "price_range": null, "currency_code": "USD", "currency_symbol": "$", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "$", "currency_suffix": "", "raw_prices": { "precision": 6, "price": "18000000", "regular_price": "20000000", "sale_price": "18000000" } }, "totals": { "line_subtotal": "1800", "line_subtotal_tax": "180", "line_total": "1530", "line_total_tax": "153", "currency_code": "USD", "currency_symbol": "$", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "$", "currency_suffix": "" }, "catalog_visibility": "visible", "extensions": {} }, { "key": "b6d767d2f8ed5d21a44b0e5886680cb9", "id": 22, "quantity": 1, "quantity_limits": { "minimum": 1, "maximum": 9999, "multiple_of": 1, "editable": true }, "name": "Belt", "short_description": "

This is a simple product.

", "description": "

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.

", "sku": "woo-belt", "low_stock_remaining": null, "backorders_allowed": false, "show_backorder_badge": false, "sold_individually": false, "permalink": "https://local.wordpress.test/product/belt/", "images": [ { "id": 51, "src": "https://local.wordpress.test/wp-content/uploads/2023/03/belt-2.jpg", "thumbnail": "https://local.wordpress.test/wp-content/uploads/2023/03/belt-2-450x450.jpg", "srcset": "https://local.wordpress.test/wp-content/uploads/2023/03/belt-2.jpg 801w, https://local.wordpress.test/wp-content/uploads/2023/03/belt-2-450x450.jpg 450w, https://local.wordpress.test/wp-content/uploads/2023/03/belt-2-100x100.jpg 100w, https://local.wordpress.test/wp-content/uploads/2023/03/belt-2-600x600.jpg 600w, https://local.wordpress.test/wp-content/uploads/2023/03/belt-2-300x300.jpg 300w, https://local.wordpress.test/wp-content/uploads/2023/03/belt-2-150x150.jpg 150w, https://local.wordpress.test/wp-content/uploads/2023/03/belt-2-768x768.jpg 768w", "sizes": "(max-width: 801px) 100vw, 801px", "name": "belt-2.jpg", "alt": "" } ], "variation": [], "item_data": [], "prices": { "price": "5500", "regular_price": "6500", "sale_price": "5500", "price_range": null, "currency_code": "USD", "currency_symbol": "$", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "$", "currency_suffix": "", "raw_prices": { "precision": 6, "price": "55000000", "regular_price": "65000000", "sale_price": "55000000" } }, "totals": { "line_subtotal": "5500", "line_subtotal_tax": "550", "line_total": "4675", "line_total_tax": "468", "currency_code": "USD", "currency_symbol": "$", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "$", "currency_suffix": "" }, "catalog_visibility": "visible", "extensions": {} } ], "coupons": [ { "code": "test", "discount_type": "percent", "totals": { "total_discount": "1095", "total_discount_tax": "109", "currency_code": "USD", "currency_symbol": "$", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "$", "currency_suffix": "" } } ], "fees": [], "totals": { "total_items": "7300", "total_items_tax": "730", "total_fees": "0", "total_fees_tax": "0", "total_discount": "1095", "total_discount_tax": "110", "total_shipping": "1300", "total_shipping_tax": "130", "total_price": "8256", "total_tax": "751", "tax_lines": [ { "name": "Tax", "price": "751", "rate": "10%" } ], "currency_code": "USD", "currency_symbol": "$", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "$", "currency_suffix": "" }, "shipping_address": { "first_name": "John", "last_name": "Doe", "company": "", "address_1": "Hello street", "address_2": "", "city": "beverly hills", "state": "CA", "postcode": "90211", "country": "US", "phone": "123456778" }, "billing_address": { "first_name": "John", "last_name": "Doe", "company": "", "address_1": "Hello street", "address_2": "", "city": "beverly hills", "state": "CA", "postcode": "90211", "country": "US", "email": "checkout@templates.com", "phone": "123456778" }, "needs_payment": true, "needs_shipping": true, "payment_requirements": [ "products" ], "has_calculated_shipping": true, "shipping_rates": [ { "package_id": 0, "name": "Shipment 1", "destination": { "address_1": "Hello street", "address_2": "", "city": "beverly hills", "state": "CA", "postcode": "90211", "country": "US" }, "items": [ { "key": "a5771bce93e200c36f7cd9dfd0e5deaa", "name": "Beanie with Logo", "quantity": 1 }, { "key": "b6d767d2f8ed5d21a44b0e5886680cb9", "name": "Belt", "quantity": 1 } ], "shipping_rates": [ { "rate_id": "flat_rate:10", "name": "Flat rate", "description": "", "delivery_time": "", "price": "1300", "taxes": "130", "instance_id": 10, "method_id": "flat_rate", "meta_data": [ { "key": "Items", "value": "Beanie with Logo × 1, Belt × 1" } ], "selected": true, "currency_code": "USD", "currency_symbol": "$", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "$", "currency_suffix": "" }, { "rate_id": "free_shipping:12", "name": "Free shipping", "description": "", "delivery_time": "", "price": "0", "taxes": "0", "instance_id": 12, "method_id": "free_shipping", "meta_data": [ { "key": "Items", "value": "Beanie with Logo × 1, Belt × 1" } ], "selected": false, "currency_code": "USD", "currency_symbol": "$", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "$", "currency_suffix": "" }, { "rate_id": "local_pickup:13", "name": "Local pickup", "description": "", "delivery_time": "", "price": "0", "taxes": "0", "instance_id": 13, "method_id": "local_pickup", "meta_data": [ { "key": "Items", "value": "Beanie with Logo × 1, Belt × 1" } ], "selected": false, "currency_code": "USD", "currency_symbol": "$", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "$", "currency_suffix": "" } ] } ], "items_count": 2, "items_weight": 0, "cross_sells": [], "errors": [], "payment_methods": [ "bacs", "cod" ], "extensions": {} } ``` ### Error Response If a cart action cannot be performed, an error response will be returned. This will include a reason code and an error message: ```json { "code": "woocommerce_rest_cart_invalid_product", "message": "This product cannot be added to the cart.", "data": { "status": 400 } } ``` Some error responses indicate conflicts (error 409), for example, when an item cannot be found or a coupon is no longer applied. When this type of response is returned, the current state of the cart from the server is also returned as part of the error data: ```json { "code": "woocommerce_rest_cart_invalid_key", "message": "Cart item no longer exists or is invalid.", "data": { "status": 409, "cart": { ... } } } ``` This allows the client to remain in sync with the cart data without additional requests, should the cart change or become outdated. ## Add Item Add an item to the cart and return the full cart response, or an error. This endpoint will return an error unless a valid [Nonce Token](/docs/apis/store-api/nonce-tokens) or [Cart Token](/docs/apis/store-api/cart-tokens) is provided. ```http POST /cart/add-item ``` | Attribute | Type | Required | Description | | :---------- | :------ | :------: | :---------------------------------------------------------------------------------------------------------------------------------------- | | `id` | integer | Yes | The cart item product or variation ID. | | `quantity` | integer | Yes | Quantity of this item in the cart. | | `variation` | array | Yes | Chosen attributes (for variations) containing an array of objects with keys `attribute` and `value`. See notes on attribute naming below. | ```sh curl --header "Nonce: 12345" --request POST https://example-store.com/wp-json/wc/store/v1/cart/add-item?id=100&quantity=1 ``` Returns the full [Cart Response](#cart-response) on success, or an [Error Response](#error-response) on failure. If you want to add supplemental cart item data before it is passed into `CartController::add_to_cart` use the [`woocommerce_store_api_add_to_cart_data`](https://github.com/woocommerce/woocommerce-blocks/blob/4d1c295a2bace9a4f6397cfd5469db31083d477a/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_store_api_add_to_cart_data) filter. For example: ```php add_filter( 'woocommerce_store_api_add_to_cart_data', function( $add_to_cart_data, \WP_REST_Request $request ) { if ( ! empty( $request['custom-request-param'] ) ) { $add_to_cart_data['cart_item_data']['custom-request-data'] = sanitize_text_field( $request['custom-request-param'] ); } return $add_to_cart_data; }, 10, 2 ); ``` **Variation attribute naming:** When adding variations to the cart, the naming of the attribute is important. For global attributes, the attribute posted to the API should be the slug of the attribute. This should have a `pa_` prefix. For example, if you have an attribute named `Color`, the slug will be `pa_color`. For product specific attributes, the attribute posted to the API can be one of the following: - the name of the attribute. For example, if you have an attribute named `Size`, the name will be `Size`. This is case-sensitive. - the slug of the attribute. For example, if you have an attribute named `Autograph ✏️`, the name will be `attribute_autograph-%e2%9c%8f%ef%b8%8f`. This is case-sensitive. You can get this slug from the related `select` on the product page. **Example POST body:** ```json { "id": 13, "quantity": 1, "variation": [ { "attribute": "pa_color", "value": "blue" }, { "attribute": "attribute_autograph-%e2%9c%8f%ef%b8%8f", "value": "Yes" }, { "attribute": "Logo", "value": "Yes" } ] } ``` The above example adds a product variation to the cart with attributes size and color. **Batching:** If you want to add multiple items at once, you need to use the batch endpoint: ```http POST /wc/store/v1/batch ``` The JSON payload for adding multiple items to the cart would look like this: ```json { "requests": [ { "path": "/wc/store/v1/cart/add-item", "method": "POST", "cache": "no-store", "body": { "id": 26, "quantity": 1 }, "headers": { "Nonce": "1db1d13784" } }, { "path": "/wc/store/v1/cart/add-item", "method": "POST", "cache": "no-store", "body": { "id": 27, "quantity": 1 }, "headers": { "Nonce": "1db1d13784" } } ] } ``` ## Remove Item Remove an item from the cart and return the full cart response, or an error. This endpoint will return an error unless a valid [Nonce Token](/docs/apis/store-api/nonce-tokens) or [Cart Token](/docs/apis/store-api/cart-tokens) is provided. ```http POST /cart/remove-item ``` | Attribute | Type | Required | Description | | :-------- | :----- | :------: | :-------------------------------- | | `key` | string | Yes | The key of the cart item to edit. | ```sh curl --header "Nonce: 12345" --request POST https://example-store.com/wp-json/wc/store/v1/cart/remove-item?key=e369853df766fa44e1ed0ff613f563bd ``` Returns the full [Cart Response](#cart-response) on success, or an [Error Response](#error-response) on failure. ## Update Item Update an item in the cart and return the full cart response, or an error. This endpoint will return an error unless a valid [Nonce Token](/docs/apis/store-api/nonce-tokens) or [Cart Token](/docs/apis/store-api/cart-tokens) is provided. ```http POST /cart/update-item ``` | Attribute | Type | Required | Description | | :--------- | :------ | :------: | :--------------------------------- | | `key` | string | Yes | The key of the cart item to edit. | | `quantity` | integer | Yes | Quantity of this item in the cart. | ```sh curl --header "Nonce: 12345" --request POST https://example-store.com/wp-json/wc/store/v1/cart/update-item?key=e369853df766fa44e1ed0ff613f563bd&quantity=10 ``` Returns the full [Cart Response](#cart-response) on success, or an [Error Response](#error-response) on failure. ## Apply Coupon Apply a coupon to the cart and return the full cart response, or an error. This endpoint will return an error unless a valid [Nonce Token](/docs/apis/store-api/nonce-tokens) or [Cart Token](/docs/apis/store-api/cart-tokens) is provided. ```http POST /cart/apply-coupon/ ``` | Attribute | Type | Required | Description | | :-------- | :----- | :------: | :--------------------------------------------- | | `code` | string | Yes | The coupon code you wish to apply to the cart. | ```sh curl --header "Nonce: 12345" --request POST https://example-store.com/wp-json/wc/store/v1/cart/apply-coupon?code=20off ``` Returns the full [Cart Response](#cart-response) on success, or an [Error Response](#error-response) on failure. ## Remove Coupon Remove a coupon from the cart and return the full cart response, or an error. This endpoint will return an error unless a valid [Nonce Token](/docs/apis/store-api/nonce-tokens) or [Cart Token](/docs/apis/store-api/cart-tokens) is provided. ```http POST /cart/remove-coupon/ ``` | Attribute | Type | Required | Description | | :-------- | :----- | :------: | :------------------------------------------------ | | `code` | string | Yes | The coupon code you wish to remove from the cart. | ```sh curl --header "Nonce: 12345" --request POST https://example-store.com/wp-json/wc/store/v1/cart/remove-coupon?code=20off ``` Returns the full [Cart Response](#cart-response) on success, or an [Error Response](#error-response) on failure. ## Update Customer Update customer data and return the full cart response, or an error. This endpoint will return an error unless a valid [Nonce Token](/docs/apis/store-api/nonce-tokens) or [Cart Token](/docs/apis/store-api/cart-tokens) is provided. ```http POST /cart/update-customer ``` | Attribute | Type | Required | Description | | :---------------------------- | :----- | :------: | :--------------------------------------------------------------------------------------- | | `billing_address` | object | no | Customer billing address. | | `billing_address.first_name` | string | no | Customer first name. | | `billing_address.last_name` | string | no | Customer last name. | | `billing_address.address_1` | string | no | First line of the address being shipped to. | | `billing_address.address_2` | string | no | Second line of the address being shipped to. | | `billing_address.city` | string | no | City of the address being shipped to. | | `billing_address.state` | string | no | ISO code, or name, for the state, province, or district of the address being shipped to. | | `billing_address.postcode` | string | no | Zip or Postcode of the address being shipped to. | | `billing_address.country` | string | no | ISO code for the country of the address being shipped to. | | `billing_address.email` | string | no | Email for the customer. | | `billing_address.phone` | string | no | Phone number of the customer. | | `shipping_address` | object | no | Customer shipping address. | | `shipping_address.first_name` | string | no | Customer first name. | | `shipping_address.last_name` | string | no | Customer last name. | | `shipping_address.address_1` | string | no | First line of the address being shipped to. | | `shipping_address.address_2` | string | no | Second line of the address being shipped to. | | `shipping_address.city` | string | no | City of the address being shipped to. | | `shipping_address.state` | string | no | ISO code, or name, for the state, province, or district of the address being shipped to. | | `shipping_address.postcode` | string | no | Zip or Postcode of the address being shipped to. | | `shipping_address.country` | string | no | ISO code for the country of the address being shipped to. | Returns the full [Cart Response](#cart-response) on success, or an [Error Response](#error-response) on failure. ## Select Shipping Rate Selects an available shipping rate for a package, then returns the full cart response, or an error. This endpoint will return an error unless a valid [Nonce Token](/docs/apis/store-api/nonce-tokens) or [Cart Token](/docs/apis/store-api/cart-tokens) is provided. ```http POST /cart/select-shipping-rate ``` | Attribute | Type | Required | Description | | :----------- | :------ | :------: | :---------------------------------------------- | | `package_id` | integer | yes | The ID of the shipping package within the cart. | | `rate_id` | string | yes | The chosen rate ID for the package. | ```sh curl --header "Nonce: 12345" --request POST /cart/select-shipping-rate?package_id=1&rate_id=flat_rate:1 ``` Returns the full [Cart Response](#cart-response) on success, or an [Error Response](#error-response) on failure. --- ## Checkout order API *Source: apis/store-api/resources-endpoints/checkout-order.md* # Checkout order API The checkout order API facilitates the processing of existing orders and handling payments. All checkout order endpoints require a [Nonce Token](/docs/apis/store-api/nonce-tokens) or a [Cart Token](/docs/apis/store-api/cart-tokens) otherwise these endpoints will return an error. ## Process Order and Payment Accepts the final chosen payment method, and any additional payment data, then attempts payment and returns the result. ```http POST /wc/store/v1/checkout/{ORDER_ID} ``` | Attribute | Type | Required | Description | | :----------------- | :----- | :------: | :------------------------------------------------------------------ | | `key` | string | Yes | The key for the order verification. | | `billing_email` | string | No | The email address used to verify guest orders. | | `billing_address` | object | Yes | Object of updated billing address data for the customer. | | `shipping_address` | object | Yes | Object of updated shipping address data for the customer. | | `payment_method` | string | Yes | The ID of the payment method being used to process the payment. | | `payment_data` | array | No | Data to pass through to the payment method when processing payment. | ```sh curl --header "Nonce: 12345" --request POST https://example-store.com/wp-json/wc/store/v1/checkout/{ORDER_ID} -d '{"key":"wc_order_oFmQYREzh9Tfv","billing_email":"admin@example.com","payment_method":"cheque","billing_address":{...},"shipping_address":{...}' ``` **Example request:** ```json { "key": "wc_order_oFmQYREzh9Tfv", "billing_email": "admin@example.com", "billing_address": { "first_name": "Peter", "last_name": "Venkman", "company": "", "address_1": "550 Central Park West", "address_2": "Corner Penthouse Spook Central", "city": "New York", "state": "NY", "postcode": "10023", "country": "US", "email": "admin@example.com", "phone": "555-2368" }, "shipping_address": { "first_name": "Peter", "last_name": "Venkman", "company": "", "address_1": "550 Central Park West", "address_2": "Corner Penthouse Spook Central", "city": "New York", "state": "NY", "postcode": "10023", "country": "US", "phone": "555-2368" }, "payment_method": "cheque", "payment_data": [] } ``` **Example response:** ```json { "order_id": 146, "status": "on-hold", "order_key": "wc_order_oFmQYREzh9Tfv", "customer_note": "", "customer_id": 1, "billing_address": { "first_name": "Peter", "last_name": "Venkman", "company": "", "address_1": "550 Central Park West", "address_2": "Corner Penthouse Spook Central", "city": "New York", "state": "NY", "postcode": "10023", "country": "US", "email": "admin@example.com", "phone": "555-2368" }, "shipping_address": { "first_name": "Peter", "last_name": "Venkman", "company": "", "address_1": "550 Central Park West", "address_2": "Corner Penthouse Spook Central", "city": "New York", "state": "NY", "postcode": "10023", "country": "US", "phone": "555-2368" }, "payment_method": "cheque", "payment_result": { "payment_status": "success", "payment_details": [], "redirect_url": "https://local.wordpress.test/block-checkout/order-received/146/?key=wc_order_VPffqyvgWVqWL" } } ``` ## Payment Data There are many payment gateways available for merchants to use, and each one will be expecting different `payment_data`. We cannot comprehensively list all expected requests for all payment gateways, and we would recommend reaching out to the authors of the payment gateway plugins you're working with for further information. An example of the payment data sent to the Checkout Order endpoint when using the [WooCommerce Stripe Payment Gateway](https://wordpress.org/plugins/woocommerce-gateway-stripe/) is shown below. For further information on generating a `stripe_source` please check [the Stripe documentation](https://stripe.com/docs). ```json { "payment_data": [ { "key": "stripe_source", "value": "src_xxxxxxxxxxxxx" }, { "key": "billing_email", "value": "myemail@email.com" }, { "key": "billing_first_name", "value": "Jane" }, { "key": "billing_last_name", "value": "Doe" }, { "key": "paymentMethod", "value": "stripe" }, { "key": "paymentRequestType", "value": "cc" }, { "key": "wc-stripe-new-payment-method", "value": true } ] } ``` --- ## Checkout API *Source: apis/store-api/resources-endpoints/checkout.md* # Checkout API The checkout API facilitates the creation of orders (from the current cart) and handling payments for payment methods. All checkout endpoints require either a [Nonce Token](/docs/apis/store-api/nonce-tokens) or a [Cart Token](/docs/apis/store-api/cart-tokens) otherwise these endpoints will return an error. ## Get Checkout Data Returns data required for the checkout. This includes a draft order (created from the current cart) and customer billing and shipping addresses. The payment information will be empty, as it's only persisted when the order gets updated via POST requests (right before payment processing). ```http GET /wc/store/v1/checkout ``` There are no parameters required for this endpoint. ```sh curl --header "Nonce: 12345" --request GET https://example-store.com/wp-json/wc/store/v1/checkout ``` ### Example Response ```json { "order_id": 146, "status": "checkout-draft", "order_key": "wc_order_VPffqyvgWVqWL", "customer_note": "", "customer_id": 1, "billing_address": { "first_name": "Peter", "last_name": "Venkman", "company": "", "address_1": "550 Central Park West", "address_2": "Corner Penthouse Spook Central", "city": "New York", "state": "NY", "postcode": "10023", "country": "US", "email": "admin@example.com", "phone": "555-2368" }, "shipping_address": { "first_name": "Peter", "last_name": "Venkman", "company": "", "address_1": "550 Central Park West", "address_2": "Corner Penthouse Spook Central", "city": "New York", "state": "NY", "postcode": "10023", "country": "US" }, "payment_method": "", "payment_result": { "payment_status": "", "payment_details": [], "redirect_url": "" } } ``` ## Update checkout data This endpoint allows you to update the checkout data for the current order. This can be called from the frontend to persist checkout fields, for example. ```http PUT /wc/store/v1/checkout?__experimental_calc_totals=true ``` Note the `__experimental_calc_totals` parameter. This is used to determine if the cart totals should be recalculated. This should be set to true if the cart totals are being updated in response to a PUT request, false otherwise. | Attribute | Type | Required | Description | | :------------------ | :----- | :------: | :-------------------------------------------------- | | `additional_fields` | object | No | Name => value pairs of additional fields to update. | | `payment_method` | string | No | The ID of the payment method selected. | | `order_notes` | string | No | Order notes. | ```sh curl --header "Nonce: 12345" --request PUT https://example-store.com/wp-json/wc/store/v1/checkout?additional_fields[plugin-namespace/leave-on-porch]=true&additional_fields[plugin-namespace/location-on-porch]=dsdd&payment_method=bacs&order_notes=Please%20leave%20package%20on%20back%20porch ``` ### Example Request ```json { "additional_fields": { "plugin-namespace/leave-on-porch": true, "plugin-namespace/location-on-porch": "dsdd" }, "payment_method": "bacs", "order_notes": "Please leave package on back porch" } ``` ### Example Response ```json { "order_id": 1486, "status": "checkout-draft", "order_key": "wc_order_KLpMaJ054PVlb", "order_number": "1486", "customer_note": "", "customer_id": 1, "billing_address": { "first_name": "Peter", "last_name": "Venkman", "company": "", "address_1": "550 Central Park West", "address_2": "Corner Penthouse Spook Central", "city": "New York", "state": "NY", "postcode": "10023", "country": "US", "email": "admin@example.com", "phone": "555-2368" }, "shipping_address": { "first_name": "Peter", "last_name": "Venkman", "company": "", "address_1": "550 Central Park West", "address_2": "Corner Penthouse Spook Central", "city": "New York", "state": "NY", "postcode": "10023", "country": "US" }, "payment_method": "bacs", "payment_result": null, "additional_fields": { "plugin-namespace/leave-on-porch": true, "plugin-namespace/location-on-porch": "dsdd" }, "__experimentalCart": { ... }, "extensions": {} } ``` Note the `__experimentalCart` field that is returned as part of the response. Totals will be updated on the front-end following a PUT request. This makes it possible to manipulate cart totals in response to fields persisted via the PUT request. ## Process Order and Payment Accepts the final customer addresses and chosen payment method, and any additional payment data, then attempts payment and returns the result. ```http POST /wc/store/v1/checkout ``` | Attribute | Type | Required | Description | | :------------------ | :----- | :------: | :------------------------------------------------------------------ | | `billing_address` | object | Yes | Object of updated billing address data for the customer. | | `shipping_address` | object | Yes | Object of updated shipping address data for the customer. | | `customer_note` | string | No | Note added to the order by the customer during checkout. | | `payment_method` | string | Yes | The ID of the payment method being used to process the payment. | | `payment_data` | array | No | Data to pass through to the payment method when processing payment. | | `customer_password` | string | No | Optionally define a password for new accounts. | ```sh curl --header "Nonce: 12345" --request POST https://example-store.com/wp-json/wc/store/v1/checkout?payment_method=paypal&payment_data[0][key]=test-key&payment_data[0][value]=test-value ``` ### Example Request ```json { "billing_address": { "first_name": "Peter", "last_name": "Venkman", "company": "", "address_1": "550 Central Park West", "address_2": "Corner Penthouse Spook Central", "city": "New York", "state": "NY", "postcode": "10023", "country": "US", "email": "admin@example.com", "phone": "555-2368" }, "shipping_address": { "first_name": "Peter", "last_name": "Venkman", "company": "", "address_1": "550 Central Park West", "address_2": "Corner Penthouse Spook Central", "city": "New York", "state": "NY", "postcode": "10023", "country": "US" }, "customer_note": "Test notes on order.", "create_account": false, "payment_method": "cheque", "payment_data": [], "extensions": { "some-extension-name": { "some-data-key": "some data value" } } } ``` ### Example Response ```json { "order_id": 146, "status": "on-hold", "order_key": "wc_order_VPffqyvgWVqWL", "customer_note": "", "customer_id": 1, "billing_address": { "first_name": "Peter", "last_name": "Venkman", "company": "", "address_1": "550 Central Park West", "address_2": "Corner Penthouse Spook Central", "city": "New York", "state": "NY", "postcode": "10023", "country": "US", "email": "admin@example.com", "phone": "555-2368" }, "shipping_address": { "first_name": "Peter", "last_name": "Venkman", "company": "", "address_1": "550 Central Park West", "address_2": "Corner Penthouse Spook Central", "city": "New York", "state": "NY", "postcode": "10023", "country": "US" }, "payment_method": "cheque", "payment_result": { "payment_status": "success", "payment_details": [], "redirect_url": "https://local.wordpress.test/block-checkout/order-received/146/?key=wc_order_VPffqyvgWVqWL" } } ``` ## Payment Data There are many payment gateways available for merchants to use, and each one will be expecting different `payment_data`. We cannot comprehensively list all expected requests for all payment gateways, and we would recommend reaching out to the authors of the payment gateway plugins you're working with for further information. An example of the payment data sent to the Checkout endpoint when using the [WooCommerce Stripe Payment Gateway](https://wordpress.org/plugins/woocommerce-gateway-stripe/) is shown below. For further information on generating a `stripe_source` please check [the Stripe documentation](https://stripe.com/docs). ```json { "payment_data": [ { "key": "stripe_source", "value": "src_xxxxxxxxxxxxx" }, { "key": "billing_email", "value": "myemail@email.com" }, { "key": "billing_first_name", "value": "Jane" }, { "key": "billing_last_name", "value": "Doe" }, { "key": "paymentMethod", "value": "stripe" }, { "key": "paymentRequestType", "value": "cc" }, { "key": "wc-stripe-new-payment-method", "value": true } ] } ``` --- ## Order API *Source: apis/store-api/resources-endpoints/order.md* # Order API The order API returns the pay-for-order order. ## Get Order ```http GET /order/{ORDER_ID}?key={KEY}&billing_email={BILLING_EMAIL} ``` There is one required parameter for this endpoint which is `key`. `billing_email` must be added for guest orders. ```sh curl "https://example-store.com/wp-json/wc/store/v1/order/{ORDER_ID}?key={KEY}&billing_email={BILLING_EMAIL}" ``` Returns the full order object response (see [Order Response](#order-response)). ## Responses Order endpoints return responses in the same format as `/cart`; an order object which includes order items, applied coupons, shipping addresses and rates, and non-sensitive customer data. ### Order Response ```json { "id": 147, "status": "pending", "coupons": [ { "code": "discount20", "totals": { "currency_code": "GBP", "currency_symbol": "£", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "£", "currency_suffix": "", "total_discount": "421", "total_discount_tax": "0" } } ], "shipping_address": { "first_name": "Peter", "last_name": "Venkman", "company": "", "address_1": "550 Central Park West", "address_2": "Corner Penthouse Spook Central", "city": "New York", "state": "NY", "postcode": "10023", "country": "US", "phone": "555-2368" }, "billing_address": { "first_name": "Peter", "last_name": "Venkman", "company": "", "address_1": "550 Central Park West", "address_2": "Corner Penthouse Spook Central", "city": "New York", "state": "NY", "postcode": "10023", "country": "US", "email": "admin@example.com", "phone": "555-2368" }, "items": [ { "key": "9bf31c7ff062936a96d3c8bd1f8f2ff3", "id": 15, "quantity": 1, "quantity_limits": { "minimum": 1, "maximum": 99, "multiple_of": 1, "editable": true }, "name": "Beanie", "short_description": "

This is a simple product.

", "description": "

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.

", "sku": "woo-beanie", "low_stock_remaining": null, "backorders_allowed": false, "show_backorder_badge": false, "sold_individually": false, "permalink": "https://local.wordpress.test/product/beanie/", "images": [ { "id": 44, "src": "https://local.wordpress.test/wp-content/uploads/2020/03/beanie-2.jpg", "thumbnail": "https://local.wordpress.test/wp-content/uploads/2020/03/beanie-2-324x324.jpg", "srcset": "https://local.wordpress.test/wp-content/uploads/2020/03/beanie-2.jpg 801w, https://local.wordpress.test/wp-content/uploads/2020/03/beanie-2-324x324.jpg 324w, https://local.wordpress.test/wp-content/uploads/2020/03/beanie-2-100x100.jpg 100w, https://local.wordpress.test/wp-content/uploads/2020/03/beanie-2-416x416.jpg 416w, https://local.wordpress.test/wp-content/uploads/2020/03/beanie-2-300x300.jpg 300w, https://local.wordpress.test/wp-content/uploads/2020/03/beanie-2-150x150.jpg 150w, https://local.wordpress.test/wp-content/uploads/2020/03/beanie-2-768x768.jpg 768w", "sizes": "(max-width: 801px) 100vw, 801px", "name": "beanie-2.jpg", "alt": "" } ], "variation": [], "prices": { "currency_code": "GBP", "currency_symbol": "£", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "£", "currency_suffix": "", "price": "1000", "regular_price": "2000", "sale_price": "1000", "price_range": null, "raw_prices": { "precision": 6, "price": "10000000", "regular_price": "20000000", "sale_price": "10000000" } }, "item_data": [], "totals": { "currency_code": "GBP", "currency_symbol": "£", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "£", "currency_suffix": "", "line_subtotal": "1000", "line_subtotal_tax": "0", "line_total": "800", "line_total_tax": "0" }, "catalog_visibility": "view" }, { "key": "e369853df766fa44e1ed0ff613f563bd", "id": 34, "quantity": 1, "quantity_limits": { "minimum": 1, "maximum": 99, "multiple_of": 1, "editable": true }, "name": "WordPress Pennant", "short_description": "

This is an external product.

", "description": "

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.

", "sku": "wp-pennant", "low_stock_remaining": null, "backorders_allowed": false, "show_backorder_badge": false, "sold_individually": false, "permalink": "https://local.wordpress.test/product/wordpress-pennant/", "images": [ { "id": 57, "src": "https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1.jpg", "thumbnail": "https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-324x324.jpg", "srcset": "https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1.jpg 800w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-324x324.jpg 324w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-100x100.jpg 100w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-416x416.jpg 416w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-300x300.jpg 300w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-150x150.jpg 150w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-768x768.jpg 768w", "sizes": "(max-width: 800px) 100vw, 800px", "name": "pennant-1.jpg", "alt": "" } ], "variation": [], "prices": { "currency_code": "GBP", "currency_symbol": "£", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "£", "currency_suffix": "", "price": "1105", "regular_price": "1105", "sale_price": "1105", "price_range": null, "raw_prices": { "precision": 6, "price": "11050000", "regular_price": "11050000", "sale_price": "11050000" } }, "item_data": [], "totals": { "currency_code": "GBP", "currency_symbol": "£", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "£", "currency_suffix": "", "line_subtotal": "1105", "line_subtotal_tax": "0", "line_total": "884", "line_total_tax": "0" }, "catalog_visibility": "view" } ], "needs_payment": true, "needs_shipping": true, "totals": { "subtotal":"2105", "total_discount": "421", "total_shipping": "500", "total_fees": "0", "total_tax": "0", "total_refund": "0", "total_price": "2184", "total_items": "2105", "total_items_tax": "0", "total_fees_tax": "0", "total_discount_tax": "0", "total_shipping_tax": "0", "tax_lines": [] }, "errors": [], "payment_requirements": [ "products" ], } ``` ### Error Response If an order action cannot be performed, an error response will be returned. This will include a reason code and an error message: ```json { "code": "woocommerce_rest_invalid_order", "message": "Invalid order ID or key provided.", "data": { "status": 401 } } ``` --- ## Product Attribute Terms API *Source: apis/store-api/resources-endpoints/product-attribute-terms.md* # Product Attribute Terms API ```http GET /products/attributes/:id/terms GET /products/attributes/:id/terms?orderby=slug ``` | Attribute | Type | Required | Description | | :-------- | :------ | :------: |:--------------------------------------------------------------------------------------------------------------| | `id` | integer | Yes | The ID of the attribute to retrieve terms for. | | `order` | string | no | Order ascending or descending. Allowed values: `asc`, `desc` | | `orderby` | string | no | Sort collection by object attribute. Allowed values: `id`, `name`, `name_num`, `slug`, `count`, `menu_order`. | ```sh curl "https://example-store.com/wp-json/wc/store/v1/products/attributes/1/terms" ``` **Example response:** ```json [ { "id": 22, "name": "Blue", "slug": "blue", "count": 5 }, { "id": 48, "name": "Burgundy", "slug": "burgundy", "count": 1 } ] ``` --- ## Product Attributes API *Source: apis/store-api/resources-endpoints/product-attributes.md* # Product Attributes API ## List Product Attributes ```http GET /products/attributes ``` There are no parameters required for this endpoint. ```sh curl "https://example-store.com/wp-json/wc/store/v1/products/attributes" ``` Example response: ```json [ { "id": 1, "name": "Color", "taxonomy": "pa_color", "type": "select", "order": "menu_order", "has_archives": false }, { "id": 2, "name": "Size", "taxonomy": "pa_size", "type": "select", "order": "menu_order", "has_archives": false } ] ``` ## Single Product Attribute Get a single attribute taxonomy. ```http GET /products/attributes/:id ``` | Attribute | Type | Required | Description | | :-------- | :------ | :------: | :----------------------------------- | | `id` | integer | Yes | The ID of the attribute to retrieve. | ```sh curl "https://example-store.com/wp-json/wc/store/v1/products/attributes/1" ``` **Example response:** ```json { "id": 1, "name": "Color", "taxonomy": "pa_color", "type": "select", "order": "menu_order", "has_archives": false } ``` --- ## Product Brands API *Source: apis/store-api/resources-endpoints/product-brands.md* # Product Brands API ## List Product Brands ```http GET /products/brands ``` There are no parameters required for this endpoint. ```sh curl "https://example-store.com/wp-json/wc/store/v1/products/brands" ``` Example response: ```json [ { "id": 16, "name": "Nike", "slug": "nike", "description": "This is the Nike brand.", "parent": 0, "count": 11, "image": { "id": 55, "src": "https://store.local/wp-content/uploads/2021/11/nike-logo.jpg", "thumbnail": "https://store.local/wp-content/uploads/2021/11/nike-logo-324x324.jpg", "srcset": "https://store.local/wp-content/uploads/2021/11/nike-logo.jpg 800w, https://store.local/wp-content/uploads/2021/11/nike-logo-324x324.jpg 324w, https://store.local/wp-content/uploads/2021/11/nike-logo-100x100.jpg 100w, https://store.local/wp-content/uploads/2021/11/nike-logo-416x416.jpg 416w, https://store.local/wp-content/uploads/2021/11/nike-logo-300x300.jpg 300w, https://store.local/wp-content/uploads/2021/11/nike-logo-150x150.jpg 150w, https://store.local/wp-content/uploads/2021/11/nike-logo-768x768.jpg 768w", "sizes": "(max-width: 800px) 100vw, 800px", "name": "nike-logo.jpg", "alt": "" }, "review_count": 2, "permalink": "https://store.local/product-brand/nike/" }, { "id": 21, "name": "Adidas", "slug": "adidas", "description": "", "parent": 0, "count": 1, "image": null, "review_count": 1, "permalink": "https://store.local/product-brand/adidas/" } ] ``` ## Single Product Brand Get a single brand. ```http GET /products/brands/:id ``` or ```http GET /products/brands/:slug ``` | Parameter | Type | Required | Description | | :-------- | :------ | :------: |:---------------------------------------------------------------------| | `identifier` | string | Yes | The identifier of the brand to retrieve. Can be an brand ID or slug. | ```sh curl "https://example-store.com/wp-json/wc/store/v1/products/brands/1" ``` or ```sh curl "https://example-store.com/wp-json/wc/store/v1/products/brands/adidas" ``` **Example response:** ```json { "id": 1, "name": "Adidas", "slug": "adidas", "description": "", "parent": 0, "count": 1, "image": null, "review_count": 1, "permalink": "https://store.local/product-brand/adidas/" } ``` --- ## Product Categories API *Source: apis/store-api/resources-endpoints/product-categories.md* # Product Categories API ## List Product Categories ```http GET /products/categories ``` There are no parameters required for this endpoint. ```sh curl "https://example-store.com/wp-json/wc/store/v1/products/categories" ``` Example response: ```json [ { "id": 16, "name": "Clothing", "slug": "clothing", "description": "This is the clothing category.", "parent": 0, "count": 11, "image": { "id": 55, "src": "https://store.local/wp-content/uploads/2021/11/t-shirt-with-logo-1.jpg", "thumbnail": "https://store.local/wp-content/uploads/2021/11/t-shirt-with-logo-1-324x324.jpg", "srcset": "https://store.local/wp-content/uploads/2021/11/t-shirt-with-logo-1.jpg 800w, https://store.local/wp-content/uploads/2021/11/t-shirt-with-logo-1-324x324.jpg 324w, https://store.local/wp-content/uploads/2021/11/t-shirt-with-logo-1-100x100.jpg 100w, https://store.local/wp-content/uploads/2021/11/t-shirt-with-logo-1-416x416.jpg 416w, https://store.local/wp-content/uploads/2021/11/t-shirt-with-logo-1-300x300.jpg 300w, https://store.local/wp-content/uploads/2021/11/t-shirt-with-logo-1-150x150.jpg 150w, https://store.local/wp-content/uploads/2021/11/t-shirt-with-logo-1-768x768.jpg 768w", "sizes": "(max-width: 800px) 100vw, 800px", "name": "t-shirt-with-logo-1.jpg", "alt": "" }, "review_count": 2, "permalink": "https://store.local/product-category/clothing/" }, { "id": 21, "name": "Decor", "slug": "decor", "description": "", "parent": 0, "count": 1, "image": null, "review_count": 1, "permalink": "https://store.local/product-category/decor/" } ] ``` ## Single Product Category Get a single category. ```http GET /products/categories/:id ``` | Category | Type | Required | Description | | :------- | :------ | :------: | :---------------------------------- | | `id` | integer | Yes | The ID of the category to retrieve. | ```sh curl "https://example-store.com/wp-json/wc/store/v1/products/categories/1" ``` **Example response:** ```json { "id": 1, "name": "Decor", "slug": "decor", "description": "", "parent": 0, "count": 1, "image": null, "review_count": 1, "permalink": "https://store.local/product-category/decor/" } ``` --- ## Product Collection Data API *Source: apis/store-api/resources-endpoints/product-collection-data.md* # Product Collection Data API This endpoint allows you to get aggregate data from a collection of products, for example, the min and max price in a collection of products (ignoring pagination). This is used by blocks for product filtering widgets, since counts are based on the product catalog being viewed. ```http GET /products/collection-data GET /products/collection-data?calculate_price_range=true GET /products/collection-data?calculate_attribute_counts[0][query_type]=or&calculate_attribute_counts[0][taxonomy]=pa_color GET /products/collection-data?calculate_rating_counts=true ``` | Attribute | Type | Required | Description | | :--------------------------- | :----- | :------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `calculate_price_range` | bool | No | Returns the min and max price for the product collection. If false, only `null` will be returned. | | `calculate_attribute_counts` | object | No | Returns attribute counts for a list of attribute taxonomies you pass in via this parameter. Each should be provided as an object with keys "taxonomy" and "query_type". If empty, `null` will be returned. | | `calculate_rating_counts` | bool | No | Returns the counts of products with a certain average rating, 1-5. If false, only `null` will be returned. | | `calculate_stock_status_counts` | bool | No | Returns counts of products with each stock status (in stock, out of stock, on backorder). If false, only `null` will be returned. | **In addition to the above attributes**, all product list attributes are supported. This allows you to get data for a certain subset of products. See [the products API list products section](/docs/apis/store-api/resources-endpoints/products#list-products) for the full list. ```sh curl "https://example-store.com/wp-json/wc/store/v1/products/collection-data?calculate_price_range=true&calculate_attribute_counts=pa_size,pa_color&calculate_rating_counts=true" ``` **Example response:** ```json { "price_range": [ "currency_minor_unit": 2, "min_price": "0", "max_price": "9000", "currency_code": "USD", "currency_decimal_separator": ".", "currency_minor_unit": 2, "currency_prefix": "$", "currency_suffix": "", "currency_symbol": "$", "currency_thousand_separator": ",", ], "attribute_counts": [ { "term": 22, "count": 4 }, { "term": 23, "count": 3 }, { "term": 24, "count": 4 } ], "rating_counts": [ { "rating": 3, "count": 1 }, { "rating": 4, "count": 1 } ] } ``` --- ## Product Reviews API *Source: apis/store-api/resources-endpoints/product-reviews.md* # Product Reviews API ## List Product Reviews This endpoint returns product reviews (comments) and can also show results from either specific products or specific categories. ```http GET /products/reviews GET /products/reviews?category_id=1,2,3 GET /products/reviews?product_id=1,2,3 GET /products/reviews?orderby=rating&order=desc ``` | Attribute | Type | Required | Description | | :------------ | :------ | :------: | :-------------------------------------------------------------------------------------------------- | | `page` | integer | no | Current page of the collection. | | `per_page` | integer | no | Maximum number of items to be returned in result set. Defaults to no limit if left blank. | | `offset` | integer | no | Offset the result set by a specific number of items. | | `order` | string | no | Order sort attribute ascending or descending. Allowed values: `asc`, `desc` | | `orderby` | string | no | Sort collection by object attribute. Allowed values : `date`, `date_gmt`, `id`, `rating`, `product` | | `category_id` | string | no | Limit result set to reviews from specific category IDs. | | `product_id` | string | no | Limit result set to reviews from specific product IDs. | ```sh curl "https://example-store.com/wp-json/wc/store/v1/products/collection-data?calculate_price_range=true&calculate_attribute_counts=pa_size,pa_color&calculate_rating_counts=true" ``` **Example response:** ```json [ { "id": 83, "date_created": "2022-01-12T15:42:14", "formatted_date_created": "January 12, 2022", "date_created_gmt": "2022-01-12T15:42:14", "product_id": 33, "product_name": "Beanie with Logo", "product_permalink": "https://store.local/product/beanie-with-logo/", "product_image": { "id": 56, "src": "https://store.local/wp-content/uploads/2021/11/beanie-with-logo-1.jpg", "thumbnail": "https://store.local/wp-content/uploads/2021/11/beanie-with-logo-1-324x324.jpg", "srcset": "https://store.local/wp-content/uploads/2021/11/beanie-with-logo-1.jpg 800w, https://store.local/wp-content/uploads/2021/11/beanie-with-logo-1-324x324.jpg 324w, https://store.local/wp-content/uploads/2021/11/beanie-with-logo-1-100x100.jpg 100w, https://store.local/wp-content/uploads/2021/11/beanie-with-logo-1-416x416.jpg 416w, https://store.local/wp-content/uploads/2021/11/beanie-with-logo-1-300x300.jpg 300w, https://store.local/wp-content/uploads/2021/11/beanie-with-logo-1-150x150.jpg 150w, https://store.local/wp-content/uploads/2021/11/beanie-with-logo-1-768x768.jpg 768w", "sizes": "(max-width: 800px) 100vw, 800px", "name": "beanie-with-logo-1.jpg", "alt": "" }, "reviewer": "reviewer-name", "review": "

This is a fantastic product.

\n", "rating": 5, "verified": true, "reviewer_avatar_urls": { "24": "https://secure.gravatar.com/avatar/12345?s=24&d=mm&r=g", "48": "https://secure.gravatar.com/avatar/12345?s=48&d=mm&r=g", "96": "https://secure.gravatar.com/avatar/12345?s=96&d=mm&r=g" } } ] ``` --- ## Product Tags API *Source: apis/store-api/resources-endpoints/product-tags.md* # Product Tags API ## List Product Tags ```http GET /products/tags ``` There are no parameters required for this endpoint. ```sh curl "https://example-store.com/wp-json/wc/store/v1/products/tags" ``` Example response: ```json [ { "id": 1, "name": "Test Tag", "slug": "test-tag", "description": "", "parent": 0, "count": 1 }, { "id": 2, "name": "Another Tag", "slug": "another-tag", "description": "", "parent": 0, "count": 1 } ] ``` --- ## Products API *Source: apis/store-api/resources-endpoints/products.md* # Products API The store products API provides public product data so it can be rendered on the client side. ## List Products ```http GET /products GET /products?search=product%20name GET /products?slug=slug-1,slug-2 GET /products?after=2017-03-22&date_column=date GET /products?before=2017-03-22&date_column=date GET /products?exclude=10,44,33 GET /products?include=10,44,33 GET /products?offset=10 GET /products?order=asc&orderby=price GET /products?parent=10 GET /products?parent_exclude=10 GET /products?type=simple GET /products?sku=sku-1,sku-2 GET /products?featured=true GET /products?category=22 GET /products?brand=adidas GET /products?_unstable_tax_my-taxonomy=my-taxonomy-term-id GET /products?tag=special-items GET /products?attributes[0][attribute]=pa_color&attributes[0][slug]=red GET /products?on_sale=true GET /products?min_price=5000 GET /products?max_price=10000 GET /products?stock_status=['outofstock'] GET /products?catalog_visibility=search GET /products?rating=4,5 GET /products?return_price_range=true GET /products?return_attribute_counts=pa_size,pa_color GET /products?return_rating_counts=true ``` | Attribute | Type | Required | Description | | :------------------------------------------ | :------ | :------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `search` | integer | no | Limit results to those matching a string. | | `slug` | string | no | Limit result set to products with specific slug(s). Use commas to separate. | | `after` | string | no | Limit response to resources created after a given ISO8601 compliant date. | | `before` | string | no | Limit response to resources created before a given ISO8601 compliant date. | | `date_column` | string | no | When limiting response using after/before, which date column to compare against. Allowed values: `date`, `date_gmt`, `modified`, `modified_gmt` | | `exclude` | array | no | Ensure result set excludes specific IDs. | | `include` | array | no | Limit result set to specific ids. | | `offset` | integer | no | Offset the result set by a specific number of items. | | `order` | string | no | Order sort attribute ascending or descending. Allowed values: `asc`, `desc` | | `orderby` | string | no | Sort collection by object attribute. Allowed values : `date`, `modified`, `id`, `include`, `title`, `slug`, `price`, `popularity`, `rating`, `menu_order`, `comment_count` | | `parent` | array | no | Limit result set to those of particular parent IDs. | | `parent_exclude` | array | no | Limit result set to all items except those of a particular parent ID. | | `type` | string | no | Limit result set to products assigned a specific type. | | `sku` | string | no | Limit result set to products with specific SKU(s). Use commas to separate. | | `featured` | boolean | no | Limit result set to featured products. | | `category` | string | no | Limit result set to products assigned to categories IDs or slugs, separated by commas. | | `category_operator` | string | no | Operator to compare product category terms. Allowed values: `in`, `not_in`, `and` | | `brand` | string | no | Limit result set to products assigned to brands IDs or slugs, separated by commas. | | `brand_operator` | string | no | Operator to compare product brand terms. Allowed values: `in`, `not_in`, `and` | | `_unstable_tax_[product-taxonomy]` | string | no | Limit result set to products assigned to the term ID of that custom product taxonomy. `[product-taxonomy]` should be the key of the custom product taxonomy registered. | | `_unstable_tax_[product-taxonomy]_operator` | string | no | Operator to compare custom product taxonomy terms. Allowed values: `in`, `not_in`, `and` | | `tag` | string | no | Limit result set to products assigned a specific tag ID. | | `tag_operator` | string | no | Operator to compare product tags. Allowed values: `in`, `not_in`, `and` | | `on_sale` | boolean | no | Limit result set to products on sale. | | `min_price` | string | no | Limit result set to products based on a minimum price, provided using the smallest unit of the currency. E.g. provide 10025 for 100.25 USD, which is a two-decimal currency, and 1025 for 1025 JPY, which is a zero-decimal currency. | | `max_price` | string | no | Limit result set to products based on a maximum price, provided using the smallest unit of the currency. E.g. provide 10025 for 100.25 USD, which is a two-decimal currency, and 1025 for 1025 JPY, which is a zero-decimal currency. | | `stock_status` | array | no | Limit result set to products with specified stock statuses. Expects an array of strings containing 'instock', 'outofstock' or 'onbackorder'. | | `attributes` | array | no | Limit result set to specific attribute terms. Expects an array of objects containing `attribute` (taxonomy), `term_id` or `slug`, and optional `operator` for comparison. | | `attribute_relation` | string | no | The logical relationship between attributes when filtering across multiple at once. | | `catalog_visibility` | string | no | Determines if hidden or visible catalog products are shown. Allowed values: `any`, `visible`, `catalog`, `search`, `hidden` | | `rating` | boolean | no | Limit result set to products with a certain average rating. | ```sh curl "https://example-store.com/wp-json/wc/store/v1/products" ``` **Example response:** ```json [ { "id": 34, "name": "WordPress Pennant", "slug": "wordpress-pennant", "variation": "", "permalink": "https://local.wordpress.test/product/wordpress-pennant/", "sku": "wp-pennant", "summary": "

This is an external product.

", "short_description": "

This is an external product.

", "description": "

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.

", "on_sale": false, "prices": { "currency_code": "GBP", "currency_symbol": "£", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "£", "currency_suffix": "", "price": "1105", "regular_price": "1105", "sale_price": "1105", "price_range": null }, "average_rating": "0", "review_count": 0, "images": [ { "id": 57, "src": "https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1.jpg", "thumbnail": "https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-324x324.jpg", "srcset": "https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1.jpg 800w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-324x324.jpg 324w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-100x100.jpg 100w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-416x416.jpg 416w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-300x300.jpg 300w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-150x150.jpg 150w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-768x768.jpg 768w", "sizes": "(max-width: 800px) 100vw, 800px", "name": "pennant-1.jpg", "alt": "" } ], "has_options": false, "is_purchasable": true, "is_in_stock": true, "low_stock_remaining": null, "add_to_cart": { "text": "Add to cart", "description": "Add “WordPress Pennant” to your cart" } } ] ``` ## Single Product by ID Get a single product by id. ```http GET /products/:id ``` | Attribute | Type | Required | Description | | :-------- | :------ | :------: | :--------------------------------- | | `id` | integer | Yes | The ID of the product to retrieve. | ```sh curl "https://example-store.com/wp-json/wc/store/v1/products/34" ``` **Example response:** ```json { "id": 34, "name": "WordPress Pennant", "slug": "wordpress-pennant", "variation": "", "permalink": "https://local.wordpress.test/product/wordpress-pennant/", "sku": "wp-pennant", "summary": "

This is an external product.

", "short_description": "

This is an external product.

", "description": "

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.

", "on_sale": false, "prices": { "currency_code": "GBP", "currency_symbol": "£", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "£", "currency_suffix": "", "price": "1105", "regular_price": "1105", "sale_price": "1105", "price_range": null }, "average_rating": "0", "review_count": 0, "images": [ { "id": 57, "src": "https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1.jpg", "thumbnail": "https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-324x324.jpg", "srcset": "https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1.jpg 800w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-324x324.jpg 324w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-100x100.jpg 100w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-416x416.jpg 416w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-300x300.jpg 300w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-150x150.jpg 150w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-768x768.jpg 768w", "sizes": "(max-width: 800px) 100vw, 800px", "name": "pennant-1.jpg", "alt": "" } ], "has_options": false, "is_purchasable": true, "is_in_stock": true, "low_stock_remaining": null, "add_to_cart": { "text": "Add to cart", "description": "Add “WordPress Pennant” to your cart" } } ``` ## Single Product by slug Get a single product by slug. ```http GET /products/:slug ``` | Attribute | Type | Required | Description | | :-------- | :----- | :------: | :----------------------------------- | | `slug` | string | Yes | The slug of the product to retrieve. | ```sh curl "https://example-store.com/wp-json/wc/store/v1/products/wordpress-pennant" ``` **Example response:** ```json { "id": 34, "name": "WordPress Pennant", "slug": "wordpress-pennant", "variation": "", "permalink": "https://local.wordpress.test/product/wordpress-pennant/", "sku": "wp-pennant", "summary": "

This is an external product.

", "short_description": "

This is an external product.

", "description": "

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.

", "on_sale": false, "prices": { "currency_code": "GBP", "currency_symbol": "£", "currency_minor_unit": 2, "currency_decimal_separator": ".", "currency_thousand_separator": ",", "currency_prefix": "£", "currency_suffix": "", "price": "1105", "regular_price": "1105", "sale_price": "1105", "price_range": null }, "average_rating": "0", "review_count": 0, "images": [ { "id": 57, "src": "https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1.jpg", "thumbnail": "https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-324x324.jpg", "srcset": "https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1.jpg 800w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-324x324.jpg 324w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-100x100.jpg 100w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-416x416.jpg 416w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-300x300.jpg 300w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-150x150.jpg 150w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-768x768.jpg 768w", "sizes": "(max-width: 800px) 100vw, 800px", "name": "pennant-1.jpg", "alt": "" } ], "has_options": false, "is_purchasable": true, "is_in_stock": true, "low_stock_remaining": null, "add_to_cart": { "text": "Add to cart", "description": "Add “WordPress Pennant” to your cart" } } ``` ## Product Variations By default, Store API excludes product variations. You can retrieve the variations for a product by using the `type=variation`. ```sh curl "https://example-store.com/wp-json/wc/store/v1/products?type=variation" ``` --- ## WooCommerce coding standards *Source: best-practices/coding-standards/coding-standards.md* # WooCommerce coding standards Adhering to WooCommerce coding standards is essential for maintaining high code quality, ensuring compatibility, and facilitating easier maintenance and updates. This document outlines the recommended coding practices for developers working within the WooCommerce ecosystem, including the use of hooks, function prefixing, translatable texts, and code structure. ## Position of hooks Position hooks below the function call to align with the common pattern in the WordPress and WooCommerce ecosystem. Example: ```php /** * Add custom message. */ function YOUR_PREFIX_custom_message() { echo 'This is a custom message'; } add_action( 'wp_footer', 'YOUR_PREFIX_custom_message' ); ``` ## Prefixing function calls Use a consistent prefix for all function calls to avoid conflicts. For the code snippets in this repo, use `YOUR_PREFIX`. Example: ```php /** * Add custom discount. */ function YOUR_PREFIX_custom_discount( $price, $product ) { return $price * 0.9; // 10% discount } add_filter( 'woocommerce_product_get_price', 'YOUR_PREFIX_custom_discount', 10, 2 ); ``` ## Translatable texts and text domains Ensure all plain texts are translatable and use a consistent text domain, adhering to internationalization best practices. For the code snippets in this repo, use the textdomain `YOUR-TEXTDOMAIN`. Example: ```php /** * Add welcome message. */ function YOUR_PREFIX_welcome_message() { echo __( 'Welcome to our website', 'YOUR-TEXTDOMAIN' ); } add_action( 'wp_footer', 'YOUR_PREFIX_welcome_message' ); ``` ## Use of function_exists() To prevent errors from potential function redeclaration, wrap all function calls with `function_exists()`. Example: ```php /** * Add thumbnail support. */ if ( ! function_exists( 'YOUR_PREFIX_theme_setup' ) ) { function YOUR_PREFIX_theme_setup() { add_theme_support( 'post-thumbnails' ); } } add_action( 'after_setup_theme', 'YOUR_PREFIX_theme_setup' ); ``` ## Code quality standards To ensure the highest standards of code quality, developers are encouraged to adhere to the following practices: ### WooCommerce sniffs and WordPress code standards - **Ensure no code style issues** when code is passed through WooCommerce Sniffs and WordPress Code Standards for PHP_CodeSniffer. ### Automated testing - **Unit tests**: Implement automated unit tests to validate code functionality in isolation. - **E2E tests**: Utilize automated end-to-end tests to verify the integrated operation of components within the application. ### Tracking and managing bugs - **Monitor and aim to minimize** the number of open bugs, ensuring a stable and reliable product. ### Code organization - **Organize code in self-contained classes** to avoid creating "god/super classes" that contain all plugin code. This practice promotes modularity and simplifies maintenance. By following these coding standards and practices, developers can create high-quality, maintainable, and secure WooCommerce extensions that contribute positively to the WordPress ecosystem. --- ## CSS/Sass naming conventions *Source: best-practices/coding-standards/css-sass-naming-conventions.md* # CSS/Sass naming conventions ## Introduction Our guidelines are based on those used in [Calypso](https://github.com/Automattic/wp-calypso), which itself follows the [BEM methodology](https://getbem.com/). Refer to the [Calypso CSS/Sass Coding Guidelines](https://wpcalypso.wordpress.com/devdocs/docs/coding-guidelines/css.md) for full details. Read more about [BEM key concepts](https://en.bem.info/methodology/key-concepts/). There are a few differences in WooCommerce which are outlined below. ## Prefixing As a WordPress plugin WooCommerce has to play nicely with WordPress core and other plugins/themes. To minimize conflict potential, all classes should be prefixed with `.woocommerce-`. ## Class names When naming classes, remember: - **Block** - Standalone entity that is meaningful on its own. Such as the name of a component. - **Element** - Parts of a block and have no standalone meaning. They are semantically tied to its block. - **Modifier** - Flags on blocks or elements. Use them to change appearance or behavior. ### Example ```css /* Block */ .woocommerce-loop {} /* Nested block */ .woocommerce-loop-product {} /* Modifier */ .woocommerce-loop-product--sale {} /* Element */ .woocommerce-loop-product__link {} /* Element */ .woocommerce-loop-product__button-add-to-cart {} /* Modifier */ .woocommerce-loop-product__button-add-to-cart--added {} ``` **Note:** `.woocommerce-loop-product` is not named as such because the block is nested within `.woocommerce-loop`. It's to be specific so that we can have separate classes for single products, cart products, etc. **Nested blocks do not need to inherit their parents full name.** ## TL;DR - Follow the [WordPress Coding standards for CSS](https://make.wordpress.org/core/handbook/best-practices/coding-standards/css/) unless it contradicts anything here. - Follow [Calypso guidelines for CSS](https://wpcalypso.wordpress.com/devdocs/docs/coding-guidelines/css.md). - Use BEM for [class names](https://en.bem.info/methodology/naming-convention/). - Prefix all class names. --- ## WooCommerce grammar, punctuation and capitalization guide *Source: best-practices/coding-standards/grammar-punctuation-capitalization.md* # WooCommerce grammar, punctuation and capitalization guide Following grammar, punctuation and style guidelines helps keep our presentation consistent. Users have a better experience if they know what to expect and where to find the information they need. ## Basics **Be democratic**. Some people read every word. Some scan and search or prefer video. Help everyone. **Be focused**. Lead with the most important information in sentences, paragraphs, and sections. **Be concise**. Use plain language and brief sentences. **Be consistent**. Follow our guidelines and style tips. **Be specific**. Communicate crystal clear. Trim the fat. ## Guidelines ### Abbreviations and acronyms Spell out the full version on first mention with abbreviation or acronym in parentheses. Use the short version on second and consecutive mentions. - First use: Payment Card Industry Data Security Standard (PCI-DSS) - Second use: PCI-DSS If the abbreviation or acronym is widely known, use it as is. For example: API, FAQ, HTML, PHP, SQL, SSL. ### Active voice With active voice, the subject in the sentence performs the action. With passive voice, the subject in the sentence has the action done unto it. - Active: Jon downloaded his extension files. - Passive: The extension files were downloaded by Jon. ### Capitalization Cases when we capitalize: - Blog post and documentation article titles: First word. - Documentation headings (h2): Use sentence case (not title case) for docs titles and subheadings. - Product names: Every word except prepositions and conjunctions. - Sentences: First word. - Unordered/Bulleted lists - First word of each entry. Cases when we use lower case: - "ecommerce" (not "eCommerce") - email address - `info@woocommerce.com` - website URL - `developer.woocommerce.com` ### Contractions Use with discretion. Contractions, such as I'm and there's, give writing an informal and conversational feel, but may be inappropriate if content is being translated. For example, sometimes the not in don't is ignored by online translators. ### Emoji Emoji can add subtle emotion and humor or bring visual attention to your content. Use rarely and intentionally. ### Numbers Spell out a number at the start of a sentence, and spell out numbers one through nine in all cases. Use numerals in all other cases. - Ten products will launch in June. Not: 10 products will launch in June. - Lance ran a marathon and won third place in his age group. - I bought five hammers and 21 types of nails for the building project. - There were 18 kinds of beer on tap at the pub. Use a comma for numbers with more than three digits: 41,500, 170,000, 1,000,000 or 1 million. #### Currency Use currency codes and not only the symbol/sign when specifying dollars. Whole amounts need not have a decimal and two places. - USD $20 - CAD $19.99 - AUD $39.50 When writing about other currencies, use the symbol/sign. - €995 - ¥5,000 - £18.99 #### Dates Spell out the day of the week and month, using the format: - Monday, December 12, 2016 #### Decimals Use decimal points when a number is difficult to convert to a fraction, such as 3.141 or 98.5 or 0.29. #### Fractions Spell out fractions: one-fourth #### Percent Spell out the word 'percent.' Don't use % symbol unless space is limited, e.g., for use on social media. #### Phone numbers Use hyphens without spaces between numbers, not parentheses or periods. Use a [country code](https://countrycode.org/) for all countries. - +1-555-867-5309 - +34-902-1899-00 #### Range and span Use a hyphen to indicate a range or span of numbers: 20-30 days. #### Temperature Use the degree symbol and the capital C abbreviation for Celsius and capital F abbreviation for Fahrenheit. - 27°C - 98°F #### Times Use numbers and am or pm with a space and without periods. - 7:00 am - 7:30 pm Use a hyphen between times to indicate a time period in am or pm. Use 'to' if the time period spans am and pm. - 7:00-9:00 am and 7:00 am to 10:30 pm Specify a time zone when writing about an event with potential attendees worldwide. Automattic uses Coordinated Universal Time (UTC). Abbreviate U.S. time zones: - Eastern time: EDT or EST - Central time: CDT or CST - Mountain time: MDT or MST - Pacific time: PDT or PST #### Years Abbreviate decades - 80s and 90s - 1900s and 1890s ### Punctuation #### Ampersands Ampersands need only be used when part of an official company/brand name. Should not be substituted for 'and.' - Ben & Jerry's - Andre, Timo, and Donny went to a football game at Camp Nou. #### Apostrophes An apostrophe makes a word possessive. If a word already ends in s and is singular, add an 's. If a word ends in s and is plural, add an apostrophe. - A teammate borrowed Sam's bike. - A teammate borrowed Chris's bike. - Employees hid the office managers' pens. These are possessives: FAQ's questions, HE's weekly rotation. These are plural: FAQs and HEs. #### Colons Use a colon to create a list. - Aaron ordered three kinds of donuts: glazed, chocolate, and pumpkin. #### Commas Use a serial comma, also known as an Oxford comma, when compiling a list. - Jinny likes sunflowers, daisies, and peonies. Use common sense for other cases. Read the sentence out loud, and use a comma where clarity or pause may be needed. #### Dashes and hyphens Use a hyphen - without spaces on either side to link words, or indicate a span or range. - first-time user - Monday-Friday Use an em dash - without spaces on either side to indicate an aside. Use a true em dash, not hyphens. - Multivariate testing-just one of our new Pro features-can help you grow your business. - Austin thought Brad was the donut thief, but he was wrong-it was Lain. #### Ellipses Ellipses ... can be used to indicate an indefinite ending to a sentence or to show words are omitted when used in brackets [...] Use rarely. #### Exclamation points Use an exclamation point rarely and use only one. Exclamation points follow the same placement convention explained in Periods. #### Periods Periods should be: - Inside quotation marks - Outside parentheses when the portion in parentheses is part of a larger sentence - Inside parentheses when the part in parentheses can stand on its own Examples - Jake said, "I had the best day ever." - She went to the supermarket (and to the nail salon). - My mom loves pizza and beer. (Beer needs to be cold and dark.) #### Question marks Question marks follow the same placement convention explained in Periods. #### Quotation marks Periods and commas go within quotation marks. Question marks within quotes follow logic - if the question mark is part of the quotation, it goes within. If you're asking a question that ends with a quote, it goes outside the quote. Use single quotation marks for quotes within quotes. - Who sings, "All These Things That I've Done"? - Brandon Flowers of The Killers said, "I was inspired and on a roll when I wrote, 'I got soul, but I'm not a soldier.'" #### Semicolons Semicolons can be used to join two related phrases. - Their debut solo album hit the Top 10 in 20 countries; it was #1 in the UK. ### People, places, and things #### Company names and products Use brand identity names and products as written on official websites. - Pull&Bear - UE Boom Refer to a company or product as 'it' (not 'they'). - WooCommerce is, and not WooCommerce are. #### File extensions A file extension type should be all uppercase without periods. Add a lowercase s to make plural. - HTML - JPEG - PDF A specific file should have a lowercase extension type: - dancingcat.gif - SalesReport2016.pdf - firethatcannon.mp3 #### Names and titles First mention of a person should include their first and last name. Second and consecutive mentions can use first name only. Capitalize job titles, the names of teams, and departments. - Happiness Engineers or HEs - Team Apollo - Legal #### Pronouns Use he/him/his and she/her/her as appropriate. Don't use "one" as a pronoun. Use they/them/their if gender is unknown or when referring to a group. #### Quotations Use present tense when quoting someone. - "I love that WooCommerce is free and flexible," says Brent Jamison. #### Schools The first time you mention a school, college, or university in a piece of writing, refer to it by its full official name. On all other mentions, use its more common abbreviation. - Georgia Institute of Technology, Georgia Tech - Georgia State University, GSU #### States, cities, and countries Spell out all city and state names. Don't abbreviate city names. On first mention, write out United States. For further mentions, use U.S. The same applies to other countries or federations with a common abbreviation, such as European Union (EU) and United Kingdom (UK). #### URLs and websites Capitalize the names of websites and web publications. Don't italicize. Avoid writing out URLs; omit `http://www` when it's necessary. ### Slang and jargon Write in plain English. Text should be universally understood, with potential for translation. Briefly define technical terms when needed. ### Text formatting Use italics to indicate the title of a book, movie, or album. - The Oren Klaff book Pitch Anything is on sale for USD $5.99. Avoid: - Underline formatting - A mix of italic, bold, caps, and underline Left-align text, never center or right-aligned. Leave one space between sentences, never two. --- ## Naming conventions *Source: best-practices/coding-standards/naming-conventions.md* # Naming conventions ## PHP WooCommerce core generally follows [WordPress PHP naming conventions](https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/#naming-conventions). There are some additional conventions that apply, depending on the location of the code. ### `/src` Classes defined inside `/src` follow the [PSR-4](https://www.php-fig.org/psr/psr-4/) standard. See the [README for `/src`](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/README.md) for more information. The following conventions apply to this directory: - No class name prefix is needed, as all classes in this location live within the `Automattic\WooCommerce` namespace. - Classes are named using `CamelCase` convention. - Functions are named using `snake_case` convention. - Class file names should match the class name. They do not need a `class-` prefix. - The namespace should match the directory structure. - Hooks are prefixed with `woocommerce_`. - Hooks are named using `snake_case` convention. For example, the class defined in `src/Util/StringUtil.php` should be named `StringUtil` and should be in the `Automattic\WooCommerce\Util` namespace. ### `/includes` The `/includes` directory contains legacy code that does not follow the PSR-4 standard. See the [README for `/includes`](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/includes/README.md) for more information. The following conventions apply to this directory: - Class names are prefixed with `WC_`. - Classes are named using `Upper_Snake_Case` convention. - Functions are prefixed with `wc_`. - Functions are named using `snake_case` convention. - Hooks are prefixed with `woocommerce_`. - Hooks are named using `snake_case` convention. Class name examples: - `WC_Cache_Helper` - `WC_Cart` Function name examples: - `wc_get_product()` - `wc_is_active_theme()` Hook name examples (actions or filters): - `woocommerce_after_checkout_validation` - `woocommerce_get_formatted_order_total` ## JS WooCommerce core follows [WordPress JS naming conventions](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/javascript/#naming-conventions). As with PHP, function, class, and hook names should be prefixed, but the convention for JS is slightly different. - Global class names are prefixed with `WC`. Class names exported from modules are not prefixed. - Classes are named using `UpperCamelCase` convention. - Global function names are prefixed with `wc`. Function names exported from modules are not prefixed. - Functions are named using `camelCase` convention. - Hooks names are prefixed with `woocommerce`. - Hooks are named using `camelCase` convention. Global class name example: - `WCOrdersTable` Global function name example: - `wcSettings()` Hook name example (actions or filters): - `woocommerceTracksEventProperties` ## CSS and SASS See [CSS/Sass Naming Conventions](./css-sass-naming-conventions.md). --- ## How to add a custom field to simple and variable products *Source: best-practices/data-management/adding-a-custom-field-to-variable-products.md* # How to add a custom field to simple and variable products In this tutorial you will learn how to create a custom field for a product and show it in your store. Together we will set up the skeleton plugin, and learn about WP naming conventions and WooCommerce hooks. In the end, you will have a functioning plugin for adding a custom field. The [full plugin code](https://github.com/EdithAllison/woo-product-custom-fields) was written based on WordPress 6.2 and WooCommerce 7.6.0 ## Prerequisites To do this tutorial you will need to have a WordPress install with the WooCommerce plugin activated, and you will need at least one [simple product set up](https://woocommerce.com/document/managing-products/), or you can [import the WooCommerce sample product range](https://woocommerce.com/document/importing-woocommerce-sample-data/). ## Setting up the plugin To get started, let's do the steps to [create a skeleton plugin](https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/create-woo-extension). First, navigate to your wp-content/plugins folder, then run: ```sh npx @wordpress/create-block -t @woocommerce/create-woo-extension woo-product-fields ``` Then we navigate to our new folder and run the install and build: ```sh cd woo-product-fields npm install # Install dependencies npm run build # Build the javascript ``` WordPress has its own class file naming convention which doesn't work with PSR-4 out of the box. To learn more about Naming Conventions see the [WP Handbook](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/php/#naming-conventions). We will use the standard format of "class-my-classname.php" format, so let's go to the composer.json file and change the autoload to: ```json "autoload": { "classmap": ["includes/", "includes/admin/"] }, ``` After saving run dump-autoload to generate the class map by running in the Terminal: ```sh composer dump-autoload -o ``` This generates a new vendor/composer/autoload_classmap.php file containing a list of all our classes in the /includes/ and /includes/admin/ folder. We will need to repeat this command when we add, delete or move class files. ## WooCommerce hooks Our aim is to create a new custom text field for WooCommerce products to save new stock information for display in the store. To do this, we need to modify the section of the Woo data in the admin area which holds the stock info. WooCommerce allows us to add our code to these sections through [hooks](https://developer.wordpress.org/plugins/hooks/), which are a standard WordPress method to extend code. In the "Inventory" section we have the following action hooks available to us: For our Woo extension, we'll be appending our field right at the end with `woocommerce_product_options_inventory_product_data`. ## Creating our class Let's get started with creating a new class which will hold the code for the field. Add a new file with the name `class-product-fields.php` to the `/includes/admin/` folder. Within the class, we add our namespace, an abort if anyone tries to call the file directly and a `__construct` method which calls the `hooks()` method: ```php hooks(); } private function hooks() {} } ``` Then in Terminal we run `composer dump-autoload -o` to regenerate the class map. Once that's done, we add the class to our `setup.php` `__construct()` function like so: ```php class Setup { public function __construct() { add_action( 'admin_enqueue_scripts', array( $this, 'register_scripts' ) ); new ProductFields(); } } ``` ## Adding the custom field With the class set up and being called, we can create a function to add the custom field. WooCommerce has its own `woocommerce_wp_text_input( $args )` function which we can use here. `$args` is an array which allows us to set the text input data, and we will be using the global $product_object to access stored metadata. ```php public function add_field() { global $product_object; ?>
'_new_stock_information', 'label' => __( 'New Stock', 'woo_product_field' ), 'description' => __( 'Information shown in store', 'woo_product_field' ), 'desc_tip' => true, 'value' => $product_object->get_meta( '_new_stock_information' ) ) ); ?>
update_meta_data( '_new_stock_information', sanitize_text_field( $_POST['_new_stock_information'] ) ); $product->save_meta_data(); } } ``` This function checks if our new field is in the POST array. If yes, we create the product object, update our metadata and save the metadata. The `update_meta_data` function will either update an existing meta field or add a new one. And as we're inserting into the database, we must [sanitize our field value](https://developer.wordpress.org/apis/security/sanitizing/). And to make it all work, we add the hooks: ```php private function hooks() { add_action( 'woocommerce_product_options_inventory_product_data', array( $this, 'add_field' ) ); add_action( 'woocommerce_process_product_meta', array( $this, 'save_field' ), 10, 2 ); } ``` Now if we refresh our product screen, we can see our new field. If we add data and save the product, then the new meta data is inserted into the database. At this point you have a working extension that saves a custom field for a product as product meta. Showing the field in the store If we want to display the new field in our store, then we can do this with the `get_meta()` method of the Woo product class: `$product->get_meta( '_new_stock_information' )` Let's get started by creating a new file /includes/class-product.php. You may have noticed that this is outside the `/admin/` folder as this code will run in the front. So when we set up the class, we also adjust the namespace accordingly: ```php hooks(); } private function hooks() { } } ``` Again we run `composer dump-autoload -o` to update our class map. If you took a look at the extension setup you may have noticed that `/admin/setup.php` is only called if we're within WP Admin. So to call our new class we'll add it directly in `/woo-product-field.php`: ```php public function __construct() { if ( is_admin() ) { new Setup(); } new WooProductField\Product(); } ``` For adding the field to the front we have several options. We could create a theme template, but if we are working with a WooCommerce-compatible theme and don't need to make any other changes then a quick way is to use hooks. If we look into `/woocommerce/includes/wc-template-hooks.php` we can see all the existing actions for `woocommerce_single_product_summary` which controls the section at the top of the product page: For our extension, let's add the new stock information after the excerpt by using 21 as the priority: ```php private function hooks() { add_action( 'woocommerce_single_product_summary', array( $this, 'add_stock_info' ), 21 ); } ``` In our function we output the stock information with the [appropriate escape function](https://developer.wordpress.org/apis/security/escaping/), in this case, I'm suggesting to use `esc_html()` to force plain text. ```php public function add_stock_info() { global $product; ?>

get_meta( '_new_stock_information' ) ); ?>

ID ); woocommerce_wp_text_input( array( 'id' => '_new_stock_information' . '[' . $loop . ']', 'label' => __( 'New Stock Information', 'woo_product_field' ), 'wrapper_class' => 'form-row form-row-full', 'value' => $variation_product->get_meta( '_new_stock_information' ) ) ); } ``` For saving we use: ```php public function save_variation_field( $variation_id, $i ) { if ( isset( $_POST['_new_stock_information'][$i] ) ) { $variation_product = wc_get_product( $variation_id ); $variation_product->update_meta_data( '_new_stock_information', sanitize_text_field( $_POST['_new_stock_information'][$i] ) ); $variation_product->save_meta_data(); } } ``` And we now have a new variation field that stores our new stock information. If you cannot see the new field, please make sure to enable "Manage Stock" for the variation by ticking the checkbox in the variation details. Displaying the variation in the front store works a bit differently for variable products as only some content on the page is updated when the customer makes a selection. This exceeds the scope of this tutorial, but if you are interested have a look at `/woocommerce/assets/js/frontend/add-to-cart-variation.js` to see how WooCommerce does it. ## How to find hooks? Everyone will have their own preferred way, but for me, the quickest way is to look in the WooCommerce plugin code. The code for each data section can be found in `/woocommerce/includes/admin/meta-boxes/views`. To view how the inventory section is handled check the `html-product-data-inventory.php` file, and for variations take a look at `html-variation-admin.php`. --- ## Developing using WooCommerce CRUD objects *Source: best-practices/data-management/crud-objects.md* # Developing using WooCommerce CRUD objects CRUD is an abbreviation of the four basic operations you can do to a database or resource - Create, Read, Update, Delete. [WooCommerce 3.0 introduced CRUD objects](https://woocommerce.wordpress.com/2016/10/27/the-new-crud-classes-in-woocommerce-2-7/) for working with WooCommerce data. **Whenever possible these objects should be used in your code instead of directly updating metadata or using WordPress post objects.** Each of these objects contains a schema for the data it controls (properties), a getter and setter for each property, and a save/delete method which talks to a data store. The data store handles the actual saving/reading from the database. The object itself does not need to be aware of where the data is stored. ## The benefits of CRUD * Structure - Each object has a pre-defined structure and keeps its own data valid. * Control - We control the flow of data, and any validation needed, so we know when changes occur. * Ease of development - As a developer, you don't need to know the internals of the data you're working with, just the names. * Abstraction - The data can be moved elsewhere, e.g. custom tables, without affecting existing code. * Unification - We can use the same code for updating things in admin as we do in the REST API and CLIs. Everything is unified. * Simplified code - Less procedural code to update objects which reduces likelihood of malfunction and adds more unit test coverage. ## CRUD object structure The [`WC_Data`](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/includes/abstracts/abstract-wc-data.php) class is the basic implementation for CRUD objects, and all CRUD objects extend it. The most important properties to note are `$data`, which is an array of props supported in each object, and `$id`, which is the object's ID. The [coupon object class](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/includes/class-wc-coupon.php) is a good example of extending `WC_Data` and adding CRUD functions to all properties. ### Data `$data` stores the property names, and default values: ```php /** * Data array, with defaults. * @since 3.0.0 * @var array */ protected $data = array( 'code' => '', 'amount' => 0, 'date_created' => '', 'date_modified' => '', 'discount_type' => 'fixed_cart', 'description' => '', 'date_expires' => '', 'usage_count' => 0, 'individual_use' => false, 'product_ids' => array(), 'excluded_product_ids' => array(), 'usage_limit' => 0, 'usage_limit_per_user' => 0, 'limit_usage_to_x_items' => 0, 'free_shipping' => false, 'product_categories' => array(), 'excluded_product_categories' => array(), 'exclude_sale_items' => false, 'minimum_amount' => '', 'maximum_amount' => '', 'email_restrictions' => array(), 'used_by' => array(), ); ``` ### Getters and setters Each one of the keys in this array (property) has a getter and setter, e.g. `set_used_by()` and `get_used_by()`. `$data` itself is private, so the getters and setters must be used to access the data. Example getter: ```php /** * Get records of all users who have used the current coupon. * @since 3.0.0 * @param string $context * @return array */ public function get_used_by( $context = 'view' ) { return $this->get_prop( 'used_by', $context ); } ``` Example setter: ```php /** * Set which users have used this coupon. * @since 3.0.0 * @param array $used_by * @throws WC_Data_Exception */ public function set_used_by( $used_by ) { $this->set_prop( 'used_by', array_filter( $used_by ) ); } ``` `set_prop` and `get_prop` are part of `WC_Data`. These apply various filters (based on context) and handle changes, so we can efficiently save only the props that have changed rather than all props. A note on `$context`: when getting data for use on the front end or display, `view` context is used. This applies filters to the data so extensions can change the values dynamically. `edit` context should be used when showing values to edit in the backend, and for saving to the database. Using `edit` context does not apply any filters to the data. ### The constructor The constructor of the CRUD objects facilitates the read from the database. The actual read is not done by the CRUD class, but by its data store. Example: ```php /** * Coupon constructor. Loads coupon data. * @param mixed $data Coupon data, object, ID or code. */ public function __construct( $data = '' ) { parent::__construct( $data ); if ( $data instanceof WC_Coupon ) { $this->set_id( absint( $data->get_id() ) ); } elseif ( is_numeric( $data ) && 'shop_coupon' === get_post_type( $data ) ) { $this->set_id( $data ); } elseif ( ! empty( $data ) ) { $this->set_id( wc_get_coupon_id_by_code( $data ) ); $this->set_code( $data ); } else { $this->set_object_read( true ); } $this->data_store = WC_Data_Store::load( 'coupon' ); if ( $this->get_id() > 0 ) { $this->data_store->read( $this ); } } ``` Note how it sets the ID based on the data passed to the object, then calls the data store to retrieve the data from the database. Once the data is read via the data store, or if no ID is set, `$this->set_object_read( true );` is set so the data store and CRUD object knows it's read. Once this is set, changes are tracked. ### Saving and deleting Save and delete methods are optional on CRUD child classes because the `WC_Data` parent class can handle it. When `save` is called, the data store is used to store data to the database. Delete removes the object from the database. `save` must be called for changes to persist, otherwise they will be discarded. The save method in `WC_Data` looks like this: ```php /** * Save should create or update based on object existence. * * @since 2.6.0 * @return int */ public function save() { if ( $this->data_store ) { // Trigger action before saving to the DB. Allows you to adjust object props before save. do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store ); if ( $this->get_id() ) { $this->data_store->update( $this ); } else { $this->data_store->create( $this ); } return $this->get_id(); } } ``` Update/create is used depending on whether the object has an ID yet. The ID will be set after creation. The delete method is like this: ```php /** * Delete an object, set the ID to 0, and return result. * * @since 2.6.0 * @param bool $force_delete * @return bool result */ public function delete( $force_delete = false ) { if ( $this->data_store ) { $this->data_store->delete( $this, array( 'force_delete' => $force_delete ) ); $this->set_id( 0 ); return true; } return false; } ``` ## CRUD usage examples ### Creating a new simple product ```php $product = new WC_Product_Simple(); $product->set_name( 'My Product' ); $product->set_slug( 'myproduct' ); $product->set_description( 'A new simple product' ); $product->set_regular_price( '9.50' ); $product->save(); $product_id = $product->get_id(); ``` ### Updating an existing coupon ```php $coupon = new WC_Coupon( $coupon_id ); $coupon->set_discount_type( 'percent' ); $coupon->set_amount( 25.00 ); $coupon->save(); ``` ### Retrieving a customer ```php $customer = new WC_Customer( $user_id ); $email = $customer->get_email(); $address = $customer->get_billing_address(); $name = $customer->get_first_name() . ' ' . $customer->get_last_name(); ``` --- ## Data storage primer *Source: best-practices/data-management/data-storage.md* # Data storage primer When developing for WordPress and WooCommerce, it's important to consider the nature and permanence of your data. This will help you decide the best way to store it. Here's a quick primer: ## Transients If the data may not always be present (i.e., it expires), use a [transient](https://developer.wordpress.org/apis/handbook/transients/). Transients are a simple and standardized way of storing cached data in the database temporarily by giving it a custom name and a timeframe after which it will expire and be deleted. ## WP Cache If the data is persistent but not always present, consider using the [WP Cache](https://developer.wordpress.org/reference/classes/wp_object_cache/). The WP Cache functions allow you to cache data that is computationally expensive to regenerate, such as complex query results. ## wp_options Table If the data is persistent and always present, consider the [wp_options table](https://developer.wordpress.org/apis/handbook/options/). The Options API is a simple and standardized way of storing data in the wp_options table in the WordPress database. ## Post Types If the data type is an entity with n units, consider a [post type](https://developer.wordpress.org/post_type/). Post types are "types" of content that are stored in the same way, but are easy to distinguish in the code and UI. ## Taxonomies If the data is a means of sorting/categorizing an entity, consider a [taxonomy](https://developer.wordpress.org/taxonomy/). Taxonomies are a way of grouping things together. ## Logging Logs should be written to a file using the [WC_Logger](https://woocommerce.com/wc-apidocs/class-WC_Logger.html) class. This is a simple and standardized way of recording events and errors for debugging purposes. Remember, the best method of data storage depends on the nature of the data and how it will be used in your application. --- ## How to manage WooCommerce Data Stores *Source: best-practices/data-management/data-stores.md* # How to manage WooCommerce Data Stores ## Introduction Data store classes act as a bridge between WooCommerce's data CRUD classes (`WC_Product`, `WC_Order`, `WC_Customer`, etc) and the database layer. With the database logic separate from data, WooCommerce becomes more flexible. The data stores shipped with WooCommerce core (powered by WordPress' custom posts system and some custom tables) can be swapped out for a different database structure, type, or even be powered by an external API. This guide will walk through the structure of a data store class, how to create a new data store, how to replace a core data store, and how to call a data store from your own code. The examples in this guide will look at the [`WC_Coupon`](https://github.com/woocommerce/woocommerce/blob/dcecf0f22890f3cd92fbea13a98c11b2537df2a8/includes/class-wc-coupon.php#L19) CRUD data class and [`WC_Coupon_Data_Store_CPT`](https://github.com/woocommerce/woocommerce/blob/dcecf0f22890f3cd92fbea13a98c11b2537df2a8/includes/data-stores/class-wc-coupon-data-store-cpt.php), an implementation of a coupon data store using WordPress custom post types. This is how coupons are currently stored in WooCommerce. The important thing to know about `WC_Coupon` or any other CRUD data class when working with data stores is which props (properties) they contain. This is defined in the [`data`](https://github.com/woocommerce/woocommerce/blob/dcecf0f22890f3cd92fbea13a98c11b2537df2a8/includes/class-wc-coupon.php#L26) array of each class. ## Structure Every data store for a CRUD object should implement the `WC_Object_Data_Store_Interface` interface. `WC_Object_Data_Store_Interface` includes the following methods: * `create` * `read` * `update` * `delete` * `read_meta` * `delete_meta` * `add_meta` * `update_meta` The `create`, `read`, `update`, and `delete` methods should handle the CRUD logic for your props: * `create` should create a new entry in the database. Example: Create a coupon. * `read` should query a single entry from the database and set properties based on the response. Example: Read a coupon. * `update` should make changes to an existing entry. Example: Update or edit a coupon. * `delete` should remove an entry from the database. Example: Delete a coupon. All data stores must implement handling for these methods. In addition to handling your props, other custom data can be passed. This is considered `meta`. For example, coupons can have custom data provided by plugins. The `read_meta`, `delete_meta`, `add_meta`, and `update_meta` methods should be defined so meta can be read and managed from the correct source. In the case of our WooCommerce core classes, we define them in `WC_Data_Store_WP` and then use the same code for all of our data stores. They all use the WordPress meta system. You can redefine these if meta should come from a different source. Your data store can also implement other methods to replace direct queries. For example, the coupons data store has a public `get_usage_by_user_id` method. Data stores should always define and implement an interface for the methods they expect, so other developers know what methods they need to write. Put another way, in addition to the `WC_Object_Data_Store_Interface` interface, `WC_Coupon_Data_Store_CPT` also implements `WC_Coupon_Data_Store_Interface`. ## Replacing a data store Let's look at how we would replace the `WC_Coupon_Data_Store_CPT` class with a `WC_Coupon_Data_Store_Custom_Table` class. Our examples will just provide stub functions, instead of a full working solution. Imagine that we would like to store coupons in a table named `wc_coupons` with the following columns: ```text id, code, amount, date_created, date_modified, discount_type, description, date_expires, usage_count,individual_use, product_ids, excluded_product_ids, usage_limit, usage_limit_per_user, limit_usage_to_x_items, free_shipping, product_categories, excluded_product_categories, exclude_sale_items, minimum_amount, maximum_amount, email_restrictions, used_by ``` These column names match 1 to 1 with prop names. First we would need to create a new data store class to contain our logic: ```php /** * WC Coupon Data Store: Custom Table. */ class WC_Coupon_Data_Store_Custom_Table extends WC_Data_Store_WP implements WC_Coupon_Data_Store_Interface, WC_Object_Data_Store_Interface { } ``` Note that we implement the main `WC_Object_Data_Store_Interface` interface as well as the ` WC_Coupon_Data_Store_Interface` interface. Together, these represent all the methods we need to provide logic for. We would then define the CRUD handling for these properties: ```php /** * Method to create a new coupon in the database. * * @param WC_Coupon */ public function create( &$coupon ) { $coupon->set_date_created( current_time( 'timestamp' ) ); /** * This is where code for inserting a new coupon would go. * A query would be built using getters: $coupon->get_code(), $coupon->get_description(), etc. * After the INSERT operation, we want to pass the new ID to the coupon object. */ $coupon->set_id( $coupon_id ); // After creating or updating an entry, we need to also cause our 'meta' to save. $coupon->save_meta_data(); // Apply changes let's the object know that the current object reflects the database and no "changes" exist between the two. $coupon->apply_changes(); // It is helpful to provide the same hooks when an action is completed, so that plugins can interact with your data store. do_action( 'woocommerce_new_coupon', $coupon_id ); } /** * Method to read a coupon. * * @param WC_Coupon */ public function read( &$coupon ) { $coupon->set_defaults(); // Read should do a check to see if this is a valid coupon // and otherwise throw an 'Invalid coupon.' exception. // For valid coupons, set $data to contain our database result. // All props should be set using set_props with output from the database. This "hydates" the CRUD data object. $coupon_id = $coupon->get_id(); $coupon->set_props( array( 'code' => $data->code, 'description' => $data->description, // .. ) ); // We also need to read our meta data into the object. $coupon->read_meta_data(); // This flag reports if an object has been hydrated or not. If this ends up false, the database hasn't correctly set the object. $coupon->set_object_read( true ); do_action( 'woocommerce_coupon_loaded', $coupon ); } /** * Updates a coupon in the database. * * @param WC_Coupon */ public function update( &$coupon ) { // Update coupon query, using the getters. $coupon->save_meta_data(); $coupon->apply_changes(); do_action( 'woocommerce_update_coupon', $coupon->get_id() ); } /** * Deletes a coupon from the database. * * @param WC_Coupon * @param array $args Array of args to pass to the delete method. */ public function delete( &$coupon, $args = array() ) { // A lot of objects in WordPress and WooCommerce support // the concept of trashing. This usually is a flag to move the object // to a "recycling bin". Since coupons support trashing, your layer should too. // If an actual delete occurs, set the coupon ID to 0. $args = wp_parse_args( $args, array( 'force_delete' => false, ) ); $id = $coupon->get_id(); if ( $args['force_delete'] ) { // Delete Query $coupon->set_id( 0 ); do_action( 'woocommerce_delete_coupon', $id ); } else { // Trash Query do_action( 'woocommerce_trash_coupon', $id ); } } ``` We are extending `WC_Data_Store_WP` so our classes will continue to use WordPress' meta system. As mentioned in the structure section, we are responsible for implementing the methods defined by `WC_Coupon_Data_Store_Interface`. Each interface describes the methods and parameters it accepts, and what your function should do. A coupons replacement would look like the following: ```php /** * Increase usage count for current coupon. * * @param WC_Coupon * @param string $used_by Either user ID or billing email */ public function increase_usage_count( &$coupon, $used_by = '' ) { } /** * Decrease usage count for current coupon. * * @param WC_Coupon * @param string $used_by Either user ID or billing email */ public function decrease_usage_count( &$coupon, $used_by = '' ) { } /** * Get the number of uses for a coupon by user ID. * * @param WC_Coupon * @param id $user_id * @return int */ public function get_usage_by_user_id( &$coupon, $user_id ) { } /** * Return a coupon code for a specific ID. * @param int $id * @return string Coupon Code */ public function get_code_by_id( $id ) { } /** * Return an array of IDs for for a specific coupon code. * Can return multiple to check for existence. * @param string $code * @return array Array of IDs. */ public function get_ids_by_code( $code ) { } ``` Once all the data store methods are defined and logic written, we need to tell WooCommerce to load our new class instead of the built-in class. This is done using the `woocommerce_data_stores` filter. An array of data store slugs is mapped to default WooCommerce classes. Example: ```php 'coupon' => 'WC_Coupon_Data_Store_CPT', 'customer' => 'WC_Customer_Data_Store', 'customer-download' => 'WC_Customer_Download_Data_Store', 'customer-session' => 'WC_Customer_Data_Store_Session', 'order' => 'WC_Order_Data_Store_CPT', 'order-refund' => 'WC_Order_Refund_Data_Store_CPT', 'order-item' => 'WC_Order_Item_Data_Store', 'order-item-coupon' => 'WC_Order_Item_Coupon_Data_Store', 'order-item-fee' => 'WC_Order_Item_Fee_Data_Store', 'order-item-product' => 'WC_Order_Item_Product_Data_Store', 'order-item-shipping' => 'WC_Order_Item_Shipping_Data_Store', 'order-item-tax' => 'WC_Order_Item_Tax_Data_Store', 'payment-token' => 'WC_Payment_Token_Data_Store', 'product' => 'WC_Product_Data_Store_CPT', 'product-grouped' => 'WC_Product_Grouped_Data_Store_CPT', 'product-variable' => 'WC_Product_Variable_Data_Store_CPT', 'product-variation' => 'WC_Product_Variation_Data_Store_CPT', 'shipping-zone' => 'WC_Shipping_Zone_Data_Store', ``` We specifically want to target the coupon data store, so we would do something like this: ```php function myplugin_set_wc_coupon_data_store( $stores ) { $stores['coupon'] = 'WC_Coupon_Data_Store_Custom_Table'; return $stores; } add_filter( 'woocommerce_data_stores', 'myplugin_set_wc_coupon_data_store' ); ``` Our class would then be loaded by WooCommerce core, instead of `WC_Coupon_Data_Store_CPT`. ## Creating a new data store ### Defining a new product type Does your extension create a new product type? Each product type has a data store in addition to a parent product data store. The parent store handles shared properties like name or description and the child handles more specific data. For example, the external product data store handles "button text" and "external URL". The variable data store handles the relationship between parent products and their variations. Check out [this walkthrough](https://developer.woocommerce.com/2017/02/06/wc-2-7-extension-compatibility-examples-3-bookings/) for more information on this process. ### Data store for custom data If your extension introduces a new database table, new custom post type, or some new form of data not related to products, orders, etc, then you should implement your own data store. Your data store should still implement `WC_Object_Data_Store_Interface` and provide the normal CRUD functions. Your data store should be the main point of entry for interacting with your data, so any other queries or operations should also have methods. The [shipping zone data store](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/includes/data-stores/class-wc-shipping-zone-data-store.php) serves as a good example for a "simple" data store using a custom table. The coupons code is a good example for a data store using a custom post type. All you need to do to register your data store is add it to the `woocommerce_data_stores` filter: ```php function myplugin_set_my_custom_data_store( $stores ) { $stores['mycustomdata'] = 'WC_My_Custom_Data_Store'; return $stores; } add_filter( 'woocommerce_data_stores', 'myplugin_set_my_custom_data_store' ); ``` You can then load your data store like any other WooCommerce data store. ## Calling a data store Calling a data store is as simple as using the static `WC_Data_Store::load()` method: ```php // Load the shipping zone data store. $data_store = WC_Data_Store::load( 'shipping-zone' ); // Get the number of shipping methods for zone ID 4. $num_of_methods = $data_store->get_method_count( 4 ); ``` You can also chain methods: ```php // Get the number of shipping methods for zone ID 4. $num_of_methods = WC_Data_Store::load( 'shipping-zone' )->get_method_count( 4 ); ``` The `::load()` method works for any data store registered to `woocommerce_data_stores`, so you could load your custom data store: ```php $data_store = WC_Data_Store::load( 'mycustomdata' ); ``` ## Data store limitations and WP Admin Currently, several WooCommerce screens still rely on WordPress to list objects. Examples of this include coupons and products. If you replace data via a data store, some parts of the existing UI may fail. An example of this may be lists of coupons when using the `type` filter. This filter uses meta data, and is in turn passed to WordPress which runs a query using the `WP_Query` class. This cannot handle data outside of the regular meta tables (Ref #19937). To get around this, usage of `WP_Query` would need to be deprecated and replaced with custom query classes and functions. --- ## Logging in WooCommerce *Source: best-practices/data-management/logging.md* # Logging in WooCommerce WooCommerce has its own robust system for logging, which can be used for debugging during development, catching errors on production, or even sending notifications when specific events occur. By default, WooCommerce uses this logger to record errors, warnings, and other notices that may be useful for troubleshooting problems with a store. Many extensions for WooCommerce also make use of the logger for similar purposes. ## Viewing logs You can view the entries created by the logger by going to **WooCommerce > Status > Logs**. The log viewing interface depends on which log storage method is configured (see the "Configuring the logger" section below). ### File system With the default file system storage method, the first thing you will see is the list of existing log files: ![The default log viewing interface, showing a list of log files](/img/doc_images/file-browse.png) The name of a log file is based on the source of the entries it contains (meaning the extension or the part of the WooCommerce codebase), as well as the date the entries were generated. In this file browsing view, you can sort the files in different ways as well as filtering them to show only those from a specific source. Clicking on a file will take you to a single file view, where you can see the actual log entries: ![The contents of a log file](/img/doc_images/file-view-new.png) Click on a line number in the file to highlight it. This can also be used to link to a specific line in a file from elsewhere. From the file browser view, you can sort and filter a list of log files, and then search for a string within the contents of those files: ![A list of search results](/img/doc_images/search-results.png) Clicking on a search result line number will take you to that line in the single file view. ### Database With the database storage method, you will see a list of log entries, starting with the most recent: ![The log viewing interface when log entries are stored in the database](/img/doc_images/database-logs.png) These entries can be sorted by timestamp, level, and source, as well as filtered to only show a particular source or a minimum level. You can also search for a string within the log entry message fields. ## Configuring the logger From the Logs screen at **WooCommerce > Status > Logs**, click the "Settings" link to make configuration changes: ![The Logs settings screen](/img/doc_images/settings.png) ### Logger Uncheck the box here to turn off all logging. This is not recommended in most circumstances, as logging can provide valuable information about what is happening on your site! ### Log storage Out-of-the-box, WooCommerce has two different log storage methods available: * **File system** - Log entries are recorded to files. Files are differentiated by the `source` value for the log entry (see the "Adding logs" section below), and by the current date. The files are stored in the `wc-logs` subdirectory of the site's `uploads` directory. A custom directory can be defined using the `woocommerce_log_directory` filter hook. Log files can be up to 5 MB in size, after which the log file will rotate. * **Database** - Log entries are recorded to the database, in the `{$wpdb->prefix}woocommerce_log` table. If you change this setting, and you already have some log entries, those entries will not be migrated to the other storage method, but neither will they be deleted. ### Retention period The logger will periodically go through and delete logs that are older than this time period, as a space-saving measure. If log entries are stored in the file system, the entire log file that is beyond the retention period will be deleted, while with database storage, individual log entries will be deleted. ### Level threshold Each log entry has a severity level (see the "Adding logs" section below). This sets a minimum severity level, and any log entries that are generated that are not at or above the minimum level will not be recorded. Use this setting with caution! If this setting is set to "None", it means that all log entries will be recorded, regardless of severity level. ## Adding logs Logs are added via methods in the `WC_Logger` class. The class instance is accessed by using the `wc_get_logger()` function. The basic method for adding a log entry is [`WC_Logger::log( $level, $message, $context )`](https://woocommerce.github.io/code-reference/classes/WC-Logger.html#method_log). There are also shortcut methods for each log severity level, for example `WC_Logger::warning( $message, $context )`. It is preferable to use the shortcut methods rather than the generic `log` method. ### Level Logs have eight different severity levels: * `emergency` * `alert` * `critical` * `error` * `warning` * `notice` * `info` * `debug` Aside from giving a site owner context as to how important a log entry is, these levels also allow logs to be filtered. If you only want log entries to be recorded for `error` severity and higher, you can set the threshold on the Logs Settings screen (see the "Configuring the logger" above). Note that this threshold will apply to all logs, regardless of which log handler is in use. The `WC_Log_Handler_Email` class, for example, has its own threshold setting, but it is secondary to the global threshold. ### Message The message is the main content of a log entry. Make sure it's understandable by anyone who might be viewing the logs! ### Context The context parameter is intended to be used to store additional structured data related to the log entry. For example, in a log entry about an order, you may want to include contents of the related order object. When the logger is generating an entry, the data in the context parameter is converted to JSON before it is stored. So, if you want to add multiple pieces of context data, each should be added as a separate key within the context array. There are two specific keys that can be added to the context array that will cause special behavior: #### `source` It is recommended that every log entry include a `source` value in the context parameter. `source` is intended to provide context about where in the application or codebase the log was generated, and can be used to filter log entries. If a source is not specified, the logger will generate a source value based on the plugin or file where the log entry was generated. #### `backtrace` Setting the `backtrace` key in your context parameter to `true` will cause the logger to generate a backtrace (i.e. stack trace) in array format, which will be included in the context in place of the `true` value. This is useful particularly for error-related logs, so you can see what code was executed that led to the log entry being generated. ![A backtrace displayed in the log file viewer](/img/doc_images/backtrace.png) ### Full example ```php wc_get_logger()->info( 'It is time for lunch.', array( 'source' => 'your_stomach', 'backtrace' => true, 'previous_meal' => $elapsed_time_since_breakfast, 'lunch_options' => array( 'fridge leftovers', 'bahn mi', 'tacos', 'pupusas' ), ) ); ``` ## Logging best practices ### When to use logging * To help troubleshoot an unexpected problem: * An unexpected value or error is received from an API. * Data in the system is incorrect or corrupted. * The application is in an unexpected or broken state. * To keep an audit log of a process: * Transaction details not stored elsewhere, like an order note (but maybe they should be?) * Results of data import or export. * An important setting is changed. * An automated process changes or deletes data. ### When _not_ to use logging * To let a developer know that they're using a method or API incorrectly. This can lead to a large volume of useless log entries, especially if it will get triggered on every page request. Better to give them immediate feedback in the form of an error or exception (e.g. `wc_doing_it_wrong()`). ### Best practices * Rather than using the `WC_Logger`'s `log()` method directly, it's better to use one of the wrapper methods that's specific to the log level. E.g. `info()` or `error()`. * Write a message that is a complete, coherent sentence. This will make it more useful for people who aren't familiar with the codebase. * Log messages should not be translatable. Keeping the message in English makes it easier to search for solutions based on the message contents, and also makes it easier for anyone troubleshooting to understand what's happening, since they may not speak the same language as the site owner. * Ideally, each log entry message should be a single line (i.e. no line breaks within the message string). Additional lines or extra data should be put in the context array. * Avoid outputting structured data in the message string. Put it in a key in the context array instead. The logger will handle converting it to JSON and making it legible in the log viewer. * If you need to include a stack trace, let the logger generate it for you. * Decide on a source for the component or system you are working on and use it for every log call you make. This will make it easier to find all the log entries that are related to the component, and filter them out from other, unrelated log entries. * Consider adding one log entry after a looped process with the aggregated results of the loop, rather than adding a separate entry during each item within a loop. ## Customizing the logger ### The logger class The `WC_Logger` class can be substituted for another class via the `woocommerce_logging_class` filter hook. The alternative class must implement [`WC_Logger_Interface`](https://woocommerce.github.io/code-reference/classes/WC-Logger-Interface.html), or it will not be used. Generally it is better to create a custom log handler (see below) rather than overriding the logger class itself. ### Log handlers In WooCommerce, a log handler is a PHP class that takes the raw log data and transforms it into a log entry that can be stored or dispatched. WooCommerce ships with four different log handler classes: * `Automattic\\WooCommerce\\Internal\\Admin\\Logging\\LogHandlerFileV2`: This is the default handler, representing the "file system" log storage method. It records log entries to files. * `WC_Log_Handler_File`: This is the old default handler that also records log entries to files. It may be deprecated in the future, and it is not recommended to use this class or extend it. * `WC_Log_Handler_DB`: This handler represents the "database" log storage method. It records log entries to the database. * `WC_Log_Handler_Email`: This handler does not store log entries, but instead sends them as email messages. Emails are sent to the site admin email address. This handler has [some limitations](https://github.com/woocommerce/woocommerce/blob/fe81a4cf27601473ad5c394a4f0124c785aaa4e6/plugins/woocommerce/includes/log-handlers/class-wc-log-handler-email.php#L15-L27). #### Changing or adding handlers To switch from the file handler to the database handler, you can simply update the option on the Logs Settings screen. However, in some cases, you may want to have more than one log handler, and/or you might want to modify the settings of a handler. For example, you may want to have most logs saved to files, but log entries that are classified as emergency or critical errors also sent to an email address. For this, you can use the `woocommerce_register_log_handlers` filter hook to create an array of log handler class instances that you want to use. Some handler class constructors have optional parameters that you can use when instantiating the class to change their default behavior. Here's an example of adding the email handler: ```php function my_wc_log_handlers( $handlers ) { $recipients = array( 'wayne@example.com', 'garth@example.com' ); // Send logs to multiple recipients. $threshold = 'critical'; // Only send emails for logs of this level and higher. $handlers[] = new WC_Log_Handler_Email( $recipients, $threshold ); return $handlers; } add_filter( 'woocommerce_register_log_handlers', 'my_wc_log_handlers' ); ``` #### Creating a custom handler You may want to create your own log handler class in order to send logs somewhere else, such as a Slack channel or perhaps an InfluxDB instance. Your class must extend the [`WC_Log_Handler`](https://woocommerce.github.io/code-reference/classes/WC-Log-Handler.html) abstract class and implement the [`WC_Log_Handler_Interface`](https://woocommerce.github.io/code-reference/classes/WC-Log-Handler-Interface.html) interface. The [`WC_Log_Handler_Email`](https://github.com/woocommerce/woocommerce/blob/6688c60fe47ad42d49deedab8be971288e4786c1/plugins/woocommerce/includes/log-handlers/class-wc-log-handler-email.php) handler class provides a good example of how to set this up. ### Log file storage location When using the "file system" log handler, by default the log files are stored in the `wc-logs` subdirectory of the WordPress `uploads` directory, which means they might be publicly accessible. WooCommerce adds an `.htaccess` file to prevent access to `wc-logs`, but not all web servers recognize that file. If you have the option, you may want to consider storing your log files in a directory outside of the web root. Make sure the directory has the same user/group permissions as the `uploads` directory so that WordPress can access it. Then use the `woocommerce_log_directory` filter hook to set the path to your custom directory. ### Turning off noisy logs If there is a particular log that is recurring frequently and clogging up your log files, you should probably figure out why it keeps getting triggered and resolve the issue. However, if that's not possible, you can add a callback to the `woocommerce_logger_log_message` filter hook to ignore that particular log while still allowing other logs to get through: ```php function my_ignored_logs( $message, $level, $context, $handler ) { if ( false !== strpos( $message, 'Look, a squirrel!' ) ) { return null; } return $message; } add_filter( 'woocommerce_logger_log_message', 'my_ignored_logs', 10, 4 ); ``` ### The Logs UI If you create a custom log handler and you want to build a separate interface for it on the Logs screen, there are a couple of filter hooks that you can work with: * `woocommerce_logger_handler_options`: This filter hook allows you to add your custom log handler to the list of "Log storage" options on the Settings screen. Your handler must be selected for this setting, making it the "default" log handler, before you can render an alternative interface for it. * `wc_logs_render_page`: This is the action to hook into for rendering your own Logs interface. It only fires if the default log handler is set to something that is not one of the built-in handlers. * `wc_logs_load_tab`: This action fires when the Logs tab first starts loading, before any content is output. It's useful for handling form submissions. --- ## Updating countries and subdivisions *Source: best-practices/localization-translation/countries-and-subdivisions.md* # Updating countries and subdivisions WooCommerce comes complete with a comprehensive list of countries and subdivisions (such as provinces or states) that are used in various parts of the user interface. Of course, even countries and their subdivisions periodically change. In these cases, you can certainly file a [bug report](https://github.com/woocommerce/woocommerce/issues/new?template=1-bug-report.yml) or [submit a pull request](/docs/contribution/contributing). However, it is important to understand that our policy is only to accept changes if they align with the current version of the [CLDR project](https://cldr.unicode.org/). Therefore, it is generally best to review that and, if necessary, propose a change there first before asking that it be adopted by WooCommerce. This approach may not be suitable in all cases, because it can take time for CLDR to accept updates. In such cases, you can still modify the lists of countries and subdivisions by using custom snippets like the following: - [Snippet to add a country](/docs/code-snippets/add-a-country) - [Snippet to add or modify states](/docs/code-snippets/add-or-modify-states) --- ## How to translate WooCommerce *Source: best-practices/localization-translation/translating-woocommerce.md* # How to translate WooCommerce WooCommerce is already translated into several languages and is translation-ready right out of the box. All that's needed is a translation file for your language. There are several methods to create a translation, most of which are outlined in the WordPress Codex. In most cases you can contribute to the project on [https://translate.wordpress.org/projects/wp-plugins/woocommerce/](https://translate.wordpress.org/projects/wp-plugins/woocommerce/). To create custom translations you can consider using [Poedit](https://poedit.net/). ## Set up WordPress in your language To set your WordPress site's language: 1. Go to `WP Admin > Settings > General` and adjust the `Site Language`. 2. Go to `WP Admin > Dashboard > Updates` and click the `Update Translations` button. Once this has been done, the shop displays in your locale if the language file exists. Otherwise, you need to create the language files (process explained below). ## Contributing your localization to core We encourage contributions to our translations. If you want to add translated strings or start a new translation, simply register at WordPress.org and submit your translations to [https://translate.wordpress.org/projects/wp-plugins/woocommerce/](https://translate.wordpress.org/projects/wp-plugins/woocommerce/) for approval. ## Translating WooCommerce into your language Both stable and development versions of WooCommerce are available for translation. When you install or update WooCommerce, WordPress will automatically fetch a 100% complete translation for your language. If such a translation isn't available, you can either download it manually or contribute to complete the translation, benefiting all users. If you're new to translating, check out the [translators handbook](https://make.wordpress.org/polyglots/handbook/tools/glotpress-translate-wordpress-org/) to get started. ### Downloading translations from translate.wordpress.org manually 1. Go to [translate.wordpress.org](https://translate.wordpress.org/projects/wp-plugins/woocommerce) and look for your language in the list. 2. Click the title to be taken to the section for that language. ![screenshot of WooCommerce translation page on wordpress.org](https://developer.woocommerce.com/wp-content/uploads/2023/12/2016-02-17-at-09.57.png) 3. Click the heading under `Set/Sub Project` to view and download a Stable version. ![list of versions available for selected language](https://developer.woocommerce.com/wp-content/uploads/2023/12/2016-02-17-at-09.59.png) 4. Scroll to the bottom for export options. Export a `.mo` file for use on your site. 5. Rename this file to `woocommerce-YOURLANG.mo` (e.g., Great Britain English should be `en_GB`). The corresponding language code can be found by going to [https://translate.wordpress.org/projects/wp-plugins/woocommerce/](https://translate.wordpress.org/projects/wp-plugins/woocommerce/) and opening the desired language. The language code is visible in the upper-right corner. ![screenshot of plugin card with associated language code](https://developer.woocommerce.com/wp-content/uploads/2023/12/Screenshot-2023-10-17-at-09.44.53.png) 6. Upload to your site under `wp-content/languages/woocommerce/`. Once uploaded, this translation file may be used. ## Creating custom translations WooCommerce includes a language file (`.pot` file) that contains all of the English text. You can find this language file inside the plugin folder in `woocommerce/i18n/languages/`. ## Creating custom translations with PoEdit WooCommerce comes with a `.pot` file that can be imported into PoEdit to translate. To get started: 1. Open PoEdit and select `Create new translation from POT template`. 2. Choose `woocommerce.pot` and PoEdit will show the catalog properties window. ![screenshot](https://developer.woocommerce.com/wp-content/uploads/2023/12/Screen-Shot-2013-05-09-at-10.16.46.png) 3. Enter your name and details, so other translators know who you are, and click `OK`. 4. Save your `.po` file. Name it based on what you are translating to, i.e., a GB translation is saved as `woocommerce-en_GB.po`. Now the strings are listed. ![screenshot](https://developer.woocommerce.com/wp-content/uploads/2023/12/Screen-Shot-2013-05-09-at-10.20.58.png) 5. Save after translating strings. The `.mo` file is generated automatically. 6. Update your `.po` file by opening it and then go to `Catalog > Update from POT file`. 7. Choose the file and it will be updated accordingly. ## Making your translation upgrade safe WooCommerce keeps translations in `wp-content/languages/plugins`, like all other plugins. But if you wish to include a custom translation, you can use the directory `wp-content/languages/woocommerce`, or you can use a snippet to load a custom translation stored elsewhere: ```php // Code to be placed in functions.php of your theme or a custom plugin file. add_filter( 'load_textdomain_mofile', 'load_custom_plugin_translation_file', 10, 2 ); /** * Replace 'textdomain' with your plugin's textdomain. e.g. 'woocommerce'. * File to be named, for example, yourtranslationfile-en_GB.mo * File to be placed, for example, wp-content/languages/textdomain/yourtranslationfile-en_GB.mo */ function load_custom_plugin_translation_file( $mofile, $domain ) { if ( 'textdomain' === $domain ) { $mofile = WP_LANG_DIR . '/textdomain/yourtranslationfile-' . get_locale() . '.mo'; } return $mofile; } ``` ## Other tools There are some other third-party tools that can help with translations. The following list shows a few of them. ### Loco Translate [Loco Translate](https://wordpress.org/plugins/loco-translate/) provides in-browser editing of WordPress translation files and integration with automatic translation services. ### Say what? [Say what?](https://wordpress.org/plugins/say-what/) allows to effortlessly translate or modify specific words without delving into a WordPress theme's `.po` file. ### String locator [String Locator](https://wordpress.org/plugins/string-locator/) enables quick searches within themes, plugins, or the WordPress core, displaying a list of files with the matching text and its line number. ## FAQ ### Why some strings on the Checkout page are not being translated? You may see that some of the strings are not being translated on the Checkout page. For example, in the screenshot below, `Local pickup` shipping method, `Cash on delivery` payment method and a message related to Privacy Policy are not being translated to Russian while the rest of the form is indeed translated: ![checkout page with some strings not translated](https://developer.woocommerce.com/wp-content/uploads/2023/12/not_translated.jpg) This usually happens when you first install WooCommerce and select default site language (English) and later change the site language to another one. In WooCommerce, the strings that have not been translated in the screenshot, are stored in the database after the initial WooCommerce installation. Therefore, if the site language is changed to another one, there is no way for WooCommerce to detect a translatable string since these are database entries. In order to fix it, navigate to WooCommerce settings corresponding to the string you need to change and update the translation there directly. For example, to fix the strings in our case above, you would need to do the following: **Local pickup**: 1. Go to `WooCommerce > Settings > Shipping > Shipping Zones`. 2. Select the shipping zone where "Local pickup" is listed. 3. Open "Local pickup" settings. 4. Rename the method using your translation. 5. Save the setting. **Cash on delivery**: 1. Go to `WooCommerce > Settings > Payments`. 2. Select the "Cash on delivery" payment method. 3. Open its settings. 4. Rename the method title, description, and instructions using your translation. 5. Save the setting. **Privacy policy message**: 1. Go to `WooCommerce > Settings > Accounts & Privacy`. 2. Scroll to the "Privacy policy" section. 3. Edit both the `Registration privacy policy` and `Checkout privacy policy` fields with your translation. 4. Save the settings. Navigate back to the Checkout page - translations should be reflected there. ### I have translated the strings I needed, but some of them don't show up translated on the front end. Why? If some of your translated strings don't show up as expected on your WooCommerce site, the first thing to check is if these strings have both a Single and Plural form in the Source text section. To do so, open the corresponding translation on [https://translate.wordpress.org/projects/wp-plugins/woocommerce/](https://translate.wordpress.org/projects/wp-plugins/woocommerce/), e.g. [the translation for Product and Products](https://translate.wordpress.org/projects/wp-plugins/woocommerce/stable/de/default/?filters%5Bstatus%5D=either&filters%5Boriginal_id%5D=577764&filters%5Btranslation_id%5D=24210880). This screenshot shows that the Singular translation is available: ![This screenshot shows that the Singular translation is available:](https://developer.woocommerce.com/wp-content/uploads/2023/12/Screenshot-2023-10-17-at-10.10.06.png) While this screenshot shows that the Plural translation is not available: ![this screenshot shows that the Plural translation is not available](https://developer.woocommerce.com/wp-content/uploads/2023/12/Screenshot-2023-10-17-at-10.10.21.png) --- ## How to configure caching plugins for WooCommerce *Source: best-practices/performance/configuring-caching-plugins.md* # How to configure caching plugins for WooCommerce ## Excluding pages from the cache Oftentimes if using caching plugins they'll already exclude these pages. Otherwise make sure you exclude the following pages from the cache through your caching systems respective settings. - Cart - My Account - Checkout These pages need to stay dynamic since they display information specific to the current customer and their cart. ## Excluding WooCommerce session from the cache If the caching system you're using offers database caching, it might be helpful to exclude `_wc_session_` from being cached. This will be dependent on the plugin or host caching so refer to the specific instructions or docs for that system. ## Excluding WooCommerce cookies from the cache Cookies in WooCommerce help track the products in your customers cart, can keep their cart in the database if they leave the site, and powers the recently viewed widget. Below is a list of the cookies WooCommerce uses for this, which you can exclude from caching. | COOKIE NAME | DURATION | PURPOSE | | --- | --- | --- | | woocommerce_cart_hash | session | Helps WooCommerce determine when cart contents/data changes. | | woocommerce_items_in_cart | session | Helps WooCommerce determine when cart contents/data changes. | | wp_woocommerce_session_ | 2 days | Contains a unique code for each customer so that it knows where to find the cart data in the database for each customer. | | woocommerce_recently_viewed | session | Powers the Recent Viewed Products widget. | | store_notice[notice id] | session | Allows customers to dismiss the Store Notice. | We're unable to cover all options, but we have added some tips for the popular caching plugins. For more specific support, please reach out to the support team responsible for your caching integration. ### W3 total cache minify settings Ensure you add 'mfunc' to the 'Ignored comment stems' option in the Minify settings. ### WP-Rocket WooCommerce is fully compatible with WP-Rocket. Please ensure that the following pages (Cart, Checkout, My Account) are not to be cached in the plugin's settings. We recommend avoiding JavaScript file minification. ### WP Super Cache WooCommerce is natively compatible with WP Super Cache. WooCommerce sends information to WP Super Cache so that it doesn't cache the Cart, Checkout, or My Account pages by default. ### Varnish ```varnish if (req.url ~ "^/(cart|my-account|checkout|addons)") { return (pass); } if ( req.url ~ "\\?add-to-cart=" ) { return (pass); } ``` ## Troubleshooting ### Why is my Varnish configuration not working in WooCommerce? Check out the following WordPress.org Support forum post on[ how cookies may be affecting your varnish coding](https://wordpress.org/support/topic/varnish-configuration-not-working-in-woocommerce). ```text Add this to vcl_recv above "if (req.http.cookie) {": # Unset Cookies except for WordPress admin and WooCommerce pages if (!(req.url ~ "(wp-login|wp-admin|cart|my-account/*|wc-api*|checkout|addons|logout|lost-password|product/*)")) { unset req.http.cookie; } # Pass through the WooCommerce dynamic pages if (req.url ~ "^/(cart|my-account/*|checkout|wc-api/*|addons|logout|lost-password|product/*)") { return (pass); } # Pass through the WooCommerce add to cart if (req.url ~ "\?add-to-cart=" ) { return (pass); } # Pass through the WooCommerce API if (req.url ~ "\?wc-api=" ) { return (pass); } # Block access to php admin pages via website if (req.url ~ "^/phpmyadmin/.*$" || req.url ~ "^/phppgadmin/.*$" || req.url ~ "^/server-status.*$") { error 403 "For security reasons, this URL is only accessible using localhost (127.0.0.1) as the hostname"; } Add this to vcl_fetch: # Unset Cookies except for WordPress admin and WooCommerce pages if ( (!(req.url ~ "(wp-(login|admin)|login|cart|my-account/*|wc-api*|checkout|addons|logout|lost-password|product/*)")) || (req.request == "GET") ) { unset beresp.http.set-cookie; } ``` ### Why is my Password Reset stuck in a loop? This is due to the My Account page being cached, Some hosts with server-side caching don't prevent my-account.php from being cached. If you're unable to reset your password and keep being returned to the login screen, please speak to your host to make sure this page is being excluded from their caching. --- ## Performance best practices for WooCommerce extensions *Source: best-practices/performance/performance-best-practices.md* # Performance best practices for WooCommerce extensions Optimizing the performance of WooCommerce extensions is vital for ensuring that online stores run smoothly, provide a superior user experience, and rank well in search engine results. This guide is tailored for developers looking to enhance the speed and efficiency of their WooCommerce extensions, with a focus on understanding performance impacts, benchmarking, testing, and implementing strategies for improvement. ## Performance optimization For WooCommerce extensions, performance optimization means ensuring that your code contributes to a fast, responsive user experience without adding unnecessary load times or resource usage to the store. ### Why performance is critical - **User Experience**: Fast-performing extensions contribute to a seamless shopping experience, encouraging customers to complete purchases. - **Store Performance**: Extensions can significantly impact the overall speed of WooCommerce stores; optimized extensions help maintain optimal site performance. - **SEO and Conversion Rates**: Speed is a critical factor for SEO rankings and conversion rates. Efficient extensions support better store rankings and higher conversions. ## Benchmarking performance Setting clear performance benchmarks is essential for development and continuous improvement of WooCommerce extensions. A recommended performance standard is achieving a Chrome Core Web Vitals "Performance" score of 90 or above on a simple Woo site, using tools like the [Chrome Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/). ### Using accessible tools for benchmarking Chrome Lighthouse provides a comprehensive framework for evaluating the performance of web pages, including those impacted by your WooCommerce extension. By integrating Lighthouse testing into your development workflow, you can identify and address performance issues early on. We recommend leveraging tools like this to assess the impact of your extension on a WooCommerce store's performance and to identify areas for improvement. ## Performance improvement strategies Optimizing the performance of WooCommerce extensions can involve several key strategies: - **Optimize asset loading**: Ensure that scripts and styles are loaded conditionally, only on pages where they're needed. - **Efficient database queries**: Optimize database interactions to minimize query times and resource usage. Use indexes appropriately and avoid unnecessary data retrieval. - **Lazy Loading**: Implement lazy loading for images and content loaded by your extension to reduce initial page load times. - **Minification and concatenation**: Minify CSS and JavaScript files and concatenate them where possible to reduce the number of HTTP requests. - **Testing with and without your extension**: Regularly test WooCommerce stores with your extension activated and deactivated to clearly understand its impact on performance. - **Caching support**: Ensure your extension is compatible with popular caching solutions, and avoid actions that might bypass or clear cache unnecessarily. By following these best practices and regularly benchmarking and testing your extension, you can ensure it enhances, rather than detracts from, the performance of WooCommerce stores. Implementing these strategies will lead to more efficient, faster-loading extensions that store owners and their customers will appreciate. --- ## How to optimize performance for WooCommerce stores *Source: best-practices/performance/performance-optimization.md* # How to optimize performance for WooCommerce stores ## Introduction This guide covers best practices and techniques for optimizing the performance of WooCommerce stores, including caching, image optimization, database maintenance, code minification, and the use of Content Delivery Networks (CDNs). By following these recommendations, developers can build high-performing WooCommerce stores that provide a better user experience and contribute to higher conversion rates. ## Audience This guide is intended for developers who are familiar with WordPress and WooCommerce and want to improve the performance of their online stores. ## Prerequisites To follow this guide, you should have: 1. A basic understanding of WordPress and WooCommerce. 2. Access to a WordPress website with WooCommerce installed and activated. ## Step 1 - Implement caching Caching plays a crucial role in speeding up your WooCommerce store by serving static versions of your pages to visitors, reducing the load on your server. There are several ways to implement caching for your WooCommerce store: ### Server-Side caching Enable server-side caching through your hosting provider or by using server-level caching solutions like Varnish, NGINX FastCGI Cache, or Redis. ### WordPress caching plugins Install and configure a WordPress caching plugin, such as WP Rocket, W3 Total Cache, or WP Super Cache. These plugins can help you set up page caching, browser caching, and object caching for your WooCommerce store. ### WooCommerce-Specific caching Ensure that your caching solution is configured correctly for WooCommerce, allowing dynamic content such as cart and checkout pages to remain uncached. Some caching plugins, like WP Rocket, include built-in support for WooCommerce caching. ## Step 2 - Optimize images Optimizing images can significantly improve your store's performance by reducing the size of image files without compromising quality. To optimize images for your WooCommerce store: 1. Use the right image format: Choose an appropriate format for your images, such as JPEG for photographs and PNG for graphics with transparency. 2. Compress images: Use an image compression tool like TinyPNG or ShortPixel to reduce file sizes before uploading them to your store. 3. Enable lazy loading: Lazy loading delays the loading of images until they're needed, improving initial page load times. Many caching plugins and performance optimization plugins offer built-in lazy loading options. 4. Use responsive images: Ensure that your theme and plugins serve appropriately sized images for different devices and screen resolutions. ## Step 3 - Minify and optimize code Minifying and optimizing your store's HTML, CSS, and JavaScript files can help reduce file sizes and improve page load times. To minify and optimize code for your WooCommerce store: 1. Use a plugin: Install a performance optimization plugin like Autoptimize, WP Rocket, or W3 Total Cache to minify and optimize your store's HTML, CSS, and JavaScript files. 2. Combine and inline critical CSS: Where possible, combine and inline critical CSS to reduce the number of requests and improve page load times. 3. Defer non-critical JavaScript: Defer loading of non-critical JavaScript files to improve perceived page load times. ## Step 4 - Use a content delivery network (CDN) A Content Delivery Network (CDN) can help speed up your WooCommerce store by serving static assets like images, CSS, and JavaScript files from a network of servers distributed across the globe. To use a CDN for your WooCommerce store: 1. Choose a CDN provider: Select a CDN provider like Cloudflare, Fastly, or Amazon CloudFront that fits your needs and budget. 2. Set up your CDN: Follow your chosen CDN provider's instructions to set up and configure the CDN for your WooCommerce store. ## Step 5 - Optimize database Regularly optimizing your WordPress database can help improve your WooCommerce store's performance by removing unnecessary data and optimizing database tables. To optimize your database: 1. Use a plugin: Install a database optimization plugin like WP-Optimize, WP-Sweep, or Advanced Database Cleaner to clean up and optimize your WordPress database. 2. Remove unnecessary data: Regularly delete spam comments, post revisions, and expired transients to reduce database clutter. 3. Optimize database tables: Use the database optimization plugin to optimize your database tables, improving their efficiency and reducing query times. ## Step 6 - Choose a high-performance theme and plugins The theme and plugins you choose for your WooCommerce store can have a significant impact on performance. To ensure your store runs efficiently: 1. Select a lightweight, performance-optimized theme: Choose a theme specifically designed for WooCommerce that prioritizes performance and follows best coding practices. 2. Evaluate plugin performance: Use tools like Query Monitor or WP Hive to analyze the performance impact of the plugins you install, and remove or replace those that negatively affect your store's performance. ## Step 7 - Enable GZIP compression GZIP compression can help reduce the size of your store's HTML, CSS, and JavaScript files, leading to faster page load times. To enable GZIP compression: 1. Use a plugin: Install a performance optimization plugin like WP Rocket, W3 Total Cache, or WP Super Cache that includes GZIP compression options. 2. Configure your server: Alternatively, enable GZIP compression directly on your server by modifying your .htaccess file (for Apache servers) or nginx.conf file (for NGINX servers). ## Step 8 - Monitor and analyze performance Continuously monitor and analyze your WooCommerce store's performance to identify potential bottlenecks and areas for improvement. To monitor and analyze performance: 1. Use performance testing tools: Regularly test your store's performance using tools like Google PageSpeed Insights, GTmetrix, or WebPageTest. 2. Implement performance monitoring: Install a performance monitoring plugin like New Relic or use a monitoring service like Uptime Robot to keep track of your store's performance over time. ## Conclusion By following these best practices and techniques for performance optimization, you can build a high-performing WooCommerce store that offers a better user experience and contributes to higher conversion rates. Continuously monitor and analyze your store's performance to ensure it remains optimized as your store grows and evolves. --- ## Review guidelines *Source: best-practices/review-guidelines/README.md* # Review guidelines Reviews are an integral part of the online shopping experience, and people installing software pay attention to them. Prospective users of your extensions will likely consider average ratings when making software choices. Many of today's most popular online review platforms - from Yelp business reviews, to Amazon product reviews - have a range of opinion that can be polarized, with many extremely positive and/or negative reviews, and fewer moderate opinions. This creates a "J-shaped" distribution of reviews that isn't as accurate or as helpful as could be. WooCommerce.com and WordPress.org both feature reviews heavily, and competing extensions having a higher rating likely have the edge in user choice. ## Primary considerations around reviews Requesting more reviews for a extension with major issues will not generate good reviews, and analyzing existing reviews will help surface areas to address before soliciting reviews. It is extremely rare for users of WordPress plugins to leave reviews organically (.2% of users for WordPress.org leave reviews), which means that there's an untapped market of 99.8% of users of the average plugin. These plugins are competing with other plugins on the same search terms in the WordPress.org plugin directory, and ratings are a large factor in the ranking algorithm. This is not usually a factor to the same extent on the WooCommerce Marketplace. For instance, WooCommerce's PayPal extension directly competes on all possible keywords with other PayPal extensions on the WordPress.org repository, while it does not compete with other PayPal payments extensions on the Marketplace. --- ## How to request WooCommerce extension reviews *Source: best-practices/review-guidelines/how-to-request-reviews.md* # How to request WooCommerce extension reviews ## Methods of requesting reviews ### Admin notices Admin notices are an industry standard method of requesting reviews, but bombarding the admin dashboard with admin notices is not effective. We recommend using restraint in the design of a notice, as well as limiting to a single notice at a time. It's very easy to overwhelm merchants with too many notices, or too intrusive notices. #### Recommendations * A good place for an admin notice to review an extension would be to show on the `Plugins` page and extension's settings pages. * Include a snooze option (or multiple) on your notices with a clear expectation of when the notice will reappear. * Admin notices should always be always be completely dismissable. They cannot only have a snooze option. * The options presented in the notice must be phrased carefully to avoid manipulative language. * Use consistently designed notices so the request for reviews feels like a part of your extension, and looks consistent with WooCommerce's design. ### Direct contact #### Recommendations * The most direct route to requesting reviews with the highest chance of being positive is to contact the customer when they are the happiest with the product. * This can be milestone or time based, following the timing guidelines below. * The best method for this is either an email or other direct exchange (support chat, call, etc.). This has the highest conversion rate, especially when timed properly so that the customer is happiest. * This is also extremely effective when you are able to request feedback from specifically qualified merchants, such as merchants that have processed a certain amount with your platform, or who have shipped their first 100 orders using your fulfillment extension, or similar. * Direct outreach is most likely to be successful if you have ways of targeting users for review requests (merchant account / usage info, etc.), as well as ways to gather the reviews, like sales or marketing teams able to email/call/chat with merchants. ## Messaging for requesting reviews One method of requesting feedback that we recommend is using the NPS style of review solicitation. This can allow for an increase positive reviews as well as providing the opportunity to assist merchants that are struggling. NPS-style reviews first ask the user how they rate the product (out of 5 stars), then route them based on their response: * If they click 4 or 5 stars, ask them to leave a review. * If they click 1, 2 or 3, tell them we're here to help & ask them to submit a support ticket. Merchants are significantly more likely to leave a review after a positive support interaction with a support rep who explicitly asked for a review. The language "the best way to thank me is to leave a 5 star review that mentions me in it" or similar tends to work very well - people are more willing to help a person than a produc or company. --- ## Miscellaneous guidelines and advice *Source: best-practices/review-guidelines/misc-guidelines.md* # Miscellaneous guidelines and advice Contributors' names matching search terms directly will rank extremely highly on the WordPress.org plugin repo, which means that having a WordPress.org user named after your business (if that's a search term for your plugin) could tilt the scales over a competing plugin. Constant nags and overwhelming the admin dashboard with unnecessary alerts detract from your user experience. You can request to have reviews that are not actually feedback removed. Where this might be applicable is if the request is a simple support request, where it's obviously in the wrong spot. 1 star reviews, even if aggressive or angry, are not usually removed. Reply to reviews! Thank the giver for offering feedback, acknowledge the issue if needed, ask for more specific feedback, or provide an update when that feedback is addressed! Your reviews are a great window into what your extension's users are actually thinking. Having folks close to the extension's development (think developers and project managers) looking at reviews on a regular basis is a good way to ensure the customers voice is heard. 1 star reviews are among the most useful, as long as the issue is understood and addressed (if needed). --- ## Notifying users about bug fixes and feature requests *Source: best-practices/review-guidelines/notifying-users-about-important-events.md* # Notifying users about bug fixes and feature requests A bug or a missing feature can be a showstopper for merchants. Bugs that pile up or popular features that are not implemented can lead to negative reviews and/or merchants churning and looking at a competitive solution. Bugs are usually reported via support or GitHub, or you can discover them yourselves in testing. When a critical bug is found, resolve it within a couple of days (most critical bugs should be resolved within 24 hours) and release a new plugin version promptly. Part of your release process should be to notify all stakeholders (the support team and the merchant affected) about the upcoming release. Even though a critical bug is a great source of stress for merchants, a quick resolution makes merchants feel heard and supported - having a reliable business partner, who is keen to help in the most difficult situation, helps build a stronger relationship. Therefore, we usually ask merchants for a 5* review when we deliver a fast solution. When you implement a new feature request and ship a new plugin version, you can follow a similar approach to bugs: * Notify all stakeholders. * Update the relevant request in the Feature Requests board, by sharing a public update and marking it as 'Completed'. * For breaking releases: communicating with your marketing/relations teams to publish updates/newsletters before the release. --- ## How to respond to negative WooCommerce extension reviews *Source: best-practices/review-guidelines/responding-to-negative-reviews.md* # How to respond to negative WooCommerce extension reviews An unpleasant event in the merchant's journey can lead them to leave a public, negative review. These events usually are: * a problem with the product, * a missing product feature, * an unhelpful reply, * long wait times to receive a reply, or; * combinations of the above. When receiving a negative review, your goal should always be to turn this review around - this sounds tough, but it is really rewarding. In the majority of cases, merchants who leave a negative review have first tried contacting support for help. This is useful knowledge, as we can read through the conversation history, understand the issue the merchant experienced and share more details with them when we reach out, even from our first reply. The process we have seen work well is: * Create a new response (via email, or on a public review) with subject: Regarding your recent review for xxx. * Start by introducing yourself, for example: "Hey there, This is Andrew from the team that develops xxx". * Use empathetic language and make it clear that this negative review had an impact on you. For example, "I read your recent review for xxx and I am worried to hear that an issue is preventing you from using this plugin as you had in mind. I'd be happy to help you resolve this!". Compare the above sentence with: "I am sorry to hear that you experienced an issue with xxx". "I am sorry to" indicates that you are saddened by an event, but don't necessarily plan to do something about it. In comparison, in "I am worried to hear", worry indicates action. Additionally, "That you experienced an issue" can be interpreted as if the problem is mainly the merchant's fault, whereas language like "an issue is preventing you from using this plugin as you had in mind. I'd be happy to help you resolve this!" implies you and the merchant are on the same team. * Share more details, a solution, an idea, a suggestion or an explanation. * Urge the merchant to update the review, by highlighting how important reviews are for our team and for other merchants. Example language to do this is "We would appreciate it if you could take a couple of minutes to update your review and describe your experience with our product and support. Honest reviews are extremely helpful for other merchants who try to decide if a plugin is a right fit for them. Thank you for your contribution!". * Include a direct link to the reviews section, so merchants can easily navigate there and change their review. * On a follow-up communication, if the merchant has changed the review, consider saying something like: "I shared this with the rest of the team and it made everyone's day". If the above things are true, sharing some of your procedures with merchants (highlighting how your team emphasizes and thrives on feedback) helps ensure merchants feel like you are part of their team and builds a strong relationship with them. Even a merchant that doesn't change their review can offer a mutually beneficial discussion by learning more about their setup and offering some suggestions. These conversations help grow merchants' trust. --- ## Utilizing WooCommerce extension feature requests *Source: best-practices/review-guidelines/utilizing-feature-requests.md* # Utilizing WooCommerce extension feature requests It is important to keep track of all feature requests, and have some sort of system of record where anyone can see what kind of feedback the product is receiving over time. We recommend a daily or bi-daily check-in, where you: * triage new feature requests, * celebrate positive reviews and; * act upon negative reviews. Carefully maintaining feature request boards (or similar system) is key, as the average board contains a lot of duplicate/spam content, requests about features that have been implemented and requests about features that will likely never be implemented. Poorly maintained boards make merchants feel unheard/neglected. This results in more negative reviews on the premise that the product teams were not reading/listening to their feedback. We've seen good results with the following procedures: Starting with the most affected products, go through all open requests, reply to most/all of them and categorize them as: * "Open", for requests that we still want more feedback, * "Planned", for requests that we plan to implement, * "Completed", for requests that have already been implemented and; * "Closed", for requests that we do not plan to implement, as they are not a good fit for the product, for duplicate/spam requests and for requests that were actually support questions. Replying to all "Open" requests is the goal, but if that's not attainable currently, make sure to reply to 100% of the requests that are closed. For new open requests that arrive as a feature request, discuss/triage them, reply promptly, and assign a status to avoid having the board become unmanaged, and ensure merchants feel (and are) heard. In addition to the effect a tidy board has on merchants, it also helps product teams better understand which requests are most wanted and most impactful and then plan work accordingly. --- ## Utilizing your support team to respond to feedback *Source: best-practices/review-guidelines/utilizing-your-support-team.md* # Utilizing your support team to respond to feedback Your support team is usually the primary contact point of merchants when they contact you. Tickets and chats are the best tools we have to converse with merchants, understand pain-points about our software, listen to their feedback and analyze their feature requests. Collectively, support teams have a great understanding of the products and how people use them. This information is essential to be transferred over to product and engineering teams. We recommend that you take the following steps to best utilize your support team: * Create a strict internal SLA where support team requests are answered by product or engineering teams. * Ensure you have a way for your support team to effectively report bugs to your product and engineering teams. * When responding to your support team, avoid super-short answers, and try to explain the answer simply and concisely. This will allow the support agent to copy/paste your answer to the merchant. * Avoid replying with statements like "no, this is not possible" or "no, this feature will not be implemented" without providing additional context about technical or product limitations. * Regularly dedicate additional time to implement a short custom code snippets or to provide in-depth technical details about how a custom project would be implemented so that merchants can reach a solution faster if they decide to hire a dedicated WooCommerce developer. A small effort can go a long way to amaze merchants and reveal an opportunity to request a 5 star review. * Keep support in the loop when they report a bug or request a new feature. When you release a new product version, we always consider the impact it can have on support. * Work closely with your support team. For example, consider having a feedback hangout call every month where you can discuss product feedback and planned improvements. With these kinds of practices in place, support teams are more willing to share feedback, issues, concerns, and questions with us. This helps maintain a closer relationship with merchants and identify pain-points early, before they become a reason for them to churn. --- ## When to request WooCommerce extension reviews *Source: best-practices/review-guidelines/when-to-request-reviews.md* # When to request WooCommerce extension reviews The best approach to increasing our top-star reviews is to identify key moments in the merchant's journey, when they are more likely to leave a review and actively request for it. The most distinct moments for most of our use cases are: * When merchants feel helpless, lost, frustrated and we are able to help, * When merchants find a bug in our code and we quickly ship a fix, * When merchants need a feature and we notify them when it is shipped, * When merchants feel alone and we make them feel heard and; * When merchants contact with a question and we go out of our way to provide them with top-notch support, even if this means slightly stepping outside the official boundaries of a support policy. Think about who is seeing the review request, and what they are doing at that time. Showing a request to a fulfillment worker just trying to ship an order isn't likely going to work well. Outreach after a milestone works really well. Some language we've used before is "Congratulations on your xxth sale! We're delighted that WooPayments facilitated this milestone. Would you consider sharing your experience and encouraging others by reviewing our extension?". Another way to optimally time a review request would be to setup a prompt that aligns with use patterns. For instance, if you know that most of your merchants use your extension daily, you would likely send a review request sooner than a extension that most merchants interact very sparingly with. SaaS/Connector extensions need to be particularly careful about requesting ratings correctly, as they are the most likely to be overlooked unless there is an issue, leading to skewed ratings not representative of the actual extension. Consider requesting feedback at the end of every single support interaction, especially in the WordPress.org support forums. One of the largest barriers to leaving a review is the requirement of a user being logged into WooCommerce.com (or WordPress.org), and the WordPress.org support forums present a good opportunity to gather these reviews. By being highly responsive in the public support forum and solving issues there, users are already logged in and able to immediately leave a review (after being requested to!). --- ## How to Prevent Data Leaks in WooCommerce *Source: best-practices/security/prevent-data-leaks.md* # How to Prevent Data Leaks in WooCommerce Data leaks can expose sensitive information and compromise the security of a WooCommerce site. One common source of data leaks is direct access to PHP files. This tutorial will show you how to prevent these kinds of data leaks. In each PHP file of your WooCommerce extension, you should check if a constant called 'ABSPATH' is defined. This constant is defined by WordPress itself, and it's not defined if a file is being accessed directly. Here's how you can do this: ```php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } ``` With this code, if someone tries to access the PHP file directly, the 'ABSPATH' constant won't be defined, and the script will exit before any sensitive data can be leaked. Remember, security is a crucial aspect of any WooCommerce site. Always take steps to prevent data leaks and protect your site's information. --- ## WooCommerce security best practices *Source: best-practices/security/security-best-practices.md* # WooCommerce security best practices ## Introduction to security practices Security is extremely important for online stores. A breach can lead to significant financial loss, reputational damage, and erosion of customer trust. Implementing robust security practices is crucial. This guide covers the best practices for securing WooCommerce stores, including hardening WordPress, keeping plugins and themes up to date, implementing secure coding practices, and protecting user data. By following these recommendations, developers can build secure and resilient WooCommerce stores that protect both their business and their customers. ### Audience This guide is intended for developers who are familiar with WordPress and WooCommerce and want to improve the security of their online stores. ### Prerequisites To follow this guide, you should have: 1. A basic understanding of WordPress and WooCommerce. 2. Access to a WordPress website with WooCommerce installed and activated. ## Security standards We recommend that you maintain the following standards: - **PHPCS**: The PHP Code Sniffer tool helps ensure your code adheres to specific coding standards, which can prevent common security issues. Extensions shouldn't have any failure results against PHPCS. - **SemGrep**: Focuses on identifying patterns that may indicate security vulnerabilities, allowing developers to write more secure code. Extensions shouldn't have any failure results against SemGrep. Understanding and implementing these standards can significantly reduce the risk of security vulnerabilities in your extensions and themes. ### Common security pitfalls and how to avoid them Several common security pitfalls can easily be avoided with vigilance and best practices: - **SQL injection**: Ensure all database queries are properly sanitized. - **Cross-site scripting**: Sanitize all user input to prevent malicious code from being executed. - **File inclusions**: Limit file inclusion to prevent unauthorized access to the file system. Awareness and prevention of these common issues are crucial steps in securing your WooCommerce store. For more resources, we recommend reading the [WordPress security documentation](https://developer.wordpress.org/apis/security/). ## Manual testing guidelines Even without access to automated tools like the [Woo Quality Insights Toolkit (QIT)](https://qit.woo.com), manual testing remains a vital component of securing your WooCommerce store. Here are some guidelines on how developers can manually test their extensions for security vulnerabilities: 1. **Code review**: Regularly review your code for security vulnerabilities, focusing on the areas where breaches commonly occur, such as user input fields and database queries. Code reviews should check against the [WordPress security criteria](https://developer.wordpress.org/apis/security/) mentioned above. 2. **Use security plugins**: Plugins like Wordfence, Sucuri, or iThemes Security provide an interface for manual security checks, including file integrity monitoring and malware scanning. Additionally, tooling like [OWASP Zap](https://www.zaproxy.org/) or [GitHub Security Scanning](https://github.com/features/security/) can be used. 3. **Testing environments**: Utilize staging environments to test the security of new features or updates before deploying them live. ## Other security best practices ### Keep WordPress, WooCommerce, and plugins up to date Regularly updating WordPress, WooCommerce, and all installed plugins is crucial to maintaining a secure online store. Updates often include security patches that address vulnerabilities and help protect your store from attacks. To keep your WordPress and WooCommerce installations up to date: 1. Enable automatic updates for WordPress core. 2. Regularly check for and install updates for WooCommerce and all plugins. ### Choose secure plugins and themes The plugins and themes you use can have a significant impact on the security of your WooCommerce store. To ensure your store is secure: 1. Install plugins and themes from reputable sources, such as the WordPress Plugin Directory and Theme Directory. 2. Regularly review and update the plugins and themes you use, removing any that are no longer maintained or have known security vulnerabilities. 3. Avoid using nulled or pirated plugins and themes, which may contain malicious code. ### Implement secure coding practices Secure coding practices are essential for building a secure WooCommerce store. To implement secure coding practices: 1. Follow the WordPress Coding Standards when developing custom themes or plugins. 2. Use prepared statements and parameterized queries to protect against SQL injection attacks. 3. Validate and sanitize user input to prevent cross-site scripting (XSS) attacks and other vulnerabilities. 4. Regularly review and update your custom code to address potential security vulnerabilities. ### Harden WordPress security Hardening your WordPress installation can help protect your WooCommerce store from attacks. To harden your WordPress security: 1. Use strong, unique passwords for all user accounts. 2. Limit login attempts and enable two-factor authentication (2FA) to protect against brute-force attacks. 3. Change the default `wp_` table prefix in your WordPress database. 4. Disable XML-RPC and REST API access when not needed. 5. Keep file permissions secure and restrict access to sensitive files and directories. ### Secure user data Protecting your customers' data is a critical aspect of securing your WooCommerce store. To secure user data: 1. Use SSL certificates to encrypt data transmitted between your store and your customers. 2. Store customer data securely and limit access to sensitive information. 3. Comply with data protection regulations, such as the GDPR, to ensure you handle customer data responsibly. ### Implement a security plugin Using a security plugin can help you monitor and protect your WooCommerce store from potential threats. To implement a security plugin: 1. Choose a reputable security plugin, such as Wordfence, Sucuri, or iThemes Security. 2. Configure the plugin's settings to enable features like malware scanning, firewall protection, and login security. ### Regularly monitor and audit your store's security Continuously monitor and audit your WooCommerce store's security to identify potential vulnerabilities and address them before they can be exploited. To monitor and audit your store's security: 1. Use a security plugin to perform regular scans for malware and other security threats. 2. Monitor your site's activity logs to identify suspicious activity and potential security issues. 3. Perform regular security audits to evaluate your store's overall security and identify areas for improvement. ### Create regular backups Backing up your WooCommerce store is essential for quickly recovering from security incidents, such as data loss or site compromise. To create regular backups: 1. Choose a reliable backup plugin, such as UpdraftPlus, BackupBuddy, or Duplicator. 2. Configure the plugin to automatically create regular backups of your entire site, including the database, files, and media. 3. Store your backups securely off-site to ensure they are accessible in case of an emergency. ## Conclusion By following these security best practices, you can build a secure and resilient WooCommerce store that protects both your business and your customers. Regularly monitoring, auditing, and updating your store's security measures will help ensure it remains protected as new threats and vulnerabilities emerge. --- ## Customizing WooCommerce endpoint URLs *Source: best-practices/urls-and-routing/customizing-endpoint-urls.md* # Customizing WooCommerce endpoint URLs Before you start, check out [WooCommerce Endpoints](./woocommerce-endpoints.md). ## Customizing endpoint URLs The URL for each endpoint can be customized in **WooCommerce > Settings > Advanced** in the Page setup section. ![Endpoints](https://developer.woocommerce.com/wp-content/uploads/2023/12/endpoints.png) Ensure that they are unique to avoid conflicts. If you encounter issues with 404s, go to **Settings > Permalinks** and save to flush the rewrite rules. ## Using endpoints in menus If you want to include an endpoint in your menus, you need to use the Links section: ![The Links section of a menu item in WordPress](https://developer.woocommerce.com/wp-content/uploads/2023/12/2014-02-26-at-14.26.png) Enter the full URL to the endpoint and then insert that into your menu. Remember that some endpoints, such as view-order, require an order ID to work. In general, we don't recommend adding these endpoints to your menus. These pages can instead be accessed via the my-account page. ## Using endpoints in payment gateway plugins WooCommerce provides helper functions in the order class for getting these URLs. They are: `$order->get_checkout_payment_url( $on_checkout = false );` and: `$order->get_checkout_order_received_url();` Gateways need to use these methods for full 2.1+ compatibility. ## Troubleshooting ### Endpoints showing 404 - If you see a 404 error, go to **WordPress Admin** > **Settings > Permalinks** and Save. This ensures that rewrite rules for endpoints exist and are ready to be used. - If using an endpoint such as view-order, ensure that it specifies an order number. /view-order/ is invalid. /view-order/10/ is valid. These types of endpoints should not be in your navigation menus. ### Endpoints are not working On Windows servers, the **web.config** file may not be set correctly to allow for the endpoints to work correctly. In this case, clicking on endpoint links (e.g. /edit-account/ or /customer-logout/) may appear to do nothing except refresh the page. In order to resolve this, try simplifying the **web.config** file on your Windows server. Here's a sample file configuration: ```xml <<>?xml version="1.0" encoding="UTF-8"?> ``` ### Pages direct to wrong place Landing on the wrong page when clicking an endpoint URL is typically caused by incorrect settings. For example, clicking 'Edit address' on your account page takes you to the Shop page instead of the edit address form means you selected the wrong page in settings. Confirm that your pages are correctly configured and that a different page is used for each section. ### How to remove "Downloads" from My Account Sometimes the "Downloads" endpoint on the "My account" page does not need to be displayed. This can be removed by going to **WooCommerce > Settings > Advanced > Account endpoints** and clearing the Downloads endpoint field. ![Account endpoints](https://developer.woocommerce.com/wp-content/uploads/2023/12/Screenshot-2023-04-09-at-11.45.58-PM.png) --- ## Understanding the risks of removing URL bases in WooCommerce *Source: best-practices/urls-and-routing/removing-product-product-category-or-shop-from-the-url.md* # Understanding the risks of removing URL bases in WooCommerce ## In sum Removing `/product/`, `/product-category/`, or `/shop/` from the URLs is not advisable due to the way WordPress resolves its URLs. It uses the `product-category` (or any other text for that matter) base of a URL to detect that it is a URL leading to a product category. There are SEO plugins that allow you to remove this base, but that can lead to a number of problems with performance and duplicate URLs. ## Better to avoid You will make it harder for WordPress to detect what page you are trying to reach when you type in a product category URL. Also, understand that the standard "Page" in WordPress always has no base text in the URL. For example: - `http://yoursite.com/about-page/` (this is the URL of a standard page) - `http://yoursite.com/product-category/category-x/` (this is the URL leading to a product category) What would happen if we remove that 'product-category' part? - `http://yoursite.com/about-page/` - `http://yoursite.com/category-x/` WordPress will have to do much more work to detect what page you are looking for when entering one of the above URLs. That is why we do not recommend using any SEO plugin to achieve this. --- ## Working with webhooks in WooCommerce *Source: best-practices/urls-and-routing/webhooks.md* # Working with webhooks in WooCommerce ## What are webhooks? A [Webhook](http://en.wikipedia.org/wiki/Webhook) is an event notification sent to a URL of your choice. Users can configure them to trigger events on one site to invoke behavior on another. Webhooks are useful for integrating with third-party services and other external API that support them. ## Webhooks in WooCommerce Webhooks were introduced in WooCommerce 2.2 and can trigger events each time you add, edit or delete orders, products, coupons or customers. It's also possible to use webhooks with WooCommerce actions, e.g., Create a webhook to be used every time a product is added to the shopping cart, using the action `woocommerce_add_to_cart`. Webhooks also make it easier for third-party apps to integrate with WooCommerce. ## Creating webhooks ![WebHooks screen](https://woocommerce.com/wp-content/uploads/2013/01/woo-webhooks.png) To create a new webhook: 1/ **Go to**: **WooCommerce > Settings > Advanced > Webhooks**. > **Note:** Webhooks were formerly found under WooCommerce > Settings > API prior to WooCommerce 3.4. 2/ Select **Create a new webhook** (first incident) or **Add webhook**. The **Webhook Data** box appears. ![WebHooks creation](https://woocommerce.com/wp-content/uploads/2013/01/woo-webhooks.png) 3/ **Enter**. - **Name**: The **name** is auto-generated as "Webhook created on [date and time of creation]" as a standard to facilitate creation. Change the name to something else. - **Status**: Set to **Active** (delivers payload), **Paused** (does not deliver), or **Disabled** (does not deliver due delivery failures). - **Topic**: Indicate when the webhook should be triggered - **Order Created**, **Product Deleted**, or **Customer Updated**. There are also **Action** and **Custom** options. - **Action Event**: This option is available when the Topic is a WooCommerce **Action**, such as `woocommerce_add_to_cart` for when customers add products to the shopping cart. - **Custom Topic**: This option is for **advanced users only**. It's possible to introduce new, customized topics with the help of `woocommerce_webhook_topic_hooks` filter. - **Delivery URL**: URL where the webhook payload is delivered. - **Secret**: The Secret Key generates a hash of the delivered webhook and is provided in the request headers. This defaults to the current API user's consumer secret, if nothing is entered. 4/ **Save webhook**. > **Note**: The first time your webhook is saved with the Activated status, it sends a ping to the Delivery URL. Webhooks are disabled after 5 retries by default if the delivery URL returns an unsuccessful status such as `404` or `5xx`. Successful responses are `2xx`, `301` or `302`. To increase the number of retries, you can use the `woocommerce_max_webhook_delivery_failures` filter function. ## Editing and deleting webhooks Webhooks are listed the same way as posts or products. 1. Find the webhook you wish to alter. 2. Hover over the name, and **Edit** and **Delete permanently** options appear. 3. **Delete**, or make **Edits** and **Save changes**. Bulk deletion is also possible with the dropdown. ![WebHooks deletion](https://woocommerce.com/wp-content/uploads/2013/01/editdelete-webhook.png) ## Webhook logs WooCommerce saves logs of all events triggering a webhook. Webhook logs are found at: **WooCommerce > Status > Logs**. ![WebHooks logs](https://woocommerce.com/wp-content/uploads/2022/11/Viewing-WooCommerce-Webhook-Logs.png?w=650) Logs may be reviewed to see delivery and response from the server, making it simpler to integrate and debug. --- ## Understanding WooCommerce endpoints *Source: best-practices/urls-and-routing/woocommerce-endpoints.md* # Understanding WooCommerce endpoints Endpoints are an extra part in the website URL that is detected to show different content when present. For example: You may have a 'my account' page shown at URL **yoursite.com/my-account**. When the endpoint 'edit-account' is appended to this URL, making it '**yoursite.com/my-account/edit-account**' then the **Edit account page** is shown instead of the **My account page**. This allows us to show different content without the need for multiple pages and shortcodes, and reduces the amount of content that needs to be installed. Endpoints are located at **WooCommerce > Settings > Advanced**. ## Checkout endpoints The following endpoints are used for checkout-related functionality and are appended to the URL of the /checkout page: - Pay page - `/order-pay/{ORDER_ID}` - Order received (thanks) - `/order-received/` - Add payment method - `/add-payment-method/` - Delete payment method - `/delete-payment-method/` - Set default payment method - `/set-default-payment-method/` ## Account endpoints The following endpoints are used for account-related functionality and are appended to the URL of the /my-account page: - Orders - `/orders/` - View order - `/view-order/{ORDER_ID}` - Downloads - `/downloads/` - Edit account (and change password) - `/edit-account/` - Addresses - `/edit-address/` - Payment methods - `/payment-methods/` - Lost password - `/lost-password/` - Logout - `/customer-logout/` ## Learn more - [Customizing endpoint URLs](./customizing-endpoint-urls.md) --- ## Blocks reference *Source: block-development/block-references.md* # Blocks reference This page lists the Woo blocks included in the package. (Incomplete as there are still blocks that are not using block.json definition). ## Product Average Rating (Beta) - woocommerce/product-average-rating Display the average rating of a product - **Name:** woocommerce/product-average-rating - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/single-product - **Parent:** - **Supports:** color (background, text), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize) - **Attributes:** textAlign ## Add to Cart Button - woocommerce/product-button Display a call to action button which either adds the product to the cart, or links to the product page. - **Name:** woocommerce/product-button - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/all-products, woocommerce/single-product, core/post-template, woocommerce/product-template - **Parent:** - **Supports:** align (full, wide), color (background, text, ~~link~~), interactivity, spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** isDescendentOfQueryLoop, isDescendentOfSingleProductBlock, productId, textAlign, width ## Product Image - woocommerce/product-image Display the main product image. - **Name:** woocommerce/product-image - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/all-products, woocommerce/single-product, woocommerce/product-template, core/post-template - **Parent:** - **Supports:** dimensions (aspectRatio), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize), ~~html~~ - **Attributes:** aspectRatio, height, imageSizing, isDescendentOfQueryLoop, isDescendentOfSingleProductBlock, productId, saleBadgeAlign, scale, showProductLink, showSaleBadge, width ## Product Price - woocommerce/product-price Display the price of a product. - **Name:** woocommerce/product-price - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/all-products, woocommerce/single-product, woocommerce/product-template, core/post-template - **Parent:** - **Supports:** color (background, text, ~~link~~), interactivity, spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** isDescendentOfQueryLoop, isDescendentOfSingleProductBlock, isDescendentOfSingleProductTemplate, productId, textAlign ## Product Image Gallery - woocommerce/product-image-gallery Display a product's images. - **Name:** woocommerce/product-image-gallery - **Category:** woocommerce-product-elements - **Ancestor:** - **Parent:** - **Supports:** align, interactivity (clientNavigation), ~~multiple~~ - **Attributes:** ## Product Meta - woocommerce/product-meta Display a product’s SKU, categories, tags, and more. - **Name:** woocommerce/product-meta - **Category:** woocommerce-product-elements - **Ancestor:** - **Parent:** - **Supports:** align, interactivity (clientNavigation), ~~reusable~~ - **Attributes:** ## Product Rating - woocommerce/product-rating Display the average rating of a product. - **Name:** woocommerce/product-rating - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/all-products, woocommerce/single-product, woocommerce/product-template, core/post-template - **Parent:** - **Supports:** color (text, ~~background~~, ~~link~~), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize) - **Attributes:** isDescendentOfQueryLoop, isDescendentOfSingleProductBlock, isDescendentOfSingleProductTemplate, productId, textAlign ## Product Rating Counter - woocommerce/product-rating-counter Display the review count of a product - **Name:** woocommerce/product-rating-counter - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/single-product - **Parent:** - **Supports:** color (link, ~~background~~, ~~text~~), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize), ~~inserter~~ - **Attributes:** isDescendentOfQueryLoop, isDescendentOfSingleProductBlock, isDescendentOfSingleProductTemplate, productId, textAlign ## Product Rating Stars - woocommerce/product-rating-stars Display the average rating of a product with stars - **Name:** woocommerce/product-rating-stars - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/single-product - **Parent:** - **Supports:** color (text, ~~background~~, ~~link~~), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize), ~~inserter~~ - **Attributes:** isDescendentOfQueryLoop, isDescendentOfSingleProductBlock, isDescendentOfSingleProductTemplate, productId, textAlign ## Related Products - woocommerce/related-products Display related products. - **Name:** woocommerce/related-products - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align, interactivity (clientNavigation), ~~inserter~~, ~~reusable~~ - **Attributes:** ## On-Sale Badge - woocommerce/product-sale-badge Displays an on-sale badge if the product is on-sale. - **Name:** woocommerce/product-sale-badge - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/single-product, woocommerce/product-template, core/post-template, woocommerce/product-gallery - **Parent:** - **Supports:** align, color (background, gradients, text, ~~link~~), interactivity (clientNavigation), spacing (margin), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** isDescendentOfQueryLoop, isDescendentOfSingleProductBlock, isDescendentOfSingleProductTemplate, productId ## Product SKU - woocommerce/product-sku Displays the SKU of a product. - **Name:** woocommerce/product-sku - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/product-meta, woocommerce/all-products, woocommerce/single-product, woocommerce/product-template, core/post-template - **Parent:** - **Supports:** color (background, text), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** isDescendantOfAllProducts, prefix, productId, showProductSelector, suffix ## Product Stock Indicator - woocommerce/product-stock-indicator Let shoppers know when products are out of stock or on backorder. This block is hidden when products are in stock. - **Name:** woocommerce/product-stock-indicator - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/all-products, woocommerce/single-product, woocommerce/product-template, core/post-template - **Parent:** - **Supports:** color (background, text), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** isDescendantOfAllProducts, isDescendentOfQueryLoop ## Product Summary - woocommerce/product-summary Display a short description about a product. - **Name:** woocommerce/product-summary - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/all-products, woocommerce/single-product, woocommerce/product-template, core/post-template - **Parent:** - **Supports:** color (background, link, text), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight) - **Attributes:** isDescendantOfAllProducts, isDescendentOfQueryLoop, isDescendentOfSingleProductBlock, isDescendentOfSingleProductTemplate, linkText, productId, showDescriptionIfEmpty, showLink, summaryLength ## Product Title - woocommerce/product-title Display the title of a product. - **Name:** woocommerce/product-title - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/all-products - **Parent:** - **Supports:** color (background, gradients, text, ~~link~~), interactivity (~~clientNavigation~~), spacing (margin), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** align, headingLevel, linkTarget, productId, showProductLink ## Accordion Group - woocommerce/accordion-group A group of headers and associated expandable content. - **Name:** woocommerce/accordion-group - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), background (backgroundImage, backgroundSize), color (background, gradient, text), interactivity, layout, shadow, spacing (blockGap, margin, padding), ~~html~~ - **Attributes:** allowedBlocks, autoclose, iconPosition ## Accordion Header - woocommerce/accordion-header Accordion header. - **Name:** woocommerce/accordion-header - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/accordion-item - **Supports:** anchor, border, color (background, gradient, text), interactivity, layout, shadow, spacing (margin, padding), typography (fontSize, textAlign), ~~align~~ - **Attributes:** icon, iconPosition, level, levelOptions, openByDefault, textAlignment, title ## Accordion - woocommerce/accordion-item A single accordion that displays a header and expandable content. - **Name:** woocommerce/accordion-item - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/accordion-group - **Supports:** align (full, wide), color (background, gradient, text), interactivity, layout, shadow, spacing (blockGap, margin) - **Attributes:** openByDefault ## Accordion Panel - woocommerce/accordion-panel Accordion Panel - **Name:** woocommerce/accordion-panel - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/accordion-item - **Supports:** border, color (background, gradient, text), interactivity, layout, shadow, spacing (blockGap, margin, padding), typography (fontSize, lineHeight) - **Attributes:** allowedBlocks, isSelected, openByDefault, templateLock ## Active Filters Controls - woocommerce/active-filters Display the currently active filters. - **Name:** woocommerce/active-filters - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** color (text, ~~background~~), interactivity (~~clientNavigation~~), ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~ - **Attributes:** displayStyle, headingLevel ## Add to Cart + Options (Beta) - woocommerce/add-to-cart-with-options Use blocks to create an "Add to cart" area that's customized for different product types, such as variable and grouped. - **Name:** woocommerce/add-to-cart-with-options - **Category:** woocommerce-product-elements - **Ancestor:** - **Parent:** - **Supports:** interactivity - **Attributes:** isDescendantOfAddToCartWithOptions ## Grouped Product Selector (Beta) - woocommerce/add-to-cart-with-options-grouped-product-selector Display a group of products that can be added to the cart. - **Name:** woocommerce/add-to-cart-with-options-grouped-product-selector - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/add-to-cart-with-options - **Parent:** - **Supports:** interactivity - **Attributes:** ## Grouped Product: Template (Beta) - woocommerce/add-to-cart-with-options-grouped-product-item A list item template that represents a child product within the Grouped Product Selector block. - **Name:** woocommerce/add-to-cart-with-options-grouped-product-item - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/add-to-cart-with-options-grouped-product-selector - **Parent:** - **Supports:** interactivity, ~~inserter~~ - **Attributes:** ## Grouped Product: Item Label (Beta) - woocommerce/add-to-cart-with-options-grouped-product-item-label Display the product title as a label or paragraph. - **Name:** woocommerce/add-to-cart-with-options-grouped-product-item-label - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/add-to-cart-with-options-grouped-product-item - **Parent:** - **Supports:** color (background, gradients, text), layout (selfStretch), spacing (blockGap, margin, padding), typography (fontSize, lineHeight, textAlign), ~~html~~ - **Attributes:** ## Grouped Product: Item Selector (Beta) - woocommerce/add-to-cart-with-options-grouped-product-item-selector Add a way of selecting a child product within the Grouped Product block. Depending on the type of product and its properties, this might be a button, a checkbox, or a link. - **Name:** woocommerce/add-to-cart-with-options-grouped-product-item-selector - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/add-to-cart-with-options-grouped-product-item - **Parent:** - **Supports:** interactivity, ~~inserter~~ - **Attributes:** ## Product Quantity (Beta) - woocommerce/add-to-cart-with-options-quantity-selector Display an input field customers can use to select the number of products to add to their cart. - **Name:** woocommerce/add-to-cart-with-options-quantity-selector - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/add-to-cart-with-options - **Parent:** - **Supports:** interactivity - **Attributes:** ## Variation Selector: Template (Beta) - woocommerce/add-to-cart-with-options-variation-selector-attribute A template for attribute name and options that will be applied to all variable products with attributes. - **Name:** woocommerce/add-to-cart-with-options-variation-selector-attribute - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/add-to-cart-with-options-variation-selector - **Parent:** - **Supports:** interactivity, ~~inserter~~ - **Attributes:** ## Variation Selector: Attribute Name (Beta) - woocommerce/add-to-cart-with-options-variation-selector-attribute-name Format the name of an attribute associated with a variable product. - **Name:** woocommerce/add-to-cart-with-options-variation-selector-attribute-name - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/add-to-cart-with-options-variation-selector-attribute - **Parent:** - **Supports:** color (background, gradients, text), interactivity, spacing (padding), typography (fontSize, lineHeight), ~~alignWide~~, ~~align~~, ~~inserter~~ - **Attributes:** ## Variation Selector: Attribute Options (Beta) - woocommerce/add-to-cart-with-options-variation-selector-attribute-options Display the attribute options associated with a variable product. - **Name:** woocommerce/add-to-cart-with-options-variation-selector-attribute-options - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/add-to-cart-with-options-variation-selector-attribute - **Parent:** - **Supports:** interactivity, ~~inserter~~ - **Attributes:** style ## Variation Selector (Beta) - woocommerce/add-to-cart-with-options-variation-selector Display any product variations available to select from and add to cart. - **Name:** woocommerce/add-to-cart-with-options-variation-selector - **Category:** woocommerce-product-elements - **Ancestor:** woocommerce/add-to-cart-with-options - **Parent:** - **Supports:** interactivity - **Attributes:** ## Filter by Attribute Controls - woocommerce/attribute-filter Enable customers to filter the product grid by selecting one or more attributes, such as color. - **Name:** woocommerce/attribute-filter - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** color (text, ~~background~~), ~~html~~, ~~inserter~~, ~~interactivity~~, ~~lock~~ - **Attributes:** attributeId, className, displayStyle, headingLevel, isPreview, queryType, selectType, showCounts, showFilterButton ## Store Breadcrumbs - woocommerce/breadcrumbs Enable customers to keep track of their location within the store and navigate back to parent pages. - **Name:** woocommerce/breadcrumbs - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), color (link, text, ~~background~~), interactivity (clientNavigation), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** align, contentJustification, fontSize ## Accepted Payment Methods - woocommerce/cart-accepted-payment-methods-block Display accepted payment methods. - **Name:** woocommerce/cart-accepted-payment-methods-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/cart-totals-block - **Supports:** inserter, ~~align~~, ~~html~~, ~~multiple~~, ~~reusable~~ - **Attributes:** ## Cart Cross-Sells - woocommerce/cart-cross-sells-block Shows the Cross-Sells block. - **Name:** woocommerce/cart-cross-sells-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/cart-items-block - **Supports:** inserter, ~~align~~, ~~html~~, ~~multiple~~, ~~reusable~~ - **Attributes:** ## Cart Cross-Sells Products - woocommerce/cart-cross-sells-products-block Shows the Cross-Sells products. - **Name:** woocommerce/cart-cross-sells-products-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/cart-cross-sells-block - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** columns, lock ## Express Checkout - woocommerce/cart-express-payment-block Allow customers to breeze through with quick payment options. - **Name:** woocommerce/cart-express-payment-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/cart-totals-block - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** buttonBorderRadius, buttonHeight, lock, showButtonStyles ## Cart Items - woocommerce/cart-items-block Column containing cart items. - **Name:** woocommerce/cart-items-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/filled-cart-block - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Cart Line Items - woocommerce/cart-line-items-block Block containing current line items in Cart. - **Name:** woocommerce/cart-line-items-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/cart-items-block - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Order Summary - woocommerce/cart-order-summary-block Show customers a summary of their order. - **Name:** woocommerce/cart-order-summary-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/cart-totals-block - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Coupon Form - woocommerce/cart-order-summary-coupon-form-block Shows the apply coupon form. - **Name:** woocommerce/cart-order-summary-coupon-form-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/cart-order-summary-block - **Supports:** ~~align~~, ~~html~~, ~~multiple~~, ~~reusable~~ - **Attributes:** className, lock ## Discount - woocommerce/cart-order-summary-discount-block Shows the cart discount row. - **Name:** woocommerce/cart-order-summary-discount-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/cart-order-summary-totals-block - **Supports:** ~~align~~, ~~html~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** className, lock ## Fees - woocommerce/cart-order-summary-fee-block Shows the cart fee row. - **Name:** woocommerce/cart-order-summary-fee-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/cart-order-summary-totals-block - **Supports:** ~~align~~, ~~html~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** className, lock ## Heading - woocommerce/cart-order-summary-heading-block Shows the heading row. - **Name:** woocommerce/cart-order-summary-heading-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/cart-order-summary-block - **Supports:** ~~align~~, ~~html~~, ~~multiple~~, ~~reusable~~ - **Attributes:** className, content, lock ## Shipping - woocommerce/cart-order-summary-shipping-block Shows the cart shipping row. - **Name:** woocommerce/cart-order-summary-shipping-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/cart-order-summary-totals-block - **Supports:** ~~align~~, ~~html~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Subtotal - woocommerce/cart-order-summary-subtotal-block Shows the cart subtotal row. - **Name:** woocommerce/cart-order-summary-subtotal-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/cart-order-summary-totals-block - **Supports:** ~~align~~, ~~html~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** className, lock ## Taxes - woocommerce/cart-order-summary-taxes-block Shows the cart taxes row. - **Name:** woocommerce/cart-order-summary-taxes-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/cart-order-summary-totals-block - **Supports:** ~~align~~, ~~html~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** className, lock ## Totals - woocommerce/cart-order-summary-totals-block Shows the subtotal, fees, discounts, shipping and taxes. - **Name:** woocommerce/cart-order-summary-totals-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/cart-order-summary-block - **Supports:** ~~align~~, ~~html~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** className, lock ## Cart Totals - woocommerce/cart-totals-block Column containing the cart totals. - **Name:** woocommerce/cart-totals-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/filled-cart-block - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** checkbox, lock, text ## Empty Cart - woocommerce/empty-cart-block Contains blocks that are displayed when the cart is empty. - **Name:** woocommerce/empty-cart-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/cart - **Supports:** align (wide), ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Filled Cart - woocommerce/filled-cart-block Contains blocks that are displayed when the cart contains products. - **Name:** woocommerce/filled-cart-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/cart - **Supports:** align (wide), ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Proceed to Checkout - woocommerce/proceed-to-checkout-block Allow customers proceed to Checkout. - **Name:** woocommerce/proceed-to-checkout-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/cart-totals-block - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Cart Link - woocommerce/cart-link Display a link to the cart. - **Name:** woocommerce/cart-link - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** color (background, link, ~~text~~), interactivity (clientNavigation), spacing (padding), typography (fontSize), ~~html~~, ~~multiple~~ - **Attributes:** cartIcon, content, isPreview ## Catalog Sorting - woocommerce/catalog-sorting Enable customers to change the sorting order of the products. - **Name:** woocommerce/catalog-sorting - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** color (text, ~~background~~), interactivity (clientNavigation), typography (fontSize) - **Attributes:** fontSize, useLabel ## Checkout - woocommerce/checkout Display a checkout form so your customers can submit orders. - **Name:** woocommerce/checkout - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (wide), ~~html~~, ~~multiple~~ - **Attributes:** align, isPreview, showFormStepNumbers ## Actions - woocommerce/checkout-actions-block Allow customers to place their order. - **Name:** woocommerce/checkout-actions-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-fields-block - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** cartPageId, className, lock, priceSeparator, showReturnToCart ## Additional information - woocommerce/checkout-additional-information-block Render additional fields in the 'Additional information' location. - **Name:** woocommerce/checkout-additional-information-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-fields-block - **Supports:** ~~align~~, ~~html~~, ~~multiple~~, ~~reusable~~ - **Attributes:** className, lock ## Billing Address - woocommerce/checkout-billing-address-block Collect your customer's billing address. - **Name:** woocommerce/checkout-billing-address-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-fields-block - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Contact Information - woocommerce/checkout-contact-information-block Collect your customer's contact information. - **Name:** woocommerce/checkout-contact-information-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-fields-block - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Express Checkout - woocommerce/checkout-express-payment-block Allow customers to breeze through with quick payment options. - **Name:** woocommerce/checkout-express-payment-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-fields-block - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** buttonBorderRadius, buttonHeight, className, lock, showButtonStyles ## Checkout Fields - woocommerce/checkout-fields-block Column containing checkout address fields. - **Name:** woocommerce/checkout-fields-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** className, lock ## Order Note - woocommerce/checkout-order-note-block Allow customers to add a note to their order. - **Name:** woocommerce/checkout-order-note-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-fields-block - **Supports:** ~~align~~, ~~html~~, ~~multiple~~, ~~reusable~~ - **Attributes:** className, lock ## Order Summary - woocommerce/checkout-order-summary-block Show customers a summary of their order. - **Name:** woocommerce/checkout-order-summary-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-totals-block - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Cart Items - woocommerce/checkout-order-summary-cart-items-block Shows cart items. - **Name:** woocommerce/checkout-order-summary-cart-items-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-order-summary-block - **Supports:** ~~align~~, ~~html~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** className, disableProductDescriptions, lock ## Coupon Form - woocommerce/checkout-order-summary-coupon-form-block Shows the apply coupon form. - **Name:** woocommerce/checkout-order-summary-coupon-form-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-order-summary-block - **Supports:** ~~align~~, ~~html~~, ~~multiple~~, ~~reusable~~ - **Attributes:** className, lock ## Discount - woocommerce/checkout-order-summary-discount-block Shows the cart discount row. - **Name:** woocommerce/checkout-order-summary-discount-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-order-summary-totals-block - **Supports:** ~~align~~, ~~html~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** className, lock ## Fees - woocommerce/checkout-order-summary-fee-block Shows the cart fee row. - **Name:** woocommerce/checkout-order-summary-fee-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-order-summary-totals-block - **Supports:** ~~align~~, ~~html~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** className, lock ## Shipping - woocommerce/checkout-order-summary-shipping-block Shows the cart shipping row. - **Name:** woocommerce/checkout-order-summary-shipping-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-order-summary-totals-block - **Supports:** ~~align~~, ~~html~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Subtotal - woocommerce/checkout-order-summary-subtotal-block Shows the cart subtotal row. - **Name:** woocommerce/checkout-order-summary-subtotal-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-order-summary-totals-block - **Supports:** ~~align~~, ~~html~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** className, lock ## Taxes - woocommerce/checkout-order-summary-taxes-block Shows the cart taxes row. - **Name:** woocommerce/checkout-order-summary-taxes-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-order-summary-totals-block - **Supports:** ~~align~~, ~~html~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** className, lock ## Totals - woocommerce/checkout-order-summary-totals-block Shows the subtotal, fees, discounts, shipping and taxes. - **Name:** woocommerce/checkout-order-summary-totals-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-order-summary-block - **Supports:** ~~align~~, ~~html~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** className, lock ## Payment Options - woocommerce/checkout-payment-block Payment options for your store. - **Name:** woocommerce/checkout-payment-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-fields-block - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Pickup Method - woocommerce/checkout-pickup-options-block Shows local pickup locations. - **Name:** woocommerce/checkout-pickup-options-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-fields-block - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Shipping Address - woocommerce/checkout-shipping-address-block Collect your customer's shipping address. - **Name:** woocommerce/checkout-shipping-address-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-fields-block - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Delivery - woocommerce/checkout-shipping-method-block Select between shipping or local pickup. - **Name:** woocommerce/checkout-shipping-method-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-fields-block - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Shipping Options - woocommerce/checkout-shipping-methods-block Display shipping options and rates for your store. - **Name:** woocommerce/checkout-shipping-methods-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-fields-block - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Terms and Conditions - woocommerce/checkout-terms-block Ensure that customers agree to your Terms & Conditions and Privacy Policy. - **Name:** woocommerce/checkout-terms-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout-fields-block - **Supports:** ~~align~~, ~~html~~, ~~multiple~~, ~~reusable~~ - **Attributes:** checkbox, className, showSeparator, text ## Checkout Totals - woocommerce/checkout-totals-block Column containing the checkout totals. - **Name:** woocommerce/checkout-totals-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/checkout - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** checkbox, className, text ## Classic Shortcode - woocommerce/classic-shortcode Renders classic WooCommerce shortcodes. - **Name:** woocommerce/classic-shortcode - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align, inserter, interactivity (~~clientNavigation~~), ~~html~~, ~~multiple~~, ~~reusable~~ - **Attributes:** align, shortcode ## Coming Soon - woocommerce/coming-soon - **Name:** woocommerce/coming-soon - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** color (background, text), ~~inserter~~ - **Attributes:** color, comingSoonPatternId, storeOnly ## Customer account - woocommerce/customer-account A block that allows your customers to log in and out of their accounts in your store. - **Name:** woocommerce/customer-account - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align, color (background, text), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize) - **Attributes:** displayStyle, iconClass, iconStyle ## Featured Category - woocommerce/featured-category Visually highlight a product category and encourage prompt action. - **Name:** woocommerce/featured-category - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), ariaLabel, color (background, text), interactivity (clientNavigation), spacing (padding), ~~html~~ - **Attributes:** alt, categoryId, contentAlign, dimRatio, editMode, focalPoint, hasParallax, imageFit, isRepeated, linkText, mediaId, mediaSrc, minHeight, overlayColor, overlayGradient, previewCategory, showDesc ## Featured Product - woocommerce/featured-product Highlight a product or variation. - **Name:** woocommerce/featured-product - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), ariaLabel, color (background, text), interactivity (clientNavigation), multiple, spacing (padding), ~~html~~ - **Attributes:** alt, contentAlign, dimRatio, editMode, focalPoint, hasParallax, imageFit, isRepeated, linkText, mediaId, mediaSrc, minHeight, overlayColor, overlayGradient, previewProduct, productId, showDesc, showPrice ## Filter Block - woocommerce/filter-wrapper - **Name:** woocommerce/filter-wrapper - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** interactivity (~~clientNavigation~~), ~~inserter~~ - **Attributes:** filterType, heading ## Hand-picked Products - woocommerce/handpicked-products Display a selection of hand-picked products in a grid. - **Name:** woocommerce/handpicked-products - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), interactivity (~~clientNavigation~~), ~~html~~, ~~inserter~~ - **Attributes:** align, alignButtons, columns, contentVisibility, isPreview, orderby, products ## Mini-Cart - woocommerce/mini-cart Display a button for shoppers to quickly view their cart. - **Name:** woocommerce/mini-cart - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** typography (fontSize), ~~html~~, ~~multiple~~ - **Attributes:** addToCartBehaviour, cartAndCheckoutRenderStyle, hasHiddenPrice, iconColor, iconColorValue, isPreview, miniCartIcon, onCartClickBehaviour, priceColor, priceColorValue, productCountColor, productCountColorValue, productCountVisibility ## Empty Mini-Cart view - woocommerce/empty-mini-cart-contents-block Blocks that are displayed when the Mini-Cart is empty. - **Name:** woocommerce/empty-mini-cart-contents-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/mini-cart-contents - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Filled Mini-Cart view - woocommerce/filled-mini-cart-contents-block Contains blocks that display the content of the Mini-Cart. - **Name:** woocommerce/filled-mini-cart-contents-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/mini-cart-contents - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Mini-Cart View Cart Button - woocommerce/mini-cart-cart-button-block Block that displays the cart button when the Mini-Cart has products. - **Name:** woocommerce/mini-cart-cart-button-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/mini-cart-footer-block - **Supports:** color (background, text), inserter, ~~align~~, ~~html~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Mini-Cart Proceed to Checkout Button - woocommerce/mini-cart-checkout-button-block Block that displays the checkout button when the Mini-Cart has products. - **Name:** woocommerce/mini-cart-checkout-button-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/mini-cart-footer-block - **Supports:** color (background, text), inserter, ~~align~~, ~~html~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Mini-Cart Footer - woocommerce/mini-cart-footer-block Block that displays the footer of the Mini-Cart block. - **Name:** woocommerce/mini-cart-footer-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/filled-mini-cart-contents-block - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Mini-Cart Items - woocommerce/mini-cart-items-block Contains the products table and other custom blocks of filled mini-cart. - **Name:** woocommerce/mini-cart-items-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/filled-mini-cart-contents-block - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Mini-Cart Products Table - woocommerce/mini-cart-products-table-block Block that displays the products table of the Mini-Cart block. - **Name:** woocommerce/mini-cart-products-table-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/mini-cart-items-block - **Supports:** ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Mini-Cart Shopping Button - woocommerce/mini-cart-shopping-button-block Block that displays the shopping button when the Mini-Cart is empty. - **Name:** woocommerce/mini-cart-shopping-button-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/empty-mini-cart-contents-block - **Supports:** color (background, text), inserter, ~~align~~, ~~html~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Mini-Cart Title - woocommerce/mini-cart-title-block Block that displays the title of the Mini-Cart block. - **Name:** woocommerce/mini-cart-title-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/filled-mini-cart-contents-block - **Supports:** color (text, ~~background~~), typography (fontSize), ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** lock ## Mini-Cart Title Items Counter - woocommerce/mini-cart-title-items-counter-block Block that displays the items counter part of the Mini-Cart Title block. - **Name:** woocommerce/mini-cart-title-items-counter-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/mini-cart-title-block - **Supports:** color (background, text), spacing (padding), typography (fontSize), ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** ## Mini-Cart Title Label - woocommerce/mini-cart-title-label-block Block that displays the 'Your cart' part of the Mini-Cart Title block. - **Name:** woocommerce/mini-cart-title-label-block - **Category:** woocommerce - **Ancestor:** - **Parent:** woocommerce/mini-cart-title-block - **Supports:** color (background, text), spacing (padding), typography (fontSize), ~~align~~, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~, ~~reusable~~ - **Attributes:** label ## Additional Field List - woocommerce/order-confirmation-additional-fields Display the list of additional field values from the current order. - **Name:** woocommerce/order-confirmation-additional-fields - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), spacing (margin, padding), ~~html~~, ~~multiple~~ - **Attributes:** align, className ## Additional Fields - woocommerce/order-confirmation-additional-fields-wrapper Display additional checkout fields from the 'contact' and 'order' locations. - **Name:** woocommerce/order-confirmation-additional-fields-wrapper - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), spacing (margin, padding), ~~html~~, ~~multiple~~ - **Attributes:** heading ## Additional Information - woocommerce/order-confirmation-additional-information Displays additional information provided by third-party extensions for the current order. - **Name:** woocommerce/order-confirmation-additional-information - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), spacing (margin, padding), ~~html~~, ~~multiple~~ - **Attributes:** align, className ## Billing Address - woocommerce/order-confirmation-billing-address Display the order confirmation billing address. - **Name:** woocommerce/order-confirmation-billing-address - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), color (background, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~, ~~inserter~~, ~~multiple~~ - **Attributes:** align ## Billing Address Section - woocommerce/order-confirmation-billing-wrapper Display the order confirmation billing section. - **Name:** woocommerce/order-confirmation-billing-wrapper - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), spacing (margin, padding), ~~html~~, ~~multiple~~ - **Attributes:** heading ## Account Creation - woocommerce/order-confirmation-create-account Allow customers to create an account after their purchase. - **Name:** woocommerce/order-confirmation-create-account - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), color (background, button, text), spacing (margin, padding), ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~ - **Attributes:** align, className, customerEmail, hasDarkControls, lock, nonceToken ## Order Downloads - woocommerce/order-confirmation-downloads Display links to purchased downloads. - **Name:** woocommerce/order-confirmation-downloads - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~, ~~multiple~~ - **Attributes:** align, className ## Downloads Section - woocommerce/order-confirmation-downloads-wrapper Display the downloadable products section. - **Name:** woocommerce/order-confirmation-downloads-wrapper - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), spacing (margin, padding), ~~html~~, ~~multiple~~ - **Attributes:** heading ## Shipping Address - woocommerce/order-confirmation-shipping-address Display the order confirmation shipping address. - **Name:** woocommerce/order-confirmation-shipping-address - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), color (background, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~, ~~inserter~~, ~~multiple~~ - **Attributes:** align ## Shipping Address Section - woocommerce/order-confirmation-shipping-wrapper Display the order confirmation shipping section. - **Name:** woocommerce/order-confirmation-shipping-wrapper - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), spacing (margin, padding), ~~html~~, ~~multiple~~ - **Attributes:** heading ## Order Status - woocommerce/order-confirmation-status Display a "thank you" message, or a sentence regarding the current order status. - **Name:** woocommerce/order-confirmation-status - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), color (background, gradients, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~, ~~multiple~~ - **Attributes:** align, className ## Order Summary - woocommerce/order-confirmation-summary Display the order summary on the order confirmation page. - **Name:** woocommerce/order-confirmation-summary - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), color (background, gradients, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~, ~~multiple~~ - **Attributes:** align, className ## Order Totals - woocommerce/order-confirmation-totals Display the items purchased and order totals. - **Name:** woocommerce/order-confirmation-totals - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~, ~~multiple~~ - **Attributes:** align, className ## Order Totals Section - woocommerce/order-confirmation-totals-wrapper Display the order details section. - **Name:** woocommerce/order-confirmation-totals-wrapper - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), spacing (margin, padding), ~~html~~, ~~multiple~~ - **Attributes:** heading ## WooCommerce Page - woocommerce/page-content-wrapper Displays WooCommerce page content. - **Name:** woocommerce/page-content-wrapper - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** ~~html~~, ~~inserter~~, ~~multiple~~ - **Attributes:** page ## Filter by Price Controls - woocommerce/price-filter Enable customers to filter the product grid by choosing a price range. - **Name:** woocommerce/price-filter - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** color (text, ~~background~~), interactivity (~~clientNavigation~~), ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~ - **Attributes:** className, headingLevel, inlineInput, showFilterButton, showInputFields ## Best Selling Products - woocommerce/product-best-sellers Display a grid of your all-time best selling products. - **Name:** woocommerce/product-best-sellers - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), interactivity (~~clientNavigation~~), ~~html~~, ~~inserter~~ - **Attributes:** alignButtons, catOperator, categories, columns, contentVisibility, editMode, isPreview, orderby, rows, stockStatus ## Product Categories List - woocommerce/product-categories Show all product categories as a list or dropdown. - **Name:** woocommerce/product-categories - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), color (link, text, ~~background~~), interactivity (clientNavigation), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** align, hasCount, hasEmpty, hasImage, isDropdown, isHierarchical, showChildrenOnly ## Products by Category - woocommerce/product-category Display a grid of products from your selected categories. - **Name:** woocommerce/product-category - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), interactivity (~~clientNavigation~~), ~~html~~, ~~inserter~~ - **Attributes:** alignButtons, catOperator, categories, columns, contentVisibility, editMode, isPreview, orderby, rows, stockStatus ## Product Collection - woocommerce/product-collection Display a collection of products from your store. - **Name:** woocommerce/product-collection - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), anchor, interactivity, ~~html~~ - **Attributes:** __privatePreviewState, collection, convertedFromProducts, dimensions, displayLayout, forcePageReload, hideControls, query, queryContextIncludes, queryId, tagName ## No results - woocommerce/product-collection-no-results The contents of this block will display when there are no products found. - **Name:** woocommerce/product-collection-no-results - **Category:** woocommerce - **Ancestor:** woocommerce/product-collection - **Parent:** - **Supports:** align, color (background, gradients, link, text), interactivity (clientNavigation), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ - **Attributes:** ## Product Description - woocommerce/product-description Displays the description of the product. - **Name:** woocommerce/product-description - **Category:** woocommerce - **Ancestor:** woocommerce/single-product, woocommerce/product-template, core/post-template - **Parent:** - **Supports:** align (full, wide), background (backgroundImage, backgroundSize), color (background, gradients, heading, link, text), dimensions (minHeight), interactivity (clientNavigation), layout, spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** ## Product Details - woocommerce/product-details Display a product's description, attributes, and reviews - **Name:** woocommerce/product-details - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), interactivity (clientNavigation) - **Attributes:** ## Add to Cart with Options - woocommerce/add-to-cart-form Display a button that lets customers add a product to their cart. Use the added options to optimize for different product types. - **Name:** woocommerce/add-to-cart-form - **Category:** woocommerce-product-elements - **Ancestor:** - **Parent:** - **Supports:** interactivity - **Attributes:** quantitySelectorStyle ## Product Filters - woocommerce/product-filters Let shoppers filter products displayed on the page. - **Name:** woocommerce/product-filters - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align, color (background, button, heading, text, ~~enableContrastChecker~~), inserter, interactivity, layout (default, ~~allowEditing~~), multiple, spacing (blockGap), typography (fontSize) - **Attributes:** isPreview ## Active - woocommerce/product-filter-active Display the currently active filters. - **Name:** woocommerce/product-filter-active - **Category:** woocommerce - **Ancestor:** woocommerce/product-filters - **Parent:** - **Supports:** interactivity, spacing (margin, padding, ~~blockGap~~) - **Attributes:** ## Attribute - woocommerce/product-filter-attribute Enable customers to filter the product grid by selecting one or more attributes, such as color. - **Name:** woocommerce/product-filter-attribute - **Category:** woocommerce - **Ancestor:** woocommerce/product-filters - **Parent:** - **Supports:** color (text, ~~background~~), interactivity, spacing (blockGap, margin, padding), typography (fontSize, lineHeight) - **Attributes:** attributeId, displayStyle, hideEmpty, isPreview, queryType, selectType, showCounts, sortOrder ## List - woocommerce/product-filter-checkbox-list Display a list of filter options. - **Name:** woocommerce/product-filter-checkbox-list - **Category:** woocommerce - **Ancestor:** woocommerce/product-filter-attribute, woocommerce/product-filter-status, woocommerce/product-filter-rating - **Parent:** - **Supports:** interactivity - **Attributes:** customLabelElement, customOptionElement, customOptionElementBorder, customOptionElementSelected, labelElement, optionElement, optionElementBorder, optionElementSelected ## Chips - woocommerce/product-filter-chips Display filter options as chips. - **Name:** woocommerce/product-filter-chips - **Category:** woocommerce - **Ancestor:** woocommerce/product-filter-attribute, woocommerce/product-filter-status - **Parent:** - **Supports:** interactivity - **Attributes:** chipBackground, chipBorder, chipText, customChipBackground, customChipBorder, customChipText, customSelectedChipBackground, customSelectedChipBorder, customSelectedChipText, selectedChipBackground, selectedChipBorder, selectedChipText ## Clear filters - woocommerce/product-filter-clear-button Allows shoppers to clear active filters. - **Name:** woocommerce/product-filter-clear-button - **Category:** woocommerce - **Ancestor:** woocommerce/product-filter-active - **Parent:** - **Supports:** inserter, interactivity - **Attributes:** ## Price - woocommerce/product-filter-price Let shoppers filter products by choosing a price range. - **Name:** woocommerce/product-filter-price - **Category:** woocommerce - **Ancestor:** woocommerce/product-filters - **Parent:** - **Supports:** interactivity, ~~html~~ - **Attributes:** ## Price Slider - woocommerce/product-filter-price-slider A slider helps shopper choose a price range. - **Name:** woocommerce/product-filter-price-slider - **Category:** woocommerce - **Ancestor:** woocommerce/product-filter-price - **Parent:** - **Supports:** color (~~background~~, ~~enableContrastChecker~~, ~~text~~), interactivity, ~~html~~ - **Attributes:** customSlider, customSliderHandle, customSliderHandleBorder, inlineInput, showInputFields, slider, sliderHandle, sliderHandleBorder ## Rating - woocommerce/product-filter-rating Enable customers to filter the product collection by rating. - **Name:** woocommerce/product-filter-rating - **Category:** woocommerce - **Ancestor:** woocommerce/product-filters - **Parent:** - **Supports:** color (text, ~~background~~), interactivity - **Attributes:** className, isPreview, minRating, showCounts ## Chips - woocommerce/product-filter-removable-chips Display removable active filters as chips. - **Name:** woocommerce/product-filter-removable-chips - **Category:** woocommerce - **Ancestor:** woocommerce/product-filter-active - **Parent:** - **Supports:** interactivity, layout (default, ~~allowInheriting~~, ~~allowSwitching~~, ~~allowVerticalAlignment~~) - **Attributes:** chipBackground, chipBorder, chipText, customChipBackground, customChipBorder, customChipText ## Status - woocommerce/product-filter-status Let shoppers filter products by choosing stock status. - **Name:** woocommerce/product-filter-status - **Category:** woocommerce - **Ancestor:** woocommerce/product-filters - **Parent:** - **Supports:** color (text, ~~background~~), interactivity, spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** displayStyle, hideEmpty, isPreview, showCounts ## Product Gallery (Beta) - woocommerce/product-gallery Showcase your products relevant images and media. - **Name:** woocommerce/product-gallery - **Category:** woocommerce - **Ancestor:** woocommerce/single-product - **Parent:** - **Supports:** align, interactivity, layout (allowEditing, allowOrientation, default, ~~allowJustification~~) - **Attributes:** fullScreenOnClick, hoverZoom ## Large Image - woocommerce/product-gallery-large-image Display the Large Image of a product. - **Name:** woocommerce/product-gallery-large-image - **Category:** woocommerce - **Ancestor:** woocommerce/product-gallery - **Parent:** - **Supports:** interactivity - **Attributes:** ## Next/Previous Buttons - woocommerce/product-gallery-large-image-next-previous Display next and previous buttons. - **Name:** woocommerce/product-gallery-large-image-next-previous - **Category:** woocommerce - **Ancestor:** woocommerce/product-gallery-large-image - **Parent:** - **Supports:** align, color (background, text), interactivity, layout (allowVerticalAlignment, default, ~~allowJustification~~, ~~allowOrientation~~), shadow, spacing (margin) - **Attributes:** ## Thumbnails - woocommerce/product-gallery-thumbnails Display the Thumbnails of a product. - **Name:** woocommerce/product-gallery-thumbnails - **Category:** woocommerce - **Ancestor:** woocommerce/product-gallery - **Parent:** - **Supports:** interactivity, spacing (margin) - **Attributes:** aspectRatio, thumbnailSize ## Newest Products - woocommerce/product-new Display a grid of your newest products. - **Name:** woocommerce/product-new - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), interactivity (~~clientNavigation~~), ~~html~~, ~~inserter~~ - **Attributes:** alignButtons, catOperator, categories, columns, contentVisibility, editMode, isPreview, orderby, rows, stockStatus ## On Sale Products - woocommerce/product-on-sale Display a grid of products currently on sale. - **Name:** woocommerce/product-on-sale - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), interactivity (~~clientNavigation~~), ~~html~~, ~~inserter~~ - **Attributes:** alignButtons, catOperator, categories, columns, contentVisibility, isPreview, orderby, rows, stockStatus ## Product Results Count - woocommerce/product-results-count Display the number of products on the archive page or search result page. - **Name:** woocommerce/product-results-count - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** color (text, ~~background~~), interactivity (clientNavigation), typography (fontSize) - **Attributes:** ## Product Reviews - woocommerce/product-reviews Display a product's reviews - **Name:** woocommerce/product-reviews - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), color (background, gradients, heading, link, text), interactivity, spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** tagName ## Review Author Name - woocommerce/product-review-author-name Displays the name of the author of the review. - **Name:** woocommerce/product-review-author-name - **Category:** woocommerce - **Ancestor:** woocommerce/product-reviews - **Parent:** - **Supports:** color (background, gradients, link, text), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** isLink, linkTarget, textAlign ## Review Content - woocommerce/product-review-content Displays the contents of a product review. - **Name:** woocommerce/product-review-content - **Category:** woocommerce - **Ancestor:** woocommerce/product-reviews - **Parent:** - **Supports:** color (background, gradients, link, text), spacing (padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** textAlign ## Review Date - woocommerce/product-review-date Displays the date on which the review was posted. - **Name:** woocommerce/product-review-date - **Category:** woocommerce - **Ancestor:** woocommerce/product-reviews - **Parent:** - **Supports:** color (background, gradients, link, text), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** format, isLink ## Reviews Form - woocommerce/product-review-form Display a product's reviews form. - **Name:** woocommerce/product-review-form - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** color (background, gradients, heading, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** textAlign ## Review Rating - woocommerce/product-review-rating Displays the rating of a product review. - **Name:** woocommerce/product-review-rating - **Category:** woocommerce - **Ancestor:** woocommerce/product-reviews - **Parent:** - **Supports:** color (background, gradients, text), interactivity (clientNavigation) - **Attributes:** textAlign ## Reviews Template - woocommerce/product-review-template Contains the block elements used to display product reviews, like the title, author, date, rating and more. - **Name:** woocommerce/product-review-template - **Category:** woocommerce - **Ancestor:** woocommerce/product-reviews - **Parent:** - **Supports:** align, interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ - **Attributes:** ## Reviews Pagination - woocommerce/product-reviews-pagination Displays a paginated navigation to next/previous set of product reviews, when applicable. - **Name:** woocommerce/product-reviews-pagination - **Category:** woocommerce - **Ancestor:** woocommerce/product-reviews - **Parent:** - **Supports:** align, color (background, gradients, link, text), interactivity (clientNavigation), layout (default, ~~allowInheriting~~, ~~allowSwitching~~), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ - **Attributes:** paginationArrow ## Reviews Next Page - woocommerce/product-reviews-pagination-next Displays the next product review's page link. - **Name:** woocommerce/product-reviews-pagination-next - **Category:** woocommerce - **Ancestor:** woocommerce/product-reviews-pagination - **Parent:** - **Supports:** color (background, gradients, ~~text~~), interactivity (clientNavigation), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ - **Attributes:** label ## Reviews Page Numbers - woocommerce/product-reviews-pagination-numbers Displays a list of page numbers for product reviews pagination. - **Name:** woocommerce/product-reviews-pagination-numbers - **Category:** woocommerce - **Ancestor:** woocommerce/product-reviews-pagination - **Parent:** - **Supports:** color (background, gradients, ~~text~~), interactivity (clientNavigation), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ - **Attributes:** ## Reviews Previous Page - woocommerce/product-reviews-pagination-previous Displays the previous product review's page link. - **Name:** woocommerce/product-reviews-pagination-previous - **Category:** woocommerce - **Ancestor:** woocommerce/product-reviews-pagination - **Parent:** - **Supports:** color (background, gradients, ~~text~~), interactivity (clientNavigation), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ - **Attributes:** label ## Reviews Title - woocommerce/product-reviews-title Displays a title with the number of reviews. - **Name:** woocommerce/product-reviews-title - **Category:** woocommerce - **Ancestor:** woocommerce/product-reviews - **Parent:** - **Supports:** align, color (background, gradients, text), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight), ~~anchor~~, ~~html~~ - **Attributes:** level, levelOptions, showProductTitle, showReviewsCount, textAlign ## Product Specifications - woocommerce/product-specifications Display product weight, dimensions, and attributes. - **Name:** woocommerce/product-specifications - **Category:** woocommerce - **Ancestor:** woocommerce/single-product, woocommerce/product-template, core/post-template - **Parent:** - **Supports:** align (full, wide), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** showAttributes, showDimensions, showWeight ## Products by Tag - woocommerce/product-tag Display a grid of products with selected tags. - **Name:** woocommerce/product-tag - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), interactivity (~~clientNavigation~~), ~~html~~, ~~inserter~~ - **Attributes:** alignButtons, columns, contentVisibility, isPreview, orderby, rows, stockStatus, tagOperator, tags ## Product Template - woocommerce/product-template Contains the block elements used to render a product. - **Name:** woocommerce/product-template - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), anchor, color (background, gradients, link, text), interactivity, typography (fontSize, lineHeight), ~~html~~, ~~inserter~~, ~~reusable~~ - **Attributes:** ## Top Rated Products - woocommerce/product-top-rated Display a grid of your top rated products. - **Name:** woocommerce/product-top-rated - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), interactivity (~~clientNavigation~~), ~~html~~, ~~inserter~~ - **Attributes:** alignButtons, catOperator, categories, columns, contentVisibility, editMode, isPreview, orderby, rows, stockStatus ## All Products - woocommerce/all-products Display products from your store in a grid layout. - **Name:** woocommerce/all-products - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), interactivity (~~clientNavigation~~), ~~html~~, ~~inserter~~, ~~multiple~~ - **Attributes:** alignButtons, columns, contentVisibility, isPreview, layoutConfig, orderby, rows ## Products by Attribute - woocommerce/products-by-attribute Display a grid of products with selected attributes. - **Name:** woocommerce/products-by-attribute - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), interactivity (~~clientNavigation~~), ~~html~~, ~~inserter~~ - **Attributes:** alignButtons, attrOperator, attributes, columns, contentVisibility, isPreview, orderby, rows, stockStatus ## Filter by Rating Controls - woocommerce/rating-filter Enable customers to filter the product grid by rating. - **Name:** woocommerce/rating-filter - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** color (background, button, text), interactivity (~~clientNavigation~~), ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~ - **Attributes:** className, displayStyle, isPreview, selectType, showCounts, showFilterButton ## All Reviews - woocommerce/all-reviews Show a list of all product reviews. - **Name:** woocommerce/all-reviews - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** color (text, ~~background~~), interactivity (clientNavigation), typography (fontSize), ~~html~~ - **Attributes:** ## Reviews by Category - woocommerce/reviews-by-category Show product reviews from specific categories. - **Name:** woocommerce/reviews-by-category - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** color (text, ~~background~~), interactivity (clientNavigation), typography (fontSize), ~~html~~ - **Attributes:** ## Reviews by Product - woocommerce/reviews-by-product Display reviews for your products. - **Name:** woocommerce/reviews-by-product - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** color (text, ~~background~~), interactivity (clientNavigation), typography (fontSize), ~~html~~ - **Attributes:** ## Product - woocommerce/single-product Display a single product of your choice with full control over its presentation. - **Name:** woocommerce/single-product - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), interactivity - **Attributes:** isPreview, productId ## Filter by Stock Controls - woocommerce/stock-filter Enable customers to filter the product grid by stock status. - **Name:** woocommerce/stock-filter - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** color (background, button, text), interactivity (~~clientNavigation~~), ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~ - **Attributes:** className, displayStyle, headingLevel, isPreview, selectType, showCounts, showFilterButton ## Store Notices - woocommerce/store-notices Display shopper-facing notifications generated by WooCommerce or extensions. - **Name:** woocommerce/store-notices - **Category:** woocommerce - **Ancestor:** - **Parent:** - **Supports:** align (full, wide), interactivity (clientNavigation), ~~multiple~~ - **Attributes:** align --- ## Additional checkout fields *Source: block-development/cart-and-checkout-blocks/additional-checkout-fields.md* # Additional checkout fields A common use-case for developers and merchants is to add a new field to the Checkout form to collect additional data about a customer or their order. This document will outline the steps an extension should take to register some additional checkout fields. ## Available field locations Additional checkout fields can be registered in three different places: | Title | Identifier | | ------------------------------------ | ---------- | | Contact information | **`contact`** | | Addresses (Shipping **and** Billing) | **`address`** | | Order information | **`order`** | A field can only be shown in one location, it is not possible to render the same field in multiple locations in the same registration. ### Contact information The contact information section currently renders at the top of the form. It contains the `email` field and any other additional fields. ![Showing the contact information section with two fields rendered, email and an additional checkout field (optional)](https://github.com/woocommerce/woocommerce/assets/5656702/097c2596-c629-4eab-9604-577ee7a14cfe) Fields rendered here will be saved to the shopper's account. They will be visible and editable render in the shopper's "Account details" section. ### Address The "Address" section currently contains a form for the shipping address and the billing address. Additional checkout fields can be registered to appear within these forms. ![The shipping address form showing the additional checkout field at the bottom](https://github.com/woocommerce/woocommerce/assets/5656702/746d280f-3354-4d37-a78a-a2518eb0e5de) Fields registered here will be saved to both the customer _and_ the order, so returning customers won't need to refill those values again. If a field is registered in the `address` location it will appear in both the shipping **and** the billing address. It is not possible to have the field in only one of the addresses. You will also end up collecting two values for this field, one for shipping and one for billing. ### Order information As part of the additional checkout fields feature, the checkout block has a new inner block called the "Order information block". This block is used to render fields that aren't part of the contact information or address information, for example it may be a "How did you hear about us" field or a "Gift message" field. Fields rendered here will be saved to the order. They will not be part of the customer's saved address or account information. New orders will not have any previously used values pre-filled. ![The order information section containing an additional checkout field](https://github.com/woocommerce/woocommerce/assets/5656702/295b3048-a22a-4225-96b0-6b0371a7cd5f) By default, this block will render as the last step in the Checkout form, however it can be moved using the Gutenberg block controls in the editor. ![The order information block in the post editor"](https://github.com/woocommerce/woocommerce/assets/5656702/05a3d7d9-b3af-4445-9318-443ae2c4d7d8) ## Accessing values Additional fields are saved to individual meta keys in both the customer meta and order meta, you can access them using helper methods, or using the meta keys directly, we recommend using the helper methods, as they're less likely to change, can handle future migrations, and will support future enhancements (e.g. reading from different locations). For address fields, two values are saved: one for shipping, and one for billing. If the customer has selected 'Use same address for billing` then the values will be the same, but still saved independently of each other. For contact and order fields, only one value is saved per field. ### Helper methods `CheckoutFields` provides a function to access values from both customers and orders, it's are `get_field_from_object`. To access a customer billing and/or shipping value: ```php use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields; $field_id = 'my-plugin-namespace/my-field'; $customer = wc()->customer; // Or new WC_Customer( $id ) $checkout_fields = Package::container()->get( CheckoutFields::class ); $my_customer_billing_field = $checkout_fields->get_field_from_object( $field_id, $customer, 'billing' ); $my_customer_shipping_field = $checkout_fields->get_field_from_object( $field_id, $customer, 'shipping' ); ``` To access an order field: ```php use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields; $field_id = 'my-plugin-namespace/my-field'; $order = wc_get_order( 1234 ); $checkout_fields = Package::container()->get( CheckoutFields::class ); $my_order_billing_field = $checkout_fields->get_field_from_object( $field_id, $order, 'billing' ); $my_order_shipping_field = $checkout_fields->get_field_from_object( $field_id, $order, 'shipping' ); ``` After an order is placed, the data saved to the customer and the data saved to the order will be the same. Customers can change the values for _future_ orders, or from within their My Account page. If you're looking at a customer value at a specific point in time (i.e. when the order was placed), access it from the order object, if you're looking for the most up to date value regardless, access it from the customer object. #### Guest customers When a guest customer places an order with additional fields, those fields will be saved to its session, so as long as the customer still has a valid session going, the values will always be there. #### Logged-in customers For logged-in customers, the value is only persisted once they place an order. Accessing a logged-in customer object during the place order lifecycle will return null or stale data. If you're at a place order hook, doing this will return previous data (not the currently inserted one): ```php $customer = new WC_Customer( $order->customer_id ); // Or new WC_Customer( 1234 ) $my_customer_billing_field = $checkout_fields->get_field_from_object( $field_id, $customer, 'billing' ); ``` Instead, always access the latest data if you want to run some extra validation/data-moving: ```php $customer = wc()->customer // This will return the current customer with its session. $my_customer_billing_field = $checkout_fields->get_field_from_object( $field_id, $customer, 'billing' ); ``` #### Accessing all fields You can use `get_all_fields_from_object` to access all additional fields saved to an order or a customer. ```php use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields; $order = wc_get_order( 1234 ); $checkout_fields = Package::container()->get( CheckoutFields::class ); $order_additional_billing_fields = $checkout_fields->get_all_fields_from_object( $order, 'billing' ); $order_additional_shipping_fields = $checkout_fields->get_all_fields_from_object( $order, 'shipping' ); $order_other_additional_fields = $checkout_fields->get_all_fields_from_object( $order, 'other' ); // Contact and Order are saved in the same place under the additional group. ``` This will return an array of all values, it will only include fields currently registered, if you want to include fields no longer registered, you can pass a third `true` parameter. ```php $order = wc_get_order( 1234 ); $checkout_fields = Package::container()->get( CheckoutFields::class ); $order_additional_billing_fields = $checkout_fields->get_all_fields_from_object( $order, 'billing' ); // array( 'my-plugin-namespace/my-field' => 'my-value' ); $order_additional_billing_fields = $checkout_fields->get_all_fields_from_object( $order, 'billing', true ); // array( 'my-plugin-namespace/my-field' => 'my-value', 'old-namespace/old-key' => 'old-value' ); ``` ### Accessing values directly While not recommended, you can use the direct meta key to access certain values, this is useful for external engines or page/email builders who only provide access to meta values. Values are saved under a predefined prefix, this is needed to able to query fields without knowing which ID the field was registered under, for a field with key `'my-plugin-namespace/my-field'`, it's meta key will be the following if it's an address field: - `_wc_billing/my-plugin-namespace/my-field` - `_wc_shipping/my-plugin-namespace/my-field` Or the following if it's a contact/order field: - `_wc_other/my-plugin-namespace/my-field`. Those prefixes are part of `CheckoutFields` class, and can be accessed using the following constants: ```php echo ( CheckoutFields::BILLING_FIELDS_PREFIX ); // _wc_billing/ echo ( CheckoutFields::SHIPPING_FIELDS_PREFIX ); // _wc_shipping/ echo ( CheckoutFields::OTHER_FIELDS_PREFIX ); // _wc_other/ ``` `CheckoutFields` provides a couple of helpers to get the group name or key based on one or the other: ```php CheckoutFields::get_group_name( "_wc_billing" ); // "billing" CheckoutFields::get_group_name( "_wc_billing/" ); // "billing" CheckoutFields::get_group_key( "shipping" ); // "_wc_shipping/" ``` Use cases here would be to build the key name to access the meta directly: ```php $key = CheckoutFields::get_group_key( "other" ) . 'my-plugin/is-opt-in'; $opted_in = get_user_meta( 123, $key, true ) === "1" ? true : false; ``` #### Checkboxes values When accessing a checkbox values directly, it will either return `"1"` for true, `"0"` for false, or `""` if the value doesn't exist, only the provided functions will sanitize that to a boolean. ## Supported field types The following field types are supported: - `select` - `text` - `checkbox` There are plans to expand this list, but for now these are the types available. ## Using the API To register additional checkout fields you must use the `woocommerce_register_additional_checkout_field` function. It is recommended to run this function after the `woocommerce_init` action. The registration function takes an array of options describing your field. Some field types take additional options. ### Options #### General options These options apply to all field types (except in a few circumstances which are noted inline). | Option name | Description | Required? | Example | Default value | |---------------------|-------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `id` | The field's ID. This should be a unique identifier for your field. It is composed of a namespace and field name separated by a `/`. | Yes | `plugin-namespace/how-did-you-hear` | No default - this must be provided. | | `label` | The label shown on your field. This will be the placeholder too. | Yes | `How did you hear about us?` | No default - this must be provided. | | `optionalLabel` | The label shown on your field if it is optional. This will be the placeholder too. | No | `How did you hear about us? (Optional)` | The default value will be the value of `label` with `(optional)` appended. | | `location` | The location to render your field. | Yes | `contact`, `address`, or `order` | No default - this must be provided. | | `type` | The type of field you're rendering. It defaults to `text` and must match one of the supported field types. | No | `text`, `select`, or `checkbox` | `text` | | `attributes` | An array of additional attributes to render on the field's input element. This is _not_ supported for `select` fields. | No | `[ 'data-custom-data' => 'my-custom-data' ]` | `[]` | | `required` | Can be a boolean or a JSON Schema array. If boolean and `true`, the shopper _must_ provide a value for this field during the checkout process. For checkbox fields, the shopper must check the box to place the order. If a JSON Schema array, the field will be required based on the schema conditions. See [Conditional visibility and validation via JSON Schema](#conditional-visibility-and-validation-via-json-schema). | No | `true` or `["type" => "object", "properties" => [...]]` | `false` | | `hidden` | Can be a boolean or a JSON Schema array. Must be `false` when used as a boolean. If a JSON Schema array, the field will be hidden based on the schema conditions. See [Conditional visibility and validation via JSON Schema](#conditional-visibility-and-validation-via-json-schema). | No | `false` or `["type" => "object", "properties" => [...]]` | `false` | | `validation` | An array of JSON Schema objects that define validation rules for the field. See [Conditional visibility and validation via JSON Schema](#conditional-visibility-and-validation-via-json-schema). | No | `[{"type": "object", "properties": {...}}]` | `[]` | | `sanitize_callback` | A function called to sanitize the customer provided value when posted. | No | See example below | By default the field's value is returned unchanged. | | `validate_callback` | A function called to validate the customer provided value when posted. This runs _after_ sanitization. | No | See example below | The default validation function will add an error to the response if the field is required and does not have a value. [See the default validation function.](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Blocks/Domain/Services/CheckoutFields.php#L270-L281) | ##### Example of `sanitize_callback`. This function will remove spaces from the value ```php 'sanitize_callback' => function( $field_value ) { return str_replace( ' ', '', $field_value ); }, ``` ##### Example of `validate_callback`. This function will check if the value is an email ```php 'validate_callback' => function( $field_value ) { if ( ! is_email( $field_value ) ) { return new WP_Error( 'invalid_alt_email', 'Please ensure your alternative email matches the correct format.' ); } }, ``` #### Options for `text` fields Text fields don't have any additional options beyond the general options listed above. #### Options for `select` fields As well as the options above, select fields must also be registered with an `options` option. This is used to specify what options the shopper can select. Select fields will mount with no value selected by default, if the field is required, the user will be required to select a value. You can set a placeholder to be shown on the select by passing a `placeholder` value when registering the field. This will be the first option in the select and will not be selectable if the field is required. | Option name | Description | Required? | Example | Default value | |-----|-----|-----|----------------|--------------| | `options` | An array of options to show in the select input. Each options must be an array containing a `label` and `value` property. Each entry must have a unique `value`. Any duplicate options will be removed. The `value` is what gets submitted to the server during checkout and the `label` is simply a user-friendly representation of this value. It is not transmitted to the server in any way. | Yes | see below | No default - this must be provided. | | `placeholder` | If this value is set, the shopper will see this option in the select. If the select is required, the shopper cannot select this option. | No | `Select a role` | Select a $label | ##### Example of `options` value ```php [ [ 'value' => 'store_1', 'label' => 'Our London Store' ], [ 'value' => 'store_2', 'label' => 'Our Paris Store' ], [ 'value' => 'store_3', 'label' => 'Our New York Store' ] ] ``` #### Options for `checkbox` fields As well as the options above, checkbox field support showing an error message if it's required and not checked. | Option name | Description | Required? | Example | Default value | |-----------------|------------------------------------------------------------------------------|-----------|--------------------------------------------------------------|---| | `error_message` | A custom message to show if the box is unchecked. | No | `You must confirm you are over 18 before placing the order.` | `Please check this box if you want to proceed.` | ### Attributes Adding additional attributes to checkbox and text fields is supported. Adding them to select fields is **not possible for now**. These attributes have a 1:1 mapping to the HTML attributes on `input` elements (except `pattern` on checkbox). The supported attributes are: - `data-*` attributes - `aria-*` attributes - `autocomplete` - `autocapitalize` - `pattern` (not supported on checkbox fields) - `title` - `maxLength` (equivalent to `maxlength` HTML attribute) - `readOnly` (equivalent to `readonly` HTML attribute) `maxLength` and `readOnly` are in camelCase because the attributes are rendered on a React element which must receive them in this format. Certain attributes are not passed through to the field intentionally, these are `autofocus` and `disabled`. We are welcome to hear feedback and adjust this behaviour if valid use cases are provided. ## Usage examples ### Rendering a text field This example demonstrates rendering a text field in the address section: ```php add_action( 'woocommerce_init', function() { woocommerce_register_additional_checkout_field( array( 'id' => 'namespace/gov-id', 'label' => 'Government ID', 'optionalLabel' => 'Government ID (optional)', 'location' => 'address', 'required' => true, 'attributes' => array( 'autocomplete' => 'government-id', 'aria-describedby' => 'some-element', 'aria-label' => 'custom aria label', 'pattern' => '[A-Z0-9]{5}', // A 5-character string of capital letters and numbers. 'title' => 'Title to show on hover', 'data-custom' => 'custom data', ), ), ); } ); ``` This results in the following address form (the billing form will be the same): ![The shipping address form with the Government ID field rendered at the bottom](https://github.com/woocommerce/woocommerce/assets/5656702/f6eb3c6f-9178-4978-8e74-e6b2ea353192) The rendered markup looks like this: ```html ``` ### Rendering a checkbox field This example demonstrates rendering a checkbox field in the contact information section: ```php add_action( 'woocommerce_init', function() { woocommerce_register_additional_checkout_field( array( 'id' => 'namespace/marketing-opt-in', 'label' => 'Do you want to subscribe to our newsletter?', 'location' => 'contact', 'type' => 'checkbox', ) ); } ); ``` This results in the following contact information section: ![The contact information section with a newsletter subscription checkbox rendered inside it](https://github.com/woocommerce/woocommerce/assets/5656702/7444e41a-97cc-451d-b2c9-4eedfbe05724) Note that because an `optionalLabel` was not supplied, the string `(optional)` is appended to the label. To remove that an `optionalLabel` property should be supplied to override this. ### Rendering a select field This example demonstrates rendering a select field in the order information section: ```php add_action( 'woocommerce_init', function() { woocommerce_register_additional_checkout_field( array( 'id' => 'namespace/how-did-you-hear-about-us', 'label' => 'How did you hear about us?', 'placeholder' => 'Select a source', 'location' => 'order', 'type' => 'select', 'options' => [ [ 'value' => 'google', 'label' => 'Google' ], [ 'value' => 'facebook', 'label' => 'Facebook' ], [ 'value' => 'friend', 'label' => 'From a friend' ], [ 'value' => 'other', 'label' => 'Other' ], ] ) ); } ); ``` This results in the order information section being rendered like so: ### The select input before being focused ![The select input before being focused](https://github.com/woocommerce/woocommerce/assets/5656702/bbe17ad0-7c7d-419a-951d-315f56f8898a) ### The select input when focused ![The select input when focused](https://github.com/woocommerce/woocommerce/assets/5656702/bd943906-621b-404f-aa84-b951323e25d3) If it is undesirable to force the shopper to select a value, mark the select as optional by setting the `required` option to `false`. ## Validation and sanitization It is possible to add custom validation and sanitization for additional checkout fields using WordPress action hooks. These actions happen in two places: 1. Updating and submitting the form during the checkout process and, 2. Updating address/contact information in the "My account" area. ### Sanitization Sanitization is used to ensure the value of a field is in a specific format. An example is when taking a government ID, you may want to format it so that all letters are capitalized and there are no spaces. At this point, the value should **not** be checked for _validity_. That will come later. This step is only intended to set the field up for validation. #### Using the `woocommerce_sanitize_additional_field` filter To run a custom sanitization function for a field you can use the `sanitize_callback` function on registration, or the `woocommerce_sanitize_additional_field` filter. | Argument | Type | Description | |--------------|-------------------|-------------------------------------------------------------------------| | `$field_value` | `boolean\|string` | The value of the field. | | `$field_key` | `string` | The ID of the field. This is the same ID the field was registered with. | ##### Example of sanitization This example shows how to remove whitespace and capitalize all letters in the example Government ID field we added above. ```php add_action( 'woocommerce_sanitize_additional_field', function ( $field_value, $field_key ) { if ( 'namespace/gov-id' === $field_key ) { $field_value = str_replace( ' ', '', $field_value ); $field_value = strtoupper( $field_value ); } return $field_value; }, 10, 2 ); ``` ### Validation There are two phases of validation in the additional checkout fields system. The first is validating a single field based on its key and value. #### Single field validation ##### Using the `woocommerce_validate_additional_field` action When the `woocommerce_validate_additional_field` action is fired the callback receives the field's key, the field's value, and a `WP_Error` object. To add validation errors to the response, use the [`WP_Error::add`](https://developer.wordpress.org/reference/classes/wp_error/add/) method. | Argument | Type | Description | |--------------|-------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `$errors` | `WP_Error` | An error object containing errors that were already encountered while processing the request. If no errors were added yet, it will still be a `WP_Error` object but it will be empty. | | `$field_key` | `string` | The id of the field. This is the ID the field was registered with. | | `$field_value` | `boolean\|string` | The value of the field | ###### The `WP_Error` object When adding your error to the `WP_Error` object, it should have a unique error code. You may want to prefix the error code with the plugin namespace to reduce the chance of collision. Using codes that are already in use across other plugins may result in the error message being overwritten or showing in a different location. ###### Example of single-field validation The below example shows how to apply custom validation to the `namespace/gov-id` text field from above. The code here ensures the field is made up of 5 characters, either upper-case letters or numbers. The sanitization function from the example above ensures that all whitespace is removed and all letters are capitalized, so this check is an extra safety net to ensure the input matches the pattern. ```php add_action( 'woocommerce_validate_additional_field', function ( WP_Error $errors, $field_key, $field_value ) { if ( 'namespace/gov-id' === $field_key ) { $match = preg_match( '/[A-Z0-9]{5}/', $field_value ); if ( 0 === $match || false === $match ) { $errors->add( 'invalid_gov_id', 'Please ensure your government ID matches the correct format.' ); } } }, 10, 3 ); ``` It is important to note that this action must _add_ errors to the `WP_Error` object it receives. Returning a new `WP_Error` object or any other value will result in the errors not showing. If no validation errors are encountered the function can just return void. #### Multiple field validation There are cases where the validity of a field depends on the value of another field, for example validating the format of a government ID based on what country the shopper is in. In this case, validating only single fields (as above) is not sufficient as the country may be unknown during the `woocommerce_validate_additional_field` action. To solve this, it is possible to validate a field in the context of the location it renders in. The other fields in that location will be passed to this action. ##### Using the `woocommerce_blocks_validate_location_{location}_fields` action This action will be fired for each location that additional fields can render in (`address`, `contact`, and `order`). For `address` it fires twice, once for the billing address and once for the shipping address. The callback receives the keys and values of the other additional fields in the same location. It is important to note that any fields rendered in other locations will not be passed to this action, however it might be possible to get those values by accessing the customer or order object, however this is not supported and there are no guarantees regarding backward compatibility in future versions. | Argument | Type | Description | |----------|-----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `$errors` | `WP_Error` | An error object containing errors that were already encountered while processing the request. If no errors were added yet, it will still be a `WP_Error` object but it will be empty. | | `$fields` | `array` | The fields rendered in this locations. | | `$group` | `'billing'\|'shipping'\|'other'` | If the action is for the address location, the type of address will be set here. If it is for contact or order, this will be 'other'. | There are several places where these hooks are fired. - When checking out using the Checkout block or Store API. - `woocommerce_blocks_validate_location_address_fields` (x2) - `woocommerce_blocks_validate_location_contact_fields` - `woocommerce_blocks_validate_location_other_fields` - When updating addresses in the "My account" area - `woocommerce_blocks_validate_location_address_fields` (**x1** - only the address being edited) - When updating the "Account details" section in the "My account" area - `woocommerce_blocks_validate_location_contact_fields` ##### Example of location validation In this example, assume there is another field registered alongside the `namespace/gov-id` called `namespace/confirm-gov-id`. This field will be a confirmation for the Government ID field. The example below illustrates how to verify that the value of the confirmation field matches the value of the main field. ```php add_action( 'woocommerce_blocks_validate_location_address_fields', function ( \WP_Error $errors, $fields, $group ) { if ( $fields['namespace/gov-id'] !== $fields['namespace/confirm-gov-id'] ) { $errors->add( 'gov_id_mismatch', 'Please ensure your government ID matches the confirmation.' ); } }, 10, 3 ); ``` If these fields were rendered in the "contact" location instead, the code would be the same except the hook used would be: `woocommerce_blocks_validate_location_contact_fields`. ## Conditional visibility and validation via JSON Schema The `required`, `hidden`, and `validation` properties accept an `array` of [JSON Schema](https://json-schema.org/understanding-json-schema/about) to create conditional logic for fields. This allows you to dynamically control field visibility, requirement status, and validation rules based on the values of other fields. Schema is evaluated in the frontend in real-time, and on the backend at any update. This ensures fast and responsive UI, and consistent results between the client and server. ### JSON Schema Structure Each schema in the array should be a valid JSON Schema object that defines conditions for when the property should be applied. The schema is evaluated against the current cart and checkout state, which includes all field values and various options (payment, shipping, customer). Basic structure of a JSON Schema object: ```json { "type": "object", "properties": { "fieldId": { "enum": ["value1", "value2"] } }, "required": ["fieldId"] } ``` If you're not familiar with JSON Schema, you can get a quick introduction to it [from the official website](https://json-schema.org/understanding-json-schema/basics), or from one of the libraries used [like AJV](https://ajv.js.org/json-schema.html) or [OPIS.](https://opis.io/json-schema/2.x/examples.html) Checkout builds an abstraction on top of both of them. ### Document object When you're writing your rules, you're writing a partial schema for the document object, essentially describing the ideal state you want for your field to be required or hidden. An example of the document object looks like this:
Document object ```json { "cart": { "coupons": [ "my_coupon" ], "shipping_rates": [ "free_shipping:1" ], "items": [ 27, 27, 68 ], "items_type": [ "simple", "variation" ], "items_count": 3, "items_weight": 0, "needs_shipping": true, "prefers_collection": false, "totals": { "totalPrice": 6600, "totalTax": 600 }, "extensions": {} }, "checkout": { "create_account": false, "customer_note": "", "additional_fields": { "namespace/mycontact-field": "myvalue" }, "payment_method": "bacs" }, "customer": { "id": 1, "billing_address": { "first_name": "First Name", "last_name": "Last Name", "company": "Company", "address_1": "Address 1", "address_2": "Address 2", "city": "City", "state": "State", "postcode": "08000", "country": "US", "email": "email@example.com", "phone": "1234567890", "namespace/myfield": "myvalue" }, "shipping_address": { "first_name": "First Name", "last_name": "Last Name", "company": "Company", "address_1": "Address 1", "address_2": "Address 2", "city": "City", "state": "State", "postcode": "08000", "country": "US", "phone": "1234567890", "namespace/myfield": "myvalue" }, "address": { "first_name": "First Name", "last_name": "Last Name", "company": "Company", "address_1": "Address 1", "address_2": "Address 2", "city": "City", "state": "State", "postcode": "08000", "country": "US", "phone": "1234567890", "namespace/myfield": "myvalue" } } } ```
It's full schema is this one:
Document schema ```json { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Cart and Checkout Schema", "description": "Schema for cart, checkout, and customer information", "type": "object", "properties": { "cart": { "type": "object", "description": "Information about the shopping cart", "properties": { "coupons": { "type": "array", "description": "List of coupon codes applied to the cart", "items": { "type": "string" } }, "shipping_rates": { "type": "array", "description": "List of currently selected shipping rates", "items": { "type": "string", "description": "Shipping rate identifier using the full shipping rate ID so method_id:instance_id, for example: flat_rate:1" } }, "items": { "type": "array", "description": "List of product IDs in the cart, IDs will be dubplicated depending on the quantity of the product in the cart, so if you have 2 of product ID 1, the array will have 2 entries of product ID 1", "items": { "type": "integer" } }, "items_type": { "type": "array", "description": "Types of items in the cart, for example: simple, variation, subscription, etc.", "items": { "type": "string" } }, "items_count": { "type": "integer", "description": "Total number of items in the cart", "minimum": 0 }, "items_weight": { "type": "number", "description": "Total weight of items in the cart", "minimum": 0 }, "needs_shipping": { "type": "boolean", "description": "Whether the items in the cart require shipping" }, "prefers_collection": { "type": "boolean", "description": "Whether the customer prefers using Local Pickup" }, "totals": { "type": "object", "description": "Cart totals information", "properties": { "totalPrice": { "type": "integer", "description": "Total price of the cart in smallest currency unit (e.g., cents), after applying all discounts, shipping, and taxes" }, "totalTax": { "type": "integer", "description": "Total tax amount in smallest currency unit (e.g., cents), after applying all discounts, shipping, and taxes" } } }, "extensions": { "type": "object", "description": "Additional cart extension data, this is similar to what's passed in Store API's extensions parameter" } } }, "checkout": { "type": "object", "description": "Checkout preferences and settings", "properties": { "create_account": { "type": "boolean", "description": "Whether the customer checked the create account checkbox, this will be false if the customer is logged in, cannot create an account, or forced to create an account." }, "customer_note": { "type": "string", "description": "Customer's note or special instructions for the order, this will be empty if the customer didn't add a note." }, "additional_fields": { "type": "object", "description": "Additional checkout fields, both applied to the contact or the order locations.", "additionalProperties": { "type": "string" }, "patternProperties": { "^[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+$": { "type": "string", "description": "Custom fields with namespace identifiers" } } }, "payment_method": { "type": "string", "description": "Selected payment method identifier, this will be the payment method ID regardless if the customer selected a saved payment method or new payment method" } } }, "customer": { "type": "object", "description": "Customer information", "properties": { "id": { "type": "integer", "description": "Customer ID, this will be 0 if the customer is not logged in" }, "billing_address": { "$ref": "#/definitions/address", "description": "Customer's billing address" }, "shipping_address": { "$ref": "#/definitions/address", "description": "Customer's shipping address" }, "address": { "$ref": "#/definitions/address", "description": "This is a dynamic field that will be the billing or shipping address depending on the context of the field being evaluted." } } } }, "definitions": { "address": { "type": "object", "description": "Customer address information", "properties": { "first_name": { "type": "string", "description": "First name of the recipient" }, "last_name": { "type": "string", "description": "Last name of the recipient" }, "company": { "type": "string", "description": "Company name" }, "address_1": { "type": "string", "description": "Primary address line" }, "address_2": { "type": "string", "description": "Secondary address line" }, "city": { "type": "string", "description": "City name" }, "state": { "type": "string", "description": "State or province, this will be the state code if it's a predefined list, for example: CA, TX, NY, etc, or the field value if it's a freeform state, for example: London." }, "postcode": { "type": "string", "description": "Postal or ZIP code" }, "country": { "type": "string", "description": "Country code (e.g., US, UK)" }, "email": { "type": "string", "format": "email", "description": "Email address" }, "phone": { "type": "string", "description": "Phone number" } }, "additionalProperties": { "type": "string", "description": "Custom fields with namespace identifiers" }, "patternProperties": { "^[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+$": { "type": "string", "description": "Custom fields with namespace identifiers" } } } } } ```
### Examples #### Required and visible field In this example we make the field required and visible only if local pickup is being used. ```php 'required' => [ "type" => "object", "properties" => [ "cart" => [ "properties" => [ "prefers_collection" => [ "const" => true ] ] ] ] ], 'hidden' => [ "type" => "object", "properties" => [ "cart" => [ "properties" => [ "prefers_collection" => [ "const" => false ] ] ] ] ] ``` Notice that for hidden, we inverse the field, meaning, this field should only be hidden if `prefers_collection` is false, which is almost all cases except when it's selected. In the examples above, we used [the keyword `const`](https://ajv.js.org/json-schema.html#const). #### Validation schema example Validation is slightly different from conditional visibility and requirement. In validation, you will pass in a subset of schema (only applicable to your field), and its role is to validate the field and show any errors if there. In this example, we ensure that VAT is made up of a country code and 8-12 numbers. ```php 'validation' => [ "type" => "string", "pattern" => "^[A-Z]{2}[0-9]{8,12}$" "errorMessage" => "Please enter a valid VAT code with 2 letters for country code and 8-12 numbers." ] ``` Validation can also be against other fields, for example, an alternative email field that shouldn't the current email: ```php 'validation' => [ "type" => "string", "format" => "email", "not" => [ "const" => ["$data", "0/customer/billing_address/email"] ] "errorMessage" => "Please enter a valid VAT code with 2 letters for country code and 8-12 numbers." ] ``` In the example above, we used [format keyword](https://github.com/ajv-validator/ajv-formats) and `$data` to refer to the current field value via [JSON pointers](https://ajv.js.org/guide/combining-schemas.html#data-reference). We also used the `errorMessage` property to provide a custom error message. #### `$data` keyword and JSON pointers `$data` keyword is a way in JSON schema to reference another field's value. In the above example, we use it to refer to the billing email via [JSON pointers](https://ajv.js.org/guide/combining-schemas.html#data-reference). When dealing with JSON pointers, there are some things to keep in mind: - The forward slash `/` is used to navigate through the JSON object, so for additional fields, a field named `my-plugin-namespace/my-field` will need to be referenced as `my-plugin-namespace~1my-field`. - Navigation in JSON pointers can be from the current field backward, or from the root. If you have an address field and want to validate say the phone field, this means you will validate 2 values, one for shipping, and one for billing, so you can reference the phone field in 2 ways: - `0/customer/address/phone` which uses root navigation (via the `0/`) prefix, and uses the dynamic `address` group, which will change depending if the billing or shipping value is being validated. - `1/phone` which uses relative pointers to step back, in this case, it will access its sibling field, the `phone` field. Increase the number to step back even further, for example, `2/id` will access the customer ID. ### Keywords and values that are not in spec We support [JSON Schema Draft-07](https://json-schema.org/draft-07), which is simple and doesn't support all the keywords and values that are in the latest spec, but we feel like it covers most of the use cases. On top of that, we introduced some non-standard keywords and values that are not in the spec, their implementation might be different between Opis and AJV (or any future implementation), this is the list of such keywords and values: - `errorMessage`: Custom error message for validation, in AJV, this is `errorMessage` and in Opis, this is `$error`, we only support `errorMessage` and maps that internally for Opis. We also don't support templates in `errorMessage` for now. - `$data`: Refers to the current field value via [JSON pointers](https://ajv.js.org/guide/combining-schemas.html#data-reference), both Opis and AJV use the same implementation. ### Evaluation Logic - For `required`: If any schema in the array matches the current checkout state, the field will be required. - For `hidden`: If any schema in the array matches the current checkout state, the field will be hidden. - For `validation`: The value of the field will be evaluated against the partial schema provided and an error will be shown if it didn't match. ### Performance Considerations Complex JSON Schema conditions can impact checkout performance. Keep your schemas as simple as possible and limit the number of conditions to what's necessary for your use case. ## Backward compatibility Due to technical reasons, it's not yet possible to specify the meta key for fields, as we want them to be prefixed and managed. Plugins with existing fields in shortcode Checkout can be compatible and react to reading and saving fields using hooks. Assuming 2 fields, named `my-plugin-namespace/address-field` in the address step and `my-plugin-namespace/my-other-field` in the order step, you can: ### React to saving fields You can react to those fields being saved by hooking into `woocommerce_set_additional_field_value` action. ```php add_action( 'woocommerce_set_additional_field_value', function ( $key, $value, $group, $wc_object ) { if ( 'my-plugin-namespace/address-field' !== $key ) { return; } if ( 'billing' === $group ) { $my_plugin_address_key = 'existing_billing_address_field_key'; } else { $my_plugin_address_key = 'existing_shipping_address_field_key'; } $wc_object->update_meta_data( $my_plugin_address_key, $value, true ); }, 10, 4 ); add_action( 'woocommerce_set_additional_field_value', function ( $key, $value, $group, $wc_object ) { if ( 'my-plugin-namespace/my-other-field' !== $key ) { return; } $my_plugin_key = 'existing_order_field_key'; $wc_object->update_meta_data( $my_plugin_key, $value, true ); }, 10, 4 ); ``` This way, you can ensure existing systems will continue working and your integration will continue to work. However, ideally, you should migrate your existing data and systems to use the new meta fields. ### React to reading fields You can use the `woocommerce_get_default_value_for_{$key}` filters to provide a different default value (a value coming from another meta field for example): ```php add_filter( "woocommerce_get_default_value_for_my-plugin-namespace/address-field", function ( $value, $group, $wc_object ) { if ( 'billing' === $group ) { $my_plugin_key = 'existing_billing_address_field_key'; } else { $my_plugin_key = 'existing_shipping_address_field_key'; } return $wc_object->get_meta( $my_plugin_key ); }, 10, 3 ); add_filter( "woocommerce_get_default_value_for_my-plugin-namespace/my-other-field", function ( $value, $group, $wc_object ) { $my_plugin_key = 'existing_order_field_key'; return $wc_object->get_meta( $my_plugin_key ); }, 10, 3 ); ``` ## A full example In this full example we will register the Government ID text field and verify that it conforms to a specific pattern. This example is just a combined version of the examples shared above. ```php add_action( 'woocommerce_init', function() { woocommerce_register_additional_checkout_field( array( 'id' => 'namespace/gov-id', 'label' => 'Government ID', 'location' => 'address', 'required' => true, 'attributes' => array( 'autocomplete' => 'government-id', 'pattern' => '[A-Z0-9]{5}', // A 5-character string of capital letters and numbers. 'title' => 'Your 5-digit Government ID', ), ), ); woocommerce_register_additional_checkout_field( array( 'id' => 'namespace/confirm-gov-id', 'label' => 'Confirm government ID', 'location' => 'address', 'required' => true, 'attributes' => array( 'autocomplete' => 'government-id', 'pattern' => '[A-Z0-9]{5}', // A 5-character string of capital letters and numbers. 'title' => 'Confirm your 5-digit Government ID', ), ), ); add_action( 'woocommerce_sanitize_additional_field', function ( $field_value, $field_key ) { if ( 'namespace/gov-id' === $field_key || 'namespace/confirm-gov-id' === $field_key ) { $field_value = str_replace( ' ', '', $field_value ); $field_value = strtoupper( $field_value ); } return $field_value; }, 10, 2 ); add_action( 'woocommerce_validate_additional_field', function ( WP_Error $errors, $field_key, $field_value ) { if ( 'namespace/gov-id' === $field_key ) { $match = preg_match( '/[A-Z0-9]{5}/', $field_value ); if ( 0 === $match || false === $match ) { $errors->add( 'invalid_gov_id', 'Please ensure your government ID matches the correct format.' ); } } return $error; }, 10, 3 ); } ); add_action( 'woocommerce_blocks_validate_location_address_fields', function ( \WP_Error $errors, $fields, $group ) { if ( $fields['namespace/gov-id'] !== $fields['namespace/confirm-gov-id'] ) { $errors->add( 'gov_id_mismatch', 'Please ensure your government ID matches the confirmation.' ); } }, 10, 3 ); ``` --- ## Available Filters *Source: block-development/cart-and-checkout-blocks/available-filters/README.md* # Available Filters This document lists the filters that are currently available to extensions and offers usage information for each one of them. Information on registering filters can be found on the [Checkout - Filter Registry](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/client/blocks/packages/checkout/filter-registry/README.md) page. ## Cart Line Items filters The following [Cart Line Items filters](https://developer.woocommerce.com/docs/cart-and-checkout-filters-cart-line-items/) are available: - `cartItemClass` - `cartItemPrice` - `itemName` - `saleBadgePriceFormat` - `showRemoveItemLink` - `subtotalPriceFormat` The following screenshot shows which parts the individual filters affect: ![Cart Line Items](https://woocommerce.com/wp-content/uploads/2023/10/Screenshot-2023-10-26-at-13.12.33.png) ## Order Summary Items filters The following [Order Summary Items filters](https://developer.woocommerce.com/docs/cart-and-checkout-filters-order-summary-items/) are available: - `cartItemClass` - `cartItemPrice` - `itemName` - `subtotalPriceFormat` The following screenshot shows which parts the individual filters affect: ![Order Summary Items](https://woocommerce.com/wp-content/uploads/2023/10/Screenshot-2023-10-26-at-16.29.45.png) ## Totals Footer Item filter The following [Totals Footer Item filter](https://developer.woocommerce.com/docs/cart-and-checkout-filters-totals-footer-item/) is available: - `totalLabel` - `totalValue` ## Checkout and place order button filters The following [Checkout and place order button filters](https://developer.woocommerce.com/docs/cart-and-checkout-filters-checkout-and-place-order-button/) are available: - `proceedToCheckoutButtonLabel` - `proceedToCheckoutButtonLink` - `placeOrderButtonLabel` ## Coupon filters The following [Coupon filters](https://developer.woocommerce.com/docs/cart-and-checkout-filters-coupons/) are available: - `coupons` - `showApplyCouponNotice` - `showRemoveCouponNotice` ## Additional Cart and Checkout inner block types filter The following [Additional Cart and Checkout inner block types filter](https://developer.woocommerce.com/docs/cart-and-checkout-filters-inner-block-types/) is available: - `additionalCartCheckoutInnerBlockTypes` ## Combined filters Filters can also be combined. The following example shows how to combine some of the available filters. ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const isOrderSummaryContext = ( args ) => args?.context === 'summary'; const modifyCartItemClass = ( defaultValue, extensions, args ) => { if ( isOrderSummaryContext( args ) ) { return 'my-custom-class'; } return defaultValue; }; const modifyCartItemPrice = ( defaultValue, extensions, args ) => { if ( isOrderSummaryContext( args ) ) { return ' for all items'; } return defaultValue; }; const modifyItemName = ( defaultValue, extensions, args ) => { if ( isOrderSummaryContext( args ) ) { return `${ defaultValue }`; } return defaultValue; }; const modifySubtotalPriceFormat = ( defaultValue, extensions, args ) => { if ( isOrderSummaryContext( args ) ) { return ' per item'; } return defaultValue; }; registerCheckoutFilters( 'example-extension', { cartItemClass: modifyCartItemClass, cartItemPrice: modifyCartItemPrice, itemName: modifyItemName, subtotalPriceFormat: modifySubtotalPriceFormat, } ); ``` ## Troubleshooting If you are logged in to the store as an administrator, you should be shown an error like this if your filter is not working correctly. The error will also be shown in your console. ![Troubleshooting](https://woocommerce.com/wp-content/uploads/2023/10/Screenshot-2023-10-30-at-10.52.53.png) --- ## Inner block types *Source: block-development/cart-and-checkout-blocks/available-filters/additional-cart-checkout-inner-block-types.md* # Inner block types The following Additional Cart and Checkout inner block types filter is available: - `additionalCartCheckoutInnerBlockTypes` ## `additionalCartCheckoutInnerBlockTypes` ### Description The Cart and Checkout blocks are made up of inner blocks. These inner blocks areas allow certain block types to be added as children. By default, only `core/paragraph`, `core/image`, and `core/separator` are available to add. By using the `additionalCartCheckoutInnerBlockTypes` filter it is possible to add items to this array to control what the editor can into an inner block. This filter is called once for each inner block area, so it is possible to be very granular when determining what blocks can be added where. ### Parameters - _defaultValue_ `array` (default: `[]`) - The default value of the filter. - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following key: - _block_ `string` - The block name of the inner block area, e.g. `woocommerce/checkout-shipping-address-block`. - _validation_ `boolean` or `Error` - Checks if the returned value is an array of strings. If an error occurs, it will be thrown. ### Returns - `array` - The modified array with allowed block types for the corresponding inner block area. ### Code example Let's suppose we want to allow the editor to add some blocks in specific places in the Cart and Checkout blocks. 1. Allow `core/quote` to be inserted in every block area in the Cart and Checkout blocks. 2. Allow `core/table` to be inserted in the Shipping Address block in the Checkout. In our extension we could register a filter satisfy both of these conditions like so: ```tsx document.addEventListener( 'DOMContentLoaded', function () { const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyAdditionalInnerBlockTypes = ( defaultValue, extensions, args, validation ) => { defaultValue.push( 'core/quote' ); if ( args?.block === 'woocommerce/checkout-shipping-address-block' ) { defaultValue.push( 'core/table' ); } return defaultValue; }; registerCheckoutFilters( 'example-extension', { additionalCartCheckoutInnerBlockTypes: modifyAdditionalInnerBlockTypes, } ); } ); ``` To call this filter within the editor, wrap the filter registration in a `DOMContentLoaded` event listener and ensure the code runs in the admin panel. > Filters can be also combined. See [Combined filters](../available-filters/README.md) for an example. ### Screenshots | Before | After | |:---------------------------------------------------------------------:|:---------------------------------------------------------------------:| |![Before applying the Additional Cart and Checkout inner block types filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/0d4560c8-c2b1-4ed8-8aee-469b248ccb08) |![After applying the Additional Cart and Checkout inner block types filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/d38cd568-6c8c-4158-9269-d8dffdf66988) | --- ## Cart line items *Source: block-development/cart-and-checkout-blocks/available-filters/cart-line-items.md* # Cart line items The following Cart Line Items filters are available: - `cartItemClass` - `cartItemPrice` - `itemName` - `saleBadgePriceFormat` - `showRemoveItemLink` - `subtotalPriceFormat` The following objects are shared between the filters: - Cart object - Cart Item object The following screenshot shows which parts the individual filters affect: ![Cart Line Items](https://woocommerce.com/wp-content/uploads/2023/10/Screenshot-2023-10-26-at-13.12.33.png) ## `cartItemClass` ### Description The `cartItemClass` filter allows to change the cart item class. ### Parameters - _defaultValue_ `object` (default: `''`) - The default cart item class. - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following keys: - _cart_ `object` - The cart object from `wc/store/cart`, see Cart object. - _cartItem_ `object` - The cart item object from `wc/store/cart`, see Cart Item object. - _context_ `string` (allowed values: `cart` or `summary`) - The context of the item. ### Returns - `string` - The modified cart item class, or an empty string. ### Code examples #### Basic example ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyCartItemClass = ( defaultValue, extensions, args ) => { const isCartContext = args?.context === 'cart'; if ( ! isCartContext ) { return defaultValue; } return 'my-custom-class'; }; registerCheckoutFilters( 'example-extension', { cartItemClass: modifyCartItemClass, } ); ``` #### Advanced example ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyCartItemClass = ( defaultValue, extensions, args ) => { const isCartContext = args?.context === 'cart'; if ( ! isCartContext ) { return defaultValue; } if ( args?.cartItem?.name === 'Beanie with Logo' ) { return 'cool-class'; } if ( args?.cartItem?.name === 'Sunglasses' ) { return 'hot-class'; } return 'my-custom-class'; }; registerCheckoutFilters( 'example-extension', { cartItemClass: modifyCartItemClass, } ); ``` > Filters can be also combined. See [Combined filters](../available-filters/README.md) for an example. ### Screenshots | Before | After | |:---------------------------------------------------------------------:|:---------------------------------------------------------------------:| |![Before applying the Cart Item Class filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/a587a6ce-d051-4ed0-bba5-815b5d72179d) |![After applying the Cart Item Class filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/9b25eeae-6d81-4e28-b177-32f942e1d0c2) | ## `cartItemPrice` ### Description The `cartItemPrice` filter allows to format the cart item price. ### Parameters - _defaultValue_ `string` (default: ``) - The default cart item price. - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following keys: - _cart_ `object` - The cart object from `wc/store/cart`, see Cart object. - _cartItem_ `object` - The cart item object from `wc/store/cart`, see Cart Item object. - _context_ `string` (allowed values: `cart` or `summary`) - The context of the item. - _validation_ `boolean` - Checks if the return value contains the substring ``. ### Returns - `string` - The modified format of the cart item price, which must contain the substring ``, or the original price format. ### Code examples #### Basic example ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyCartItemPrice = ( defaultValue, extensions, args, validation ) => { const isCartContext = args?.context === 'cart'; if ( ! isCartContext ) { return defaultValue; } return ' for all items'; }; registerCheckoutFilters( 'example-extension', { cartItemPrice: modifyCartItemPrice, } ); ``` #### Advanced example ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyCartItemPrice = ( defaultValue, extensions, args, validation ) => { const isCartContext = args?.context === 'cart'; if ( ! isCartContext ) { return defaultValue; } if ( args?.cartItem?.name === 'Beanie with Logo' ) { return ' to keep you warm'; } if ( args?.cartItem?.name === 'Sunglasses' ) { return ' to keep you cool'; } return ' for all items'; }; registerCheckoutFilters( 'example-extension', { cartItemPrice: modifyCartItemPrice, } ); ``` > Filters can be also combined. See [Combined filters](../available-filters/README.md) for an example. ### Screenshots | Before | After | |:---------------------------------------------------------------------:|:---------------------------------------------------------------------:| |![Before applying the Cart Item Price filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/bbaeb68a-492e-41e7-87b7-4b8b05ca3709) |![After applying the Cart Item Price filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/bbaeb68a-492e-41e7-87b7-4b8b05ca3709) | ## `itemName` ### Description The `itemName` filter allows to change the cart item name. ### Parameters - _defaultValue_ `string` - The default cart item name. - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following keys: - _cart_ `object` - The cart object from `wc/store/cart`, see Cart object. - _cartItem_ `object` - The cart item object from `wc/store/cart`, see Cart Item object. - _context_ `string` (allowed values: `cart` or `summary`) - The context of the item. ### Returns - `string` - The original or modified cart item name. ### Code examples #### Basic example ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyItemName = ( defaultValue, extensions, args ) => { const isCartContext = args?.context === 'cart'; if ( ! isCartContext ) { return defaultValue; } return `🪴 ${ defaultValue } 🪴`; }; registerCheckoutFilters( 'example-extension', { itemName: modifyItemName, } ); ``` #### Advanced example ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyItemName = ( defaultValue, extensions, args ) => { const isCartContext = args?.context === 'cart'; if ( ! isCartContext ) { return defaultValue; } if ( args?.cartItem?.name === 'Beanie with Logo' ) { return `⛷️ ${ defaultValue } ⛷️`; } if ( args?.cartItem?.name === 'Sunglasses' ) { return `🏄‍♂️ ${ defaultValue } 🏄‍♂️`; } return `🪴 ${ defaultValue } 🪴`; }; registerCheckoutFilters( 'example-extension', { itemName: modifyItemName, } ); ``` > Filters can be also combined. See [Combined filters](../available-filters/README.md) for an example. ### Screenshots | Before | After | |:---------------------------------------------------------------------:|:---------------------------------------------------------------------:| |![Before applying the Item Name filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/97d0f501-138e-4448-93df-a4d865b524e6) |![After applying the Item Name filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/69381932-d064-4e8f-b378-c2477fef56ae) | ## `saleBadgePriceFormat` ### Description The `saleBadgePriceFormat` filter allows to format the cart item sale badge price. ### Parameters - _defaultValue_ `string` (default: ``) - The default cart item sale badge price. - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following keys: - _cart_ `object` - The cart object from `wc/store/cart`, see Cart object. - _cartItem_ `object` - The cart item object from `wc/store/cart`, see Cart Item object. - _context_ `string` (allowed values: `cart` or `summary`) - The context of the item. - _validation_ `boolean` - Checks if the return value contains the substring ``. ### Returns - `string` - The modified format of the cart item sale badge price, which must contain the substring ``, or the original price format. ### Code examples ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifySaleBadgePriceFormat = ( defaultValue, extensions, args, validation ) => { const isCartContext = args?.context === 'cart'; if ( ! isCartContext ) { return defaultValue; } return ' per item'; }; registerCheckoutFilters( 'example-extension', { saleBadgePriceFormat: modifySaleBadgePriceFormat, } ); ``` #### Advanced example ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifySaleBadgePriceFormat = ( defaultValue, extensions, args, validation ) => { const isCartContext = args?.context === 'cart'; if ( ! isCartContext ) { return defaultValue; } if ( args?.cartItem?.name === 'Beanie with Logo' ) { return ' per item while keeping warm'; } if ( args?.cartItem?.name === 'Sunglasses' ) { return ' per item while looking cool'; } return ' per item'; }; registerCheckoutFilters( 'example-extension', { saleBadgePriceFormat: modifySaleBadgePriceFormat, } ); ``` > Filters can be also combined. See [Combined filters](../available-filters/README.md) for an example. ### Screenshots | Before | After | |:---------------------------------------------------------------------:|:---------------------------------------------------------------------:| |![Before applying the Sale Badge Price Format filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/d2aeb206-e620-44e0-93c1-31484cfcdca6) |![After applying the Sale Badge Price Format filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/6b929695-5d89-433b-8694-b9201a7c0519) | ## `showRemoveItemLink` ### Description The `showRemoveItemLink` is used to show or hide the cart item remove link. ### Parameters - _defaultValue_ (type: `boolean`, default: `true`) - The default value of the remove link. - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following keys: - _cart_ `object` - The cart object from `wc/store/cart`, see Cart object. - _cartItem_ `object` - The cart item object from `wc/store/cart`, see Cart Item object. - _context_ `string` (allowed values: `cart` or `summary`) - The context of the item. ### Returns - `boolean` - `true` if the cart item remove link should be shown, `false` otherwise. ### Code examples #### Basic example ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyShowRemoveItemLink = ( defaultValue, extensions, args ) => { const isCartContext = args?.context === 'cart'; if ( ! isCartContext ) { return defaultValue; } return false; }; registerCheckoutFilters( 'example-extension', { showRemoveItemLink: modifyShowRemoveItemLink, } ); ``` #### Advanced example ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyShowRemoveItemLink = ( defaultValue, extensions, args ) => { const isCartContext = args?.context === 'cart'; if ( ! isCartContext ) { return defaultValue; } if ( args?.cartItem?.name === 'Beanie with Logo' ) { return false; } if ( args?.cartItem?.name === 'Sunglasses' ) { return false; } return true; }; registerCheckoutFilters( 'example-extension', { showRemoveItemLink: modifyShowRemoveItemLink, } ); ``` > Filters can be also combined. See [Combined filters](../available-filters/README.md) for an example. ### Screenshots | Before | After | |:---------------------------------------------------------------------:|:---------------------------------------------------------------------:| |![Before applying the Show Remove Item Link filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/a4254f3b-f056-47ad-b34a-d5f6d5500e56) |![After applying the Show Remove Item Link filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/32c55dc7-ef65-4f35-ab90-9533bc79d362) | ## `subtotalPriceFormat` ### Description The `subtotalPriceFormat` filter allows to format the cart item subtotal price. ### Parameters - _defaultValue_ `string` (default: ``) - The default cart item subtotal price. - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following keys: - _cart_ `object` - The cart object from `wc/store/cart`, see Cart object. - _cartItem_ `object` - The cart item object from `wc/store/cart`, see Cart Item object. - _context_ `string` (allowed values: `cart` or `summary`) - The context of the item. - _validation_ `boolean` - Checks if the return value contains the substring ``. ### Returns - `string` - The modified format of the cart item subtotal price, which must contain the substring ``, or the original price format. ### Code examples #### Basic example ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifySubtotalPriceFormat = ( defaultValue, extensions, args, validation ) => { const isCartContext = args?.context === 'cart'; if ( ! isCartContext ) { return defaultValue; } return ' per item'; }; registerCheckoutFilters( 'example-extension', { subtotalPriceFormat: modifySubtotalPriceFormat, } ); ``` #### Advanced example ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifySubtotalPriceFormat = ( defaultValue, extensions, args, validation ) => { const isCartContext = args?.context === 'cart'; if ( ! isCartContext ) { return defaultValue; } if ( args?.cartItem?.name === 'Beanie with Logo' ) { return ' per warm beanie'; } if ( args?.cartItem?.name === 'Sunglasses' ) { return ' per cool sunglasses'; } return ' per item'; }; registerCheckoutFilters( 'example-extension', { subtotalPriceFormat: modifySubtotalPriceFormat, } ); ``` > Filters can be also combined. See [Combined filters](../available-filters/README.md) for an example. ### Screenshots | Before | After | |:---------------------------------------------------------------------:|:---------------------------------------------------------------------:| |![Before applying the Subtotal Price Format filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/a392cb24-4c40-4e25-8396-bf4971830e22) |![After applying the Subtotal Price Format filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/af69b26f-662a-4ef9-a288-3713b6e46373) | ## Cart object The Cart object of the filters above has the following keys: - _billingAddress_ `object` - The billing address object with the following keys: - _address_1_ `string` - The first line of the address. - _address_2_ `string` - The second line of the address. - _city_ `string` - The city of the address. - _company_ `string` - The company of the address. - _country_ `string` - The country of the address. - _email_ `string` - The email of the address. - _first_name_ `string` - The first name of the address. - _last_name_ `string` - The last name of the address. - _phone_ `string` - The phone of the address. - _postcode_ `string` - The postcode of the address. - _state_ `string` - The state of the address. - ~~_billingData_~~ `object` - The billing data object with the same keys as the `billingAddress` object. - _cartCoupons_ `array` - The cart coupons array. - _cartErrors_ `array` - The cart errors array. - _cartFees_ `array` - The cart fees array. - _cartHasCalculatedShipping_ `boolean` - Whether the cart has calculated shipping. - _cartIsLoading_ `boolean` - Whether the cart is loading. - _cartItemErrors_ `array` - The cart item errors array. - _cartItems_ `array` - The cart items array with cart item objects, see Cart Item object. - _cartItemsCount_ `number` - The cart items count. - _cartItemsWeight_ `number` - The cart items weight. - _cartNeedsPayment_ `boolean` - Whether the cart needs payment. - _cartNeedsShipping_ `boolean` - Whether the cart needs shipping. - _cartTotals_ `object` - The cart totals object with the following keys: - _currency_code_ `string` - The currency code. - _currency_decimal_separator_ `string` - The currency decimal separator. - _currency_minor_unit_ `number` - The currency minor unit. - _currency_prefix_ `string` - The currency prefix. - _currency_suffix_ `string` - The currency suffix. - _currency_symbol_ `string` - The currency symbol. - _currency_thousand_separator_ `string` - The currency thousand separator. - _tax_lines_ `array` - The tax lines array with tax line objects with the following keys: - _name_ `string` - The name of the tax line. - _price_ `number` - The price of the tax line. - _rate_ `string` - The rate ID of the tax line. - _total_discount_ `string` - The total discount. - _total_discount_tax_ `string` - The total discount tax. - _total_fees_ `string` - The total fees. - _total_fees_tax_ `string` - The total fees tax. - _total_items_ `string` - The total items. - _total_items_tax_ `string` - The total items tax. - _total_price_ `string` - The total price. - _total_shipping_ `string` - The total shipping. - _total_shipping_tax_ `string` - The total shipping tax. - _total_tax_ `string` - The total tax. - _crossSellsProducts_ `array` - The cross sells products array with cross sells product objects. - _extensions_ `object` (default: `{}`) - The extensions object. - _isLoadingRates_ `boolean` - Whether the cart is loading shipping rates. - _paymentRequirements_ `array` - The payment requirements array. - _shippingAddress_ `object` - The shipping address object with the same keys as the `billingAddress` object. - _shippingRates_ `array` - The shipping rates array. ## Cart Item object The Cart Item object of the filters above has the following keys: - _backorders_allowed_ `boolean` - Whether backorders are allowed. - _catalog_visibility_ `string` - The catalog visibility. - _decsription_ `string` - The cart item description. - _extensions_ `object` (default: `{}`) - The extensions object. - _id_ `number` - The item ID. - _images_ `array` - The item images array. - _item_data_ `array` - The item data array. - _key_ `string` - The item key. - _low_stock_remaining_ `number` - The low stock remaining. - _name_ `string` - The item name. - _permalink_ `string` - The item permalink. - _prices_ `object` - The item prices object with the following keys: - _currency_code_ `string` - The currency code. - _currency_decimal_separator_ `string` - The currency decimal separator. - _currency_minor_unit_ `number` - The currency minor unit. - _currency_prefix_ `string` - The currency prefix. - _currency_suffix_ `string` - The currency suffix. - _currency_symbol_ `string` - The currency symbol. - _currency_thousand_separator_ `string` - The currency thousand separator. - _price_ `string` - The price. - _price_range_ `string` - The price range. - _raw_prices_ `object` - The raw prices object with the following keys: - _precision_ `number` - The precision. - _price_ `number` - The price. - _regular_price_ `number` - The regular price. - _sale_price_ `number` - The sale price. - _regular_price_ `string` - The regular price. - _sale_price_ `string` - The sale price. - _quantity_ `number` - The item quantity. - _quantity_limits_ `object` - The item quantity limits object with the following keys: - _editable_ `boolean` - Whether the quantity is editable. - _maximum_ `number` - The maximum quantity. - _minimum_ `number` - The minimum quantity. - _multiple_of_ `number` - The multiple of quantity. - _short_description_ `string` - The item short description. - _show_backorder_badge_ `boolean` - Whether to show the backorder badge. - _sku_ `string` - The item SKU. - _sold_individually_ `boolean` - Whether the item is sold individually. - _totals_ `object` - The item totals object with the following keys: - _currency_code_ `string` - The currency code. - _currency_decimal_separator_ `string` - The currency decimal separator. - _currency_minor_unit_ `number` - The currency minor unit. - _currency_prefix_ `string` - The currency prefix. - _currency_suffix_ `string` - The currency suffix. - _currency_symbol_ `string` - The currency symbol. - _currency_thousand_separator_ `string` - The currency thousand separator. - _line_subtotal_ `string` - The line subtotal. - _line_subtotal_tax_ `string` - The line subtotal tax. - _line_total_ `string` - The line total. - _line_total_tax_ `string` - The line total tax. - _type_ `string` - The item type. - _variation_ `array` - The item variation array. --- ## Checkout and place order button *Source: block-development/cart-and-checkout-blocks/available-filters/checkout-and-place-order-button.md* # Checkout and place order button The following Checkout and place order button filters are available: - `proceedToCheckoutButtonLabel` - `proceedToCheckoutButtonLink` - `placeOrderButtonLabel` The following objects are shared between the filters: - Cart object - Cart Item object ## `proceedToCheckoutButtonLabel` ### Description The `proceedToCheckoutButtonLabel` filter allows change the label of the "Proceed to checkout" button. ### Parameters - _defaultValue_ `string` (default: `Proceed to Checkout`) - The label of the "Proceed to checkout" button. - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following keys: - _cart_ `object` - The cart object from `wc/store/cart`, see [Cart object](#cart-object). ### Returns - `string` - The label of the "Proceed to checkout" button. ### Code examples #### Basic example ```ts const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyProceedToCheckoutButtonLabel = ( defaultValue, extensions, args ) => { if ( ! args?.cart.items ) { return defaultValue; } return 'Go to checkout'; }; registerCheckoutFilters( 'example-extension', { proceedToCheckoutButtonLabel: modifyProceedToCheckoutButtonLabel, } ); ``` #### Advanced example ```ts const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyProceedToCheckoutButtonLabel = ( defaultValue, extensions, args ) => { if ( ! args?.cart.items ) { return defaultValue; } const isSunglassesInCart = args?.cart.items.some( ( item ) => item.name === 'Sunglasses' ); if ( isSunglassesInCart ) { return '😎 Proceed to checkout 😎'; } return defaultValue; }; registerCheckoutFilters( 'example-extension', { proceedToCheckoutButtonLabel: modifyProceedToCheckoutButtonLabel, } ); ``` > Filters can be also combined. See [Combined filters](../available-filters/README.md) for an example. ### Screenshots | Before | After | |:---------------------------------------------------------------------:|:---------------------------------------------------------------------:| |![Before applying the Proceed To Checkout Button Label filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/fb0216c1-a091-4d58-b443-f49ccff98ed8) |![After applying the Item Name filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/ef15b6df-fbd7-43e7-a359-b4adfbba961a) | ## `proceedToCheckoutButtonLink` ### Description The `proceedToCheckoutButtonLink` filter allows change the link of the "Proceed to checkout" button. ### Parameters - _defaultValue_ `string` (default: `/checkout`) - The link of the "Proceed to checkout" button. - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following keys: - _cart_ `object` - The cart object from `wc/store/cart`, see [Cart object](../available-filters/README.md). ### Returns - `string` - The link of the "Proceed to checkout" button. ### Code examples #### Basic example ```ts const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyProceedToCheckoutButtonLink = ( defaultValue, extensions, args ) => { if ( ! args?.cart.items ) { return defaultValue; } return '/custom-checkout'; }; registerCheckoutFilters( 'example-extension', { proceedToCheckoutButtonLink: modifyProceedToCheckoutButtonLink, } ); ``` #### Advanced example ```ts const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyProceedToCheckoutButtonLink = ( defaultValue, extensions, args ) => { if ( ! args?.cart.items ) { return defaultValue; } const isSunglassesInCart = args?.cart.items.some( ( item ) => item.name === 'Sunglasses' ); if ( isSunglassesInCart ) { return '/custom-checkout'; } return defaultValue; }; registerCheckoutFilters( 'example-extension', { proceedToCheckoutButtonLink: modifyProceedToCheckoutButtonLink, } ); ``` > Filters can be also combined. See [Combined filters](../available-filters/README.md) for an example. ### Screenshots | Before | After | |:---------------------------------------------------------------------:|:---------------------------------------------------------------------:| |![Before applying the Proceed To Checkout Button Link filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/3f657e0f-4fcc-4746-a554-64221e071b2e) |![After applying the Proceed To Checkout Button Link filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/064df213-439e-4d8f-b29c-55962604cb97) | ## `placeOrderButtonLabel` ### Description The `placeOrderButtonLabel` filter allows change the label of the "Place order" button. ### Parameters - _defaultValue_ (type: `string`, default: `Place order`) - The label of the "Place order" button. - _extensions_ `object` (default: `{}`) - The extensions object. ### Returns - `string` - The label of the "Place order" button. ### Code example ```ts const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyPlaceOrderButtonLabel = ( defaultValue, extensions ) => { return '😎 Pay now 😎'; }; registerCheckoutFilters( 'example-extension', { placeOrderButtonLabel: modifyPlaceOrderButtonLabel, } ); ``` > Filters can be also combined. See [Combined filters](../available-filters/README.md) for an example. ### Screenshots | Before | After | |:---------------------------------------------------------------------:|:---------------------------------------------------------------------:| |![Before applying the Place Order Button Label filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/aa6d9b65-4d56-45f7-8162-a6bbfe171250) |![After applying the Place Order Button Label filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/a5cc2572-16e7-4781-a5ab-5d6cdced2ff6) | ## Cart object The Cart object of the filters above has the following keys: - _billingAddress_ `object` - The billing address object with the following keys: - _address_1_ `string` - The first line of the address. - _address_2_ `string` - The second line of the address. - _city_ `string` - The city of the address. - _company_ `string` - The company of the address. - _country_ `string` - The country of the address. - _email_ `string` - The email of the address. - _first_name_ `string` - The first name of the address. - _last_name_ `string` - The last name of the address. - _phone_ `string` - The phone of the address. - _postcode_ `string` - The postcode of the address. - _state_ `string` - The state of the address. - _coupons_ `array` - The coupons array. - _crossSells_ `array` - The cross sell items array. - _errors_ `array` - The errors array. - _extensions_ `object` (default: `{}`) - The extensions object. - _fees_ `array` - The fees array. - _hasCalculatedShipping_ `boolean` - Whether the cart has calculated shipping. - _items_ `array` - The cart items array with cart item objects, see [Cart Item object](#cart-item-object). - _itemsCount_ `number` - The number of items in the cart. - _itemsWeight_ `number` - The total weight of the cart items. - _needsPayment_ `boolean` - Whether the cart needs payment. - _needsShipping_ `boolean` - Whether the cart needs shipping. - _paymentMethods_ `array` - The payment methods array. - _paymentRequirements_ `array` - The payment requirements array. - _shippingAddress_ `object` - The shipping address object with the same keys as the billing address object. - _shippingRates_ `array` - The shipping rates array. - _totals_ `object` - The totals object with the following keys: - _currency_code_ `string` - The currency code. - _currency_decimal_separator_ `string` - The currency decimal separator. - _currency_minor_unit_ `number` - The currency minor unit. - _currency_prefix_ `string` - The currency prefix. - _currency_suffix_ `string` - The currency suffix. - _currency_symbol_ `string` - The currency symbol. - _currency_thousand_separator_ `string` - The currency thousand separator. - _tax_lines_ `array` - The tax lines array of objects with the following keys: - _name_ `string` - The tax name. - _price_ `string` - The tax price. - _rate_ `string` - The tax rate. - _total_discount_ `string` - The total discount. - _total_discount_tax_ `string` - The total discount tax. - _total_fee_ `string` - The total fee. - _total_fee_tax_ `string` - The total fee tax. - _total_items_ `string` - The total items. - _total_items_tax_ `string` - The total items tax. - _total_price_ `string` - The total price. - _total_shipping_ `string` - The total shipping. - _total_shipping_tax_ `string` - The total shipping tax. - _total_tax_ `string` - The total tax. ## Cart Item object The Cart Item object of the filters above has the following keys: - _backorders_allowed_ `boolean` - Whether backorders are allowed. - _catalog_visibility_ `string` - The catalog visibility. - _decsription_ `string` - The cart item description. - _extensions_ `object` (default: `{}`) - The extensions object. - _id_ `number` - The item ID. - _images_ `array` - The item images array. - _item_data_ `array` - The item data array. - _key_ `string` - The item key. - _low_stock_remaining_ `number` - The low stock remaining. - _name_ `string` - The item name. - _permalink_ `string` - The item permalink. - _prices_ `object` - The item prices object with the following keys: - _currency_code_ `string` - The currency code. - _currency_decimal_separator_ `string` - The currency decimal separator. - _currency_minor_unit_ `number` - The currency minor unit. - _currency_prefix_ `string` - The currency prefix. - _currency_suffix_ `string` - The currency suffix. - _currency_symbol_ `string` - The currency symbol. - _currency_thousand_separator_ `string` - The currency thousand separator. - _price_ `string` - The price. - _price_range_ `string` - The price range. - _raw_prices_ `object` - The raw prices object with the following keys: - _precision_ `number` - The precision. - _price_ `number` - The price. - _regular_price_ `number` - The regular price. - _sale_price_ `number` - The sale price. - _regular_price_ `string` - The regular price. - _sale_price_ `string` - The sale price. - _quantity_ `number` - The item quantity. - _quantity_limits_ `object` - The item quantity limits object with the following keys: - _editable_ `boolean` - Whether the quantity is editable. - _maximum_ `number` - The maximum quantity. - _minimum_ `number` - The minimum quantity. - _multiple_of_ `number` - The multiple of quantity. - _short_description_ `string` - The item short description. - _show_backorder_badge_ `boolean` - Whether to show the backorder badge. - _sku_ `string` - The item SKU. - _sold_individually_ `boolean` - Whether the item is sold individually. - _totals_ `object` - The item totals object with the following keys: - _currency_code_ `string` - The currency code. - _currency_decimal_separator_ `string` - The currency decimal separator. - _currency_minor_unit_ `number` - The currency minor unit. - _currency_prefix_ `string` - The currency prefix. - _currency_suffix_ `string` - The currency suffix. - _currency_symbol_ `string` - The currency symbol. - _currency_thousand_separator_ `string` - The currency thousand separator. - _line_subtotal_ `string` - The line subtotal. - _line_subtotal_tax_ `string` - The line subtotal tax. - _line_total_ `string` - The line total. - _line_total_tax_ `string` - The line total tax. - _type_ `string` - The item type. - _variation_ `array` - The item variation array. --- ## Coupons *Source: block-development/cart-and-checkout-blocks/available-filters/coupons.md* # Coupons The following Coupon filters are available: - `coupons` - `showApplyCouponNotice` - `showRemoveCouponNotice` ## `coupons` ### Description The current functionality is to display the coupon codes in the Cart and Checkout sidebars. This could be undesirable if you dynamically generate a coupon code that is not user-friendly. It may, therefore, be desirable to change the way this code is displayed. To achieve this, the filter `coupons` exists. This filter could also be used to show or hide coupons. This filter must _not_ be used to alter the value/totals of a coupon. This will not carry through to the Cart totals. ### Parameters - _coupons_ `object` - The coupons object with the following keys: - _code_ `string` - The coupon code. - _discount_type_ `string` - The type of discount. Can be `percent` or `fixed_cart`. - _totals_ `object` - The totals object with the following keys: - _currency_code_ `string` - The currency code. - _currency_decimal_separator_ `string` - The currency decimal separator. - _currency_minor_unit_ `number` - The currency minor unit. - _currency_prefix_ `string` - The currency prefix. - _currency_suffix_ `string` - The currency suffix. - _currency_symbol_ `string` - The currency symbol. - _currency_thousand_separator_ `string` - The currency thousand separator. - _total_discount_ `string` - The total discount. - _total_discount_tax_ `string` - The total discount tax. - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following key: - _context_ `string` (default: `summary`) - The context of the item. ### Returns - `array` - The coupons array of objects with the same keys as above. ### Code example ```ts const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyCoupons = ( coupons, extensions, args ) => { return coupons.map( ( coupon ) => { if ( ! coupon.label.match( /autocoupon(?:_\d+)+/ ) ) { return coupon; } return { ...coupon, label: 'Automatic coupon', }; } ); }; registerCheckoutFilters( 'example-extension', { coupons: modifyCoupons, } ); ``` > Filters can be also combined. See [Combined filters](../available-filters/README.md) for an example. ### Screenshots | Before | After | |:---------------------------------------------------------------------:|:---------------------------------------------------------------------:| |![Before applying the Coupons filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/6cab1aff-e4b9-4909-b81c-5726c6a20c40) |![After applying the Coupons filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/a5cc2572-16e7-4781-a5ab-5d6cdced2ff6) | ## `showApplyCouponNotice` ### Description ### Parameters - _value_ `boolean` (default: `true`) - Weather to show the apply coupon notice. - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following keys: - _context_ `string` (allowed values: `wc/cart` and `wc/checkout`) - The context of the coupon notice. - _code_ `string` - The coupon code. ### Returns - `boolean` - Weather to show the apply coupon notice. ### Code examples #### Basic example ```ts const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyShowApplyCouponNotice = ( defaultValue, extensions, args ) => { return false; }; registerCheckoutFilters( 'example-extension', { showApplyCouponNotice: modifyShowApplyCouponNotice, } ); ``` #### Advanced example ```ts const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyShowApplyCouponNotice = ( defaultValue, extensions, args ) => { if ( args?.couponCode === '10off' ) { return false; } return defaultValue; }; registerCheckoutFilters( 'example-extension', { showApplyCouponNotice: modifyShowApplyCouponNotice, } ); ``` > Filters can be also combined. See [Combined filters](../available-filters/README.md) for an example. ### Screenshots | Before | After | |:---------------------------------------------------------------------:|:---------------------------------------------------------------------:| |![Before applying the Show Apply Coupon Notice filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/374d4899-61f3-49b2-ae04-5541d4c130c2) |![After applying the Show Apply Coupon Notice filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/c35dbd9b-eee4-4afe-9a29-9c554d467729) | ## `showRemoveCouponNotice` ### Description ### Parameters - _value_ `boolean` (default: `true`) - Weather to show the remove coupon notice. - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following keys: - _context_ `string` (allowed values: `wc/cart` and `wc/checkout`) - The context of the coupon notice. - _code_ `string` - The coupon code. ### Returns - `boolean` - Weather to show the apply coupon notice. ### Code examples #### Basic example ```ts const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyShowRemoveCouponNotice = ( defaultValue, extensions, args ) => { return false; }; registerCheckoutFilters( 'example-extension', { showRemoveCouponNotice: modifyShowRemoveCouponNotice, } ); ``` #### Advanced example ```ts const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyShowRemoveCouponNotice = ( defaultValue, extensions, args ) => { if ( args?.couponCode === '10off' ) { return false; } return defaultValue; }; registerCheckoutFilters( 'example-extension', { showRemoveCouponNotice: modifyShowRemoveCouponNotice, } ); ``` > Filters can be also combined. See [Combined filters](../available-filters/README.md) for an example. ### Screenshots | Before | After | |:---------------------------------------------------------------------:|:---------------------------------------------------------------------:| |![Before applying the Show Remove Coupon Notice filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/9d8607fa-ab20-4181-b70b-7954e7aa49cb) |![After applying the Show Remove Coupon Notice filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/83d5f65f-c4f3-4707-a250-077952514931) | --- ## Order summary items *Source: block-development/cart-and-checkout-blocks/available-filters/order-summary-items.md* # Order summary items The following Order Summary Items filters are available: - `cartItemClass` - `cartItemPrice` - `itemName` - `subtotalPriceFormat` The following objects are shared between the filters: - Cart object - Cart Item object The following screenshot shows which parts the individual filters affect: ![Order Summary Items](https://woocommerce.com/wp-content/uploads/2023/10/Screenshot-2023-10-26-at-16.29.45.png) ## `cartItemClass` ### Description The `cartItemClass` filter allows to change the order summary item class. ### Parameters - _defaultValue_ `string` (default: `''`) - The default order summary item class. - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following keys: - _cart_ `object` - The cart object from `wc/store/cart`, see [Cart object](#cart-object). - _cartItem_ `object` - The order summary item object from `wc/store/cart`, see [order summary item object](#cart-item-object). - _context_ `string` (allowed values: `cart` or `summary`) - The context of the item. ### Returns - `string` - The modified order summary item class, or an empty string. ### Code examples #### Basic example ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyCartItemClass = ( defaultValue, extensions, args ) => { const isOrderSummaryContext = args?.context === 'summary'; if ( ! isOrderSummaryContext ) { return defaultValue; } return 'my-custom-class'; }; registerCheckoutFilters( 'example-extension', { cartItemClass: modifyCartItemClass, } ); ``` #### Advanced example ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyCartItemClass = ( defaultValue, extensions, args ) => { const isOrderSummaryContext = args?.context === 'summary'; if ( ! isOrderSummaryContext ) { return defaultValue; } if ( args?.cartItem?.name === 'Beanie with Logo' ) { return 'cool-class'; } if ( args?.cartItem?.name === 'Sunglasses' ) { return 'hot-class'; } return 'my-custom-class'; }; registerCheckoutFilters( 'example-extension', { cartItemClass: modifyCartItemClass, } ); ``` > Filters can be also combined. See [Combined filters](../available-filters/README.md) for an example. ### Screenshots | Before | After | |:---------------------------------------------------------------------:|:---------------------------------------------------------------------:| |![Before applying the Cart Item Class filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/ff555a84-8d07-4889-97e1-8f7d50d47350) |![After applying the Cart Item Class filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/183809d8-03dc-466d-a415-d8d2062d880f) | ## `cartItemPrice` ### Description The `cartItemPrice` filter allows to format the order summary item price. ### Parameters - _defaultValue_ `string` (default: ``) - The default order summary item price. - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following keys: - _cart_ `object` - The cart object from `wc/store/cart`, see [Cart object](#cart-object). - _cartItem_ `object` - The order summary item object from `wc/store/cart`, see [order summary item object](#cart-item-object). - _context_ `string` (allowed values: `cart` or `summary`) - The context of the item. - _validation_ `boolean` - Checks if the return value contains the substring ``. ### Returns - `string` - The modified format of the order summary item price, which must contain the substring ``, or the original price format. ### Code examples #### Basic example ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyCartItemPrice = ( defaultValue, extensions, args, validation ) => { const isOrderSummaryContext = args?.context === 'summary'; if ( ! isOrderSummaryContext ) { return defaultValue; } return ' for all items'; }; registerCheckoutFilters( 'example-extension', { cartItemPrice: modifyCartItemPrice, } ); ``` #### Advanced example ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyCartItemPrice = ( defaultValue, extensions, args, validation ) => { const isOrderSummaryContext = args?.context === 'summary'; if ( ! isOrderSummaryContext ) { return defaultValue; } if ( args?.cartItem?.name === 'Beanie with Logo' ) { return ' to keep you ☀️'; } if ( args?.cartItem?.name === 'Sunglasses' ) { return ' to keep you ❄️'; } return ' for all items'; }; registerCheckoutFilters( 'example-extension', { cartItemPrice: modifyCartItemPrice, } ); ``` > Filters can be also combined. See [Combined filters](../available-filters/README.md) for an example. ### Screenshots | Before | After | |:---------------------------------------------------------------------:|:---------------------------------------------------------------------:| |![Before applying the Cart Item Price filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/58137fc4-884d-4783-9275-5f78abec1473) |![After applying the Cart Item Price filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/fb502b74-6447-49a8-8d35-241e738f089d) | ## `itemName` ### Description The `itemName` filter allows to change the order summary item name. ### Parameters - _defaultValue_ `string` - The default order summary item name. - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following keys: - _cart_ `object` - The cart object from `wc/store/cart`, see [Cart object](#cart-object). - _cartItem_ `object` - The order summary item object from `wc/store/cart`, see [order summary item object](#cart-item-object). - _context_ `string` (allowed values: `cart` or `summary`) - The context of the item. ### Returns - `string` - The original or modified order summary item name. ### Code examples #### Basic example ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyItemName = ( defaultValue, extensions, args ) => { const isOrderSummaryContext = args?.context === 'summary'; if ( ! isOrderSummaryContext ) { return defaultValue; } return `🪴 ${ defaultValue } 🪴`; }; registerCheckoutFilters( 'example-extension', { itemName: modifyItemName, } ); ``` #### Advanced example ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyItemName = ( defaultValue, extensions, args ) => { const isOrderSummaryContext = args?.context === 'summary'; if ( ! isOrderSummaryContext ) { return defaultValue; } if ( args?.cartItem?.name === 'Beanie with Logo' ) { return `⛷️ ${ defaultValue } ⛷️`; } if ( args?.cartItem?.name === 'Sunglasses' ) { return `🏄‍♂️ ${ defaultValue } 🏄‍♂️`; } return `🪴 ${ defaultValue } 🪴`; }; registerCheckoutFilters( 'example-extension', { itemName: modifyItemName, } ); ``` > Filters can be also combined. See [Combined filters](../available-filters/README.md) for an example. ### Screenshots | Before | After | |:---------------------------------------------------------------------:|:---------------------------------------------------------------------:| |![Before applying the Item Name filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/3dc0bda7-fccf-4f35-a2e2-aa04e616563a) |![After applying the Item Name filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/c96b8394-03a7-45f6-813b-5335f4bf83b5) | ## `subtotalPriceFormat` ### Description The `subtotalPriceFormat` filter allows to format the order summary item subtotal price. ### Parameters - _defaultValue_ `string` (default: ``) - The default order summary item subtotal price. - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following keys: - _cart_ `object` - The cart object from `wc/store/cart`, see [Cart object](#cart-object). - _cartItem_ `object` - The order summary item object from `wc/store/cart`, see [order summary item object](#cart-item-object). - _context_ `string` (allowed values: `cart` or `summary`) - The context of the item. - _validation_ `boolean` - Checks if the return value contains the substring ``. ### Returns - `string` - The modified format of the order summary item subtotal price, which must contain the substring ``, or the original price format. ### Code examples #### Basic example ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifySubtotalPriceFormat = ( defaultValue, extensions, args, validation ) => { const isOrderSummaryContext = args?.context === 'summary'; if ( ! isOrderSummaryContext ) { return defaultValue; } return ' per item'; }; registerCheckoutFilters( 'example-extension', { subtotalPriceFormat: modifySubtotalPriceFormat, } ); ``` #### Advanced example ```tsx const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifySubtotalPriceFormat = ( defaultValue, extensions, args, validation ) => { const isOrderSummaryContext = args?.context === 'summary'; if ( ! isOrderSummaryContext ) { return defaultValue; } if ( args?.cartItem?.name === 'Beanie with Logo' ) { return ' per warm beanie'; } if ( args?.cartItem?.name === 'Sunglasses' ) { return ' per cool sunglasses'; } return ' per item'; }; registerCheckoutFilters( 'example-extension', { subtotalPriceFormat: modifySubtotalPriceFormat, } ); ``` > Filters can be also combined. See [Combined filters](../available-filters/README.md) for an example. ### Screenshots | Before | After | |:---------------------------------------------------------------------:|:---------------------------------------------------------------------:| |![Before applying the Subtotal Price Format filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/3574e7ae-9857-4651-ac9e-e6b597e3a589) |![After applying the Subtotal Price Format filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/94e18439-6d6b-44a4-ade1-8302c5984641) | ## Cart object The Cart object of the filters above has the following keys: - _billingAddress_ `object` - The billing address object with the following keys: - _address_1_ `string` - The first line of the address. - _address_2_ `string` - The second line of the address. - _city_ `string` - The city of the address. - _company_ `string` - The company of the address. - _country_ `string` - The country of the address. - _email_ `string` - The email of the address. - _first_name_ `string` - The first name of the address. - _last_name_ `string` - The last name of the address. - _phone_ `string` - The phone of the address. - _postcode_ `string` - The postcode of the address. - _state_ `string` - The state of the address. - ~~_billingData_~~ `object` - The billing data object with the same keys as the `billingAddress` object. - _cartCoupons_ `array` - The cart coupons array. - _cartErrors_ `array` - The cart errors array. - _cartFees_ `array` - The cart fees array. - _cartHasCalculatedShipping_ `boolean` - Whether the cart has calculated shipping. - _cartIsLoading_ `boolean` - Whether the cart is loading. - _cartItemErrors_ `array` - The cart item errors array. - _cartItems_ `array` - The cart items array with cart item objects, see [Cart Item object](#cart-item-object). - _cartItemsCount_ `number` - The cart items count. - _cartItemsWeight_ `number` - The cart items weight. - _cartNeedsPayment_ `boolean` - Whether the cart needs payment. - _cartNeedsShipping_ `boolean` - Whether the cart needs shipping. - _cartTotals_ `object` - The cart totals object with the following keys: - _currency_code_ `string` - The currency code. - _currency_decimal_separator_ `string` - The currency decimal separator. - _currency_minor_unit_ `number` - The currency minor unit. - _currency_prefix_ `string` - The currency prefix. - _currency_suffix_ `string` - The currency suffix. - _currency_symbol_ `string` - The currency symbol. - _currency_thousand_separator_ `string` - The currency thousand separator. - _tax_lines_ `array` - The tax lines array with tax line objects with the following keys: - _name_ `string` - The name of the tax line. - _price_ `number` - The price of the tax line. - _rate_ `string` - The rate ID of the tax line. - _total_discount_ `string` - The total discount. - _total_discount_tax_ `string` - The total discount tax. - _total_fees_ `string` - The total fees. - _total_fees_tax_ `string` - The total fees tax. - _total_items_ `string` - The total items. - _total_items_tax_ `string` - The total items tax. - _total_price_ `string` - The total price. - _total_shipping_ `string` - The total shipping. - _total_shipping_tax_ `string` - The total shipping tax. - _total_tax_ `string` - The total tax. - _crossSellsProducts_ `array` - The cross sells products array with cross sells product objects. - _extensions_ `object` (default: `{}`) - The extensions object. - _isLoadingRates_ `boolean` - Whether the cart is loading shipping rates. - _paymentRequirements_ `array` - The payment requirements array. - _shippingAddress_ `object` - The shipping address object with the same keys as the `billingAddress` object. - _shippingRates_ `array` - The shipping rates array. ## Cart Item object The Cart Item object of the filters above has the following keys: - _backorders_allowed_ `boolean` - Whether backorders are allowed. - _catalog_visibility_ `string` - The catalog visibility. - _decsription_ `string` - The cart item description. - _extensions_ `object` (default: `{}`) - The extensions object. - _id_ `number` - The item ID. - _images_ `array` - The item images array. - _item_data_ `array` - The item data array. - _key_ `string` - The item key. - _low_stock_remaining_ `number` - The low stock remaining. - _name_ `string` - The item name. - _permalink_ `string` - The item permalink. - _prices_ `object` - The item prices object with the following keys: - _currency_code_ `string` - The currency code. - _currency_decimal_separator_ `string` - The currency decimal separator. - _currency_minor_unit_ `number` - The currency minor unit. - _currency_prefix_ `string` - The currency prefix. - _currency_suffix_ `string` - The currency suffix. - _currency_symbol_ `string` - The currency symbol. - _currency_thousand_separator_ `string` - The currency thousand separator. - _price_ `string` - The price. - _price_range_ `string` - The price range. - _raw_prices_ `object` - The raw prices object with the following keys: - _precision_ `number` - The precision. - _price_ `number` - The price. - _regular_price_ `number` - The regular price. - _sale_price_ `number` - The sale price. - _regular_price_ `string` - The regular price. - _sale_price_ `string` - The sale price. - _quantity_ `number` - The item quantity. - _quantity_limits_ `object` - The item quantity limits object with the following keys: - _editable_ `boolean` - Whether the quantity is editable. - _maximum_ `number` - The maximum quantity. - _minimum_ `number` - The minimum quantity. - _multiple_of_ `number` - The multiple of quantity. - _short_description_ `string` - The item short description. - _show_backorder_badge_ `boolean` - Whether to show the backorder badge. - _sku_ `string` - The item SKU. - _sold_individually_ `boolean` - Whether the item is sold individually. - _totals_ `object` - The item totals object with the following keys: - _currency_code_ `string` - The currency code. - _currency_decimal_separator_ `string` - The currency decimal separator. - _currency_minor_unit_ `number` - The currency minor unit. - _currency_prefix_ `string` - The currency prefix. - _currency_suffix_ `string` - The currency suffix. - _currency_symbol_ `string` - The currency symbol. - _currency_thousand_separator_ `string` - The currency thousand separator. - _line_subtotal_ `string` - The line subtotal. - _line_subtotal_tax_ `string` - The line subtotal tax. - _line_total_ `string` - The line total. - _line_total_tax_ `string` - The line total tax. - _type_ `string` - The item type. - _variation_ `array` - The item variation array. --- ## Totals footer item *Source: block-development/cart-and-checkout-blocks/available-filters/totals-footer-item.md* # Totals footer item The following Totals Footer Item filter are available: - `totalLabel` - `totalValue` ## `totalLabel` The following object is used in the filter: - [Cart object](#cart-object) ### Description The `totalLabel` filter allows to change the label of the total item in the footer of the Cart and Checkout blocks. ### Parameters - _defaultValue_ `string` (default: `Total`) - The total label. - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following keys: - _cart_ `object` - The cart object from `wc/store/cart`, see [Cart object](#cart-object). ### Returns - `string` - The updated total label. ### Code example ```ts const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyTotalLabel = ( defaultValue, extensions, args ) => { return 'Deposit due today'; }; registerCheckoutFilters( 'example-extension', { totalLabel: modifyTotalLabel, } ); ``` > Filters can be also combined. See [Combined filters](../available-filters/README.md) for an example. ### Screenshots | Before | After | |:---------------------------------------------------------------------:|:---------------------------------------------------------------------:| |![Before applying the Total Label filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/5b2fb8ab-db84-4ed0-a676-d5203edc84d2) |![After applying the Total Label filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/07955eea-cb17-48e9-9cb5-6548dd6a3b24) | ## `totalValue` The following object is used in the filter: - [Cart object](#cart-object) ### Description The `totalValue` filter allows to format the total price in the footer of the Cart and Checkout blocks. ### Parameters - _defaultValue_ `string` (default: `Total`) - The total label. - _extensions_ `object` (default: `{}`) - The extensions object. - _args_ `object` - The arguments object with the following keys: - _cart_ `object` - The cart object from `wc/store/cart`, see [Cart object](#cart-object). - _validation_ `boolean` - Checks if the return value contains the substring ``. ### Returns - `string` - The modified format of the total price, which must contain the substring ``, or the original price format. ### Code example ```ts const { registerCheckoutFilters } = window.wc.blocksCheckout; const modifyTotalsPrice = ( defaultValue, extensions, args, validation ) => { return 'Pay now'; }; registerCheckoutFilters( 'my-extension', { totalValue: modifyTotalsPrice, } ); ``` > Filters can be also combined. See [Combined filters](../available-filters/README.md) for an example. ### Screenshots | Before | After | |:---------------------------------------------------------------------:|:---------------------------------------------------------------------:| |![Before applying the Total Value filter](https://github.com/woocommerce/woocommerce/assets/3323310/4b788bdd-6fbd-406c-a9ad-4fb13f901c23) |![After applying the Total Value filter](https://github.com/woocommerce/woocommerce/assets/3323310/1b1b5f72-7f2f-4ee5-b2a4-1d8eb2208deb) | ## Cart object The Cart object of the filters above has the following keys: - _billingAddress_ `object` - The billing address object with the following keys: - _address_1_ `string` - The first line of the address. - _address_2_ `string` - The second line of the address. - _city_ `string` - The city of the address. - _company_ `string` - The company of the address. - _country_ `string` - The country of the address. - _email_ `string` - The email of the address. - _first_name_ `string` - The first name of the address. - _last_name_ `string` - The last name of the address. - _phone_ `string` - The phone of the address. - _postcode_ `string` - The postcode of the address. - _state_ `string` - The state of the address. - ~~_billingData_~~ `object` - The billing data object with the same keys as the `billingAddress` object. - _cartCoupons_ `array` - The cart coupons array. - _cartErrors_ `array` - The cart errors array. - _cartFees_ `array` - The cart fees array. - _cartHasCalculatedShipping_ `boolean` - Whether the cart has calculated shipping. - _cartIsLoading_ `boolean` - Whether the cart is loading. - _cartItemErrors_ `array` - The cart item errors array. - _cartItems_ `array` - The cart items array with cart item objects, see [Cart Item object](#cart-item-object). - _cartItemsCount_ `number` - The cart items count. - _cartItemsWeight_ `number` - The cart items weight. - _cartNeedsPayment_ `boolean` - Whether the cart needs payment. - _cartNeedsShipping_ `boolean` - Whether the cart needs shipping. - _cartTotals_ `object` - The cart totals object with the following keys: - _currency_code_ `string` - The currency code. - _currency_decimal_separator_ `string` - The currency decimal separator. - _currency_minor_unit_ `number` - The currency minor unit. - _currency_prefix_ `string` - The currency prefix. - _currency_suffix_ `string` - The currency suffix. - _currency_symbol_ `string` - The currency symbol. - _currency_thousand_separator_ `string` - The currency thousand separator. - _tax_lines_ `array` - The tax lines array with tax line objects with the following keys: - _name_ `string` - The name of the tax line. - _price_ `number` - The price of the tax line. - _rate_ `string` - The rate ID of the tax line. - _total_discount_ `string` - The total discount. - _total_discount_tax_ `string` - The total discount tax. - _total_fees_ `string` - The total fees. - _total_fees_tax_ `string` - The total fees tax. - _total_items_ `string` - The total items. - _total_items_tax_ `string` - The total items tax. - _total_price_ `string` - The total price. - _total_shipping_ `string` - The total shipping. - _total_shipping_tax_ `string` - The total shipping tax. - _total_tax_ `string` - The total tax. - _crossSellsProducts_ `array` - The cross sells products array with cross sells product objects. - _extensions_ `object` (default: `{}`) - The extensions object. - _isLoadingRates_ `boolean` - Whether the cart is loading shipping rates. - _paymentRequirements_ `array` - The payment requirements array. - _shippingAddress_ `object` - The shipping address object with the same keys as the `billingAddress` object. - _shippingRates_ `array` - The shipping rates array. ## Cart Item object The Cart Item object of the filters above has the following keys: - _backorders_allowed_ `boolean` - Whether backorders are allowed. - _catalog_visibility_ `string` - The catalog visibility. - _decsription_ `string` - The cart item description. - _extensions_ `object` (default: `{}`) - The extensions object. - _id_ `number` - The item ID. - _images_ `array` - The item images array. - _item_data_ `array` - The item data array. - _key_ `string` - The item key. - _low_stock_remaining_ `number` - The low stock remaining. - _name_ `string` - The item name. - _permalink_ `string` - The item permalink. - _prices_ `object` - The item prices object with the following keys: - _currency_code_ `string` - The currency code. - _currency_decimal_separator_ `string` - The currency decimal separator. - _currency_minor_unit_ `number` - The currency minor unit. - _currency_prefix_ `string` - The currency prefix. - _currency_suffix_ `string` - The currency suffix. - _currency_symbol_ `string` - The currency symbol. - _currency_thousand_separator_ `string` - The currency thousand separator. - _price_ `string` - The price. - _price_range_ `string` - The price range. - _raw_prices_ `object` - The raw prices object with the following keys: - _precision_ `number` - The precision. - _price_ `number` - The price. - _regular_price_ `number` - The regular price. - _sale_price_ `number` - The sale price. - _regular_price_ `string` - The regular price. - _sale_price_ `string` - The sale price. - _quantity_ `number` - The item quantity. - _quantity_limits_ `object` - The item quantity limits object with the following keys: - _editable_ `boolean` - Whether the quantity is editable. - _maximum_ `number` - The maximum quantity. - _minimum_ `number` - The minimum quantity. - _multiple_of_ `number` - The multiple of quantity. - _short_description_ `string` - The item short description. - _show_backorder_badge_ `boolean` - Whether to show the backorder badge. - _sku_ `string` - The item SKU. - _sold_individually_ `boolean` - Whether the item is sold individually. - _totals_ `object` - The item totals object with the following keys: - _currency_code_ `string` - The currency code. - _currency_decimal_separator_ `string` - The currency decimal separator. - _currency_minor_unit_ `number` - The currency minor unit. - _currency_prefix_ `string` - The currency prefix. - _currency_suffix_ `string` - The currency suffix. - _currency_symbol_ `string` - The currency symbol. - _currency_thousand_separator_ `string` - The currency thousand separator. - _line_subtotal_ `string` - The line subtotal. - _line_subtotal_tax_ `string` - The line subtotal tax. - _line_total_ `string` - The line total. - _line_total_tax_ `string` - The line total tax. - _type_ `string` - The item type. - _variation_ `array` - The item variation array. --- ## Available slots *Source: block-development/cart-and-checkout-blocks/available-slot-fills.md* # Available slots This document presents the list of available Slots that you can use for adding your custom content (Fill). If you want to add a new SlotFill component, check the [Checkout - Slot Fill document](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/client/blocks/packages/checkout/slot/README.md). To read more about Slot and Fill, check the [Slot and Fill document](./slot-fills.md). **Note About Naming:** Slots that are prefixed with `Experimental` are experimental and subject to change or remove. Once they graduate from the experimental stage, the naming would change and the `Experimental` prefix would be dropped. Check the [Feature Gating document](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/client/blocks/docs/internal-developers/blocks/feature-flags-and-experimental-interfaces.md) from more information. ## ExperimentalOrderMeta This Slot renders below the Checkout summary section and above the "Proceed to Checkout" button in the Cart. ```ts const { __ } = window.wp.i18n; const { registerPlugin } = window.wp.plugins; const { ExperimentalOrderMeta } = window.wc.blocksCheckout; const render = () => { return (
{ __( 'Yearly recurring total ...', 'YOUR-TEXTDOMAIN' ) }
); }; registerPlugin( 'slot-and-fill-examples', { render, scope: 'woocommerce-checkout', } ); ``` Cart: ![Example of ExperimentalOrderMeta in the Cart block](https://user-images.githubusercontent.com/1628454/154517779-117bb4e4-568e-413c-904c-855fc3450dfa.png) Checkout: ![Example of ExperimentalOrderMeta in the Checkout block](https://user-images.githubusercontent.com/1628454/154697224-de245182-6783-4914-81ba-1dbcf77292eb.png) ### Passed parameters - `cart`: `wc/store/cart` data but in `camelCase` instead of `snake_case`. [Object breakdown.](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/c00da597efe4c16fcf5481c213d8052ec5df3766/assets/js/type-defs/cart.ts#L172-L188) - `extensions`: external data registered by third-party developers using `ExtendSchema`. If you used `ExtendSchema` on `wc/store/cart` you would find your data under your namespace here. - `context`, equal to the name of the Block in which the fill is rendered: `woocommerce/cart` or `woocommerce/checkout` ## ExperimentalOrderShippingPackages This slot renders inside the shipping step of Checkout and inside the shipping options in Cart. ```ts const { __ } = window.wp.i18n; const { registerPlugin } = window.wp.plugins; const { ExperimentalOrderShippingPackages } = window.wc.blocksCheckout; const render = () => { return (
{ __( 'Express Shipping', 'YOUR-TEXTDOMAIN' ) }
); }; registerPlugin( 'slot-and-fill-examples', { render, scope: 'woocommerce-checkout', } ); ``` Cart: ![Example of ExperimentalOrderShippingPackages in the Cart block](https://user-images.githubusercontent.com/6165348/118399054-2b4dec80-b653-11eb-94a0-989e2e6e362a.png) Checkout: ![Example of ExperimentalOrderShippingPackages in the Checkout block](https://user-images.githubusercontent.com/6165348/118399133-90094700-b653-11eb-8ff0-c917947c199f.png) ### Passed parameters - `collapsible`: `Boolean|undefined` If a shipping package panel should be collapsible or not, this is false in Checkout and undefined in Cart. - `collapse`: `Boolean` If a panel should be collapsed by default, this is true if if panels are collapsible. - `showItems`: `Boolean|undefined` If we should show the content of each package, this is undefined in Cart and Checkout and is left to the actual package logic to decide. - `noResultsMessage`: A React element that you can render if there are no shipping options. - `renderOption`: a render function that takes a rate object and returns a render option. - `cart`: `wc/store/cart` data but in `camelCase` instead of `snake_case`. [Object breakdown.](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/c00da597efe4c16fcf5481c213d8052ec5df3766/assets/js/type-defs/cart.ts#L172-L188) - `extensions`: external data registered by third-party developers using `ExtendSchema`, if you used `ExtendSchema` on `wc/store/cart` you would find your data under your namespace here. - `components`: an object containing components you can use to render your own shipping rates, it contains `ShippingRatesControlPackage`. - `context`, equal to the name of the Block in which the fill is rendered: `woocommerce/cart` or `woocommerce/checkout` ## ExperimentalOrderLocalPickupPackages This slot renders inside the Checkout Pickup Options block in the Checkout block. It does not render in the Cart block. ```ts const { __ } = window.wp.i18n; const { registerPlugin } = window.wp.plugins; const { ExperimentalOrderLocalPickupPackages } = window.wc.blocksCheckout; const render = () => { return (
{ __( 'By using our convenient local pickup option, you can come to our store and pick up your order. We will send you and email when your order is ready for pickup.', 'YOUR-TEXTDOMAIN' ) }
); }; registerPlugin( 'slot-and-fill-examples', { render, scope: 'woocommerce-checkout', } ); ``` Checkout: ![Example of ExperimentalOrderLocalPickupPackages in the Checkout block](https://user-images.githubusercontent.com/5656702/222814945-a449d016-0621-4a70-b0f4-2ae1ce6487f1.png) ### Passed parameters - `renderPickupLocation`: a render function that renders the address details of a local pickup option. - `cart`: `wc/store/cart` data but in `camelCase` instead of `snake_case`. [Object breakdown.](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/c00da597efe4c16fcf5481c213d8052ec5df3766/assets/js/type-defs/cart.ts#L172-L188) - `extensions`: external data registered by third-party developers using `ExtendSchema`, if you used `ExtendSchema` on `wc/store/cart` you would find your data under your namespace here. - `components`: an object containing components you can use to render your own pickup rates, it contains `ShippingRatesControlPackage` and `RadioControl`. ## ExperimentalDiscountsMeta This slot renders below the `CouponCode` input. ```ts const { __ } = window.wp.i18n; const { registerPlugin } = window.wp.plugins; const { ExperimentalDiscountsMeta } = window.wc.blocksCheckout; const render = () => { return (
{ __( 'You have 98683 coins to spend ...', 'YOUR-TEXTDOMAIN' ) }
); }; registerPlugin( 'slot-and-fill-examples', { render, scope: 'woocommerce-checkout', } ); ``` Cart: ![Cart showing ExperimentalDiscountsMeta location](https://user-images.githubusercontent.com/5656702/122774218-ea27a880-d2a0-11eb-9450-11f119567f26.png) Checkout: ![Checkout showing ExperimentalDiscountsMeta location](https://user-images.githubusercontent.com/5656702/122779606-efd3bd00-d2a5-11eb-8c84-6525eca5d704.png) ### Passed parameters - `cart`: `wc/store/cart` data but in `camelCase` instead of `snake_case`. [Object breakdown.](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/c00da597efe4c16fcf5481c213d8052ec5df3766/assets/js/type-defs/cart.ts#L172-L188) - `extensions`: external data registered by third-party developers using `ExtendSchema`, if you used `ExtendSchema` on `wc/store/cart` you would find your data under your namespace here. - `context`, equal to the name of the Block in which the fill is rendered: `woocommerce/cart` or `woocommerce/checkout` --- ## Checkout flow and events *Source: block-development/cart-and-checkout-blocks/checkout-payment-methods/checkout-flow-and-events.md* # Checkout flow and events This document gives an overview of the flow for the checkout in the WooCommerce checkout block, and some general architectural overviews. The architecture of the Checkout Block is derived from the following principles: - A single source of truth for data within the checkout flow. - Provide a consistent interface for extension integrations (eg Payment methods). This interface protects the integrity of the checkout process and isolates extension logic from checkout logic. The checkout block handles _all_ communication with the server for processing the order. Extensions are able to react to and communicate with the checkout block via the provided interface. - Checkout flow state is tracked by checkout status. - Extensions are able to interact with the checkout flow via subscribing to emitted events. Here's a high level overview of the flow: ![checkout flow diagram](https://user-images.githubusercontent.com/1628454/113739726-f8c9df00-96f7-11eb-80f1-78e25ccc88cb.png) ## General Concepts ### Tracking flow through status At any point in the checkout lifecycle, components should be able to accurately detect the state of the checkout flow. This includes things like: - Is something loading? What is loading? - Is there an error? What is the error? - is the checkout calculating totals? Using simple booleans can be fine in some cases, but in others it can lead to complicated conditionals and bug prone code (especially for logic behaviour that reacts to various flow state). To surface the flow state, the block uses statuses that are tracked in the various contexts. _As much as possible_ these statuses are set internally in reaction to various actions so there's no implementation needed in children components (components just have to _consume_ the status not set status). The following statuses exist in the Checkout. #### Checkout Data Store Status There are various statuses that are exposed on the Checkout data store via selectors. All the selectors are detailed below and in the [Checkout API docs](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/client/blocks/docs/internal-developers/block-client-apis/checkout/checkout-api.md). You can use them in your component like so ```jsx const { useSelect } = window.wp.data; const { checkoutStore } = window.wc.wcBlocksData; const MyComponent = ( props ) => { const isComplete = useSelect( ( select ) => select( checkoutStore ).isComplete() ); // do something with isComplete }; ``` The following boolean flags available related to status are: **isIdle**: When the checkout status is `IDLE` this flag is true. Checkout will be this status after any change to checkout state after the block is loaded. It will also be this status when retrying a purchase is possible after processing happens with an error. **isBeforeProcessing**: When the checkout status is `BEFORE_PROCESSING` this flag is true. Checkout will be this status when the user submits checkout for processing. **isProcessing**: When the checkout status is `PROCESSING` this flag is true. Checkout will be this status when all the observers on the event emitted with the `BEFORE_PROCESSING` status are completed without error. It is during this status that the block will be sending a request to the server on the checkout endpoint for processing the order. **Note:** there are some checkout payment status changes that happen during this state as well (outlined in the `PaymentProvider` exposed statuses section). **isAfterProcessing**: When the checkout status is `AFTER_PROCESSING` this flag is true. Checkout will have this status after the block receives the response from the server side processing request. **isComplete**: When the checkout status is `COMPLETE` this flag is true. Checkout will have this status after all observers on the events emitted during the `AFTER_PROCESSING` status are completed successfully. When checkout is at this status, the shopper's browser will be redirected to the value of `redirectUrl` at that point (usually the `order-received` route). #### Special States The following booleans are exposed via the checkout provider. They are independent from each other and checkout statuses, but can be used in combination to react to various states in the checkout. ##### **isCalculating** `isCalculating` is true when the total is being re-calculated for the order, or when a plugin is intentionally disabling the checkout using the `disableCheckoutFor` action (covered in the next section). There are numerous things that might trigger a recalculation of the total—coupons being added or removed, shipping rates updating, shipping rates being selected, etc. Instead of having to check each of those individual states, you can reliably check if this boolean is true (calculating) or false (not calculating). What `isCalculating` affects: - Disables the "Place Order" button in the checkout block - Disables the "Proceed to Checkout" button in the cart block - Shows a loading state for Express Payment methods while calculations are pending ###### Controlling `isCalculating` with `disableCheckoutFor` You can programmatically control `isCalculating` using the `disableCheckoutFor` thunk: ```jsx const { dispatch } = window.wp.data; const { checkoutStore } = window.wc.wcBlocksData; // Example: Disable checkout while performing an async operation dispatch( checkoutStore ).disableCheckoutFor( async () => { // Your async operation here, e.g. validating data with an API await myAsyncOperation(); // No need to return anything - we only care about the promise resolving } ); ``` The thunk controls internal state, ensuring that the client won't be able to attempt completing the flow until the provided promise resolves, regardless of whether it succeeds or fails. ##### **hasError** `hasError` is true when anything in the checkout has created an error condition state. This might be validation errors, request errors, coupon application errors, payment processing errors etc. ### `ShippingProvider` Exposed Statuses The shipping context provider exposes everything related to shipping in the checkout. Included in this are a set of error statuses that inform what error state the shipping context is in and the error state is affected by requests to the server on address changes, rate retrieval and selection. Currently the error status may be one of `NONE`, `INVALID_ADDRESS` or `UNKNOWN` (note, this may change in the future). The status is exposed on the `currentErrorStatus` object provided by the `useShippingDataContext` hook. This object has the following properties on it: - `isPristine` and `isValid`: Both of these booleans are connected to the same error status. When the status is `NONE` the values for these booleans will be `true`. It basically means there is no shipping error. - `hasInvalidAddress`: When the address provided for shipping is invalid, this will be true. - `hasError`: This is `true` when the error status for shipping is either `UNKNOWN` or `hasInvalidAddress`. ### Payment Method Data Store Status The status of the payment lives in the payment data store. You can query the status with the following selectors: ```jsx const { select } = window.wp.data; const { paymentStore } = window.wc.wcBlocksData; const MyComponent = ( props ) => { const isPaymentIdle = select( paymentStore ).isPaymentIdle(); const isExpressPaymentStarted = select( paymentStore ).isExpressPaymentStarted(); const isPaymentProcessing = select( paymentStore ).isPaymentProcessing(); const isPaymentReady = select( paymentStore ).isPaymentReady(); const hasPaymentError = select( paymentStore ).hasPaymentError(); // do something with the boolean values }; ``` The status here will help inform the current state of _client side_ processing for the payment and are updated via the store actions at different points throughout the checkout processing cycle. _Client side_ means the state of processing any payments by registered and active payment methods when the checkout form is submitted via those payment methods registered client side components. It's still possible that payment methods might have additional server side processing when the order is being processed but that is not reflected by these statuses (more in the [payment method integration doc](./payment-method-integration.md)). The possible _internal_ statuses that may be set are: - `IDLE`: This is the status when checkout is initialized and there are payment methods that are not doing anything. This status is also set whenever the checkout status is changed to `IDLE`. - `EXPRESS_STARTED`: **Express Payment Methods Only** - This status is used when an express payment method has been triggered by the user clicking it's button. This flow happens before processing, usually in a modal window. - `PROCESSING`: This status is set when the checkout status is `PROCESSING`, checkout `hasError` is false, checkout is not calculating, and the current payment status is not `FINISHED`. When this status is set, it will trigger the payment processing event emitter. - `READY`: This status is set after all the observers hooked into the payment processing event have completed successfully. The `CheckoutProcessor` component uses this along with the checkout `PROCESSING` status to signal things are ready to send the order to the server with data for processing and to take payment - `ERROR`: This status is set after an observer hooked into the payment processing event returns an error response. This in turn will end up causing the checkout `hasError` flag to be set to true. ### Emitting Events Another tricky thing for extensibility, is providing opinionated, yet flexible interfaces for extensions to act and react to specific events in the flow. For stability, it's important that the core checkout flow _controls_ all communication to and from the server specific to checkout/order processing and leave extension specific requirements for the extension to handle. This allows for extensions to predictably interact with the checkout data and flow as needed without impacting other extensions hooking into it. One of the most reliable ways to implement this type of extensibility is via the usage of an events system. Thus the various context providers: - expose subscriber APIs for extensions to subscribe _observers_ to the events they want to react to. - emit events at specific points of the checkout flow that in turn will feed data to the registered observers and, in some cases, react accordingly to the responses from observers. One _**very important rule**_ when it comes to observers registered to any event emitter in this system is that they _cannot_ update context state. Updating state local to a specific component is okay but not any context or global state. The reason for this is that the observer callbacks are run sequentially at a specific point and thus subsequent observers registered to the same event will not react to any change in global/context state in earlier executed observers. ```jsx const unsubscribe = emitter( myCallback ); ``` You could substitute in whatever emitter you are registering for the `emitter` function. So for example if you are registering for the `onCheckoutValidation` event emitter, you'd have something like: ```jsx const unsubscribe = onCheckoutValidation( myCallback ); ``` You can also indicate what priority you want your observer to execute at. Lower priority is run before higher priority, so you can affect when your observer will run in the stack of observers registered to an emitter. You indicate priority via an number on the second argument: ```jsx const unsubscribe = onCheckoutValidation( myCallback, 10 ); ``` In the examples, `myCallback`, is your subscriber function. The subscriber function could receive data from the event emitter (described in the emitter details below) and may be expected to return a response in a specific shape (also described in the specific emitter details). The subscriber function can be a `Promise` and when the event emitter cycles through the registered observers it will await for any registered Promise to resolve. Finally, the return value of the call to the emitter function is an unsubscribe function that can be used to unregister your observer. This is especially useful in a React component context where you need to make sure you unsubscribe the observer on component unmount. An example is usage in a `useEffect` hook: ```jsx const MyComponent = ( { onCheckoutValidation } ) => { useEffect( () => { const unsubscribe = onCheckoutValidation( () => true ); return unsubscribe; }, [ onCheckoutValidation ] ); return null; }; ``` **`Event Emitter Utilities`** There are a bunch of utility methods that can be used related to events. These are available in `assets/js/base/context/event-emit/utils.ts` and can be imported as follows: ```jsx import { noticeContexts, responseTypes, shouldRetry, } from '@woocommerce/base-context'; import { isSuccessResponse, isErrorResponse, isFailResponse, } from '@woocommerce/types'; ``` The helper functions are described below: - `isSuccessResponse`, `isErrorResponse` and `isFailResponse`: These are helper functions that receive a value and report via boolean whether the object is a type of response expected. For event emitters that receive responses from registered observers, a `type` property on the returned object from the observer indicates what type of response it is and event emitters will react according to that type. So for instance if an observer returned `{ type: 'success' }` the emitter could feed that to `isSuccessResponse` and it would return `true`. You can see an example of this being implemented for the [payment processing emitted event here](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/34e17c3622637dbe8b02fac47b5c9b9ebf9e3596/assets/js/base/context/cart-checkout/payment-methods/payment-method-data-context.js#L281-L307). - `noticeContexts`: This is an object containing properties referencing areas where notices can be targeted in the checkout. The object has the following properties: - `PAYMENTS`: This is a reference to the notice area in the payment methods step. - `EXPRESS_PAYMENTS`: This is a reference to the notice area in the express payment methods step. - `responseTypes`: This is an object containing properties referencing the various response types that can be returned by observers for some event emitters. It makes it easier for autocompleting the types and avoiding typos due to human error. The types are `SUCCESS`, `FAIL`, `ERROR`. The values for these types also correspond to the [payment status types](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/34e17c3622637dbe8b02fac47b5c9b9ebf9e3596/src/Payments/PaymentResult.php#L21) from the [checkout endpoint response from the server](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/34e17c3622637dbe8b02fac47b5c9b9ebf9e3596/src/RestApi/StoreApi/Schemas/CheckoutSchema.php#L103-L113). - `shouldRetry`: This is a function containing the logic whether the checkout flow should allow the user to retry the payment after a previous payment failed. It receives the `response` object and by default checks whether the `retry` property is true/undefined or false. Refer to the [`onCheckoutSuccess`](#oncheckoutsuccess) documentation for more details. Note: `noticeContexts` and `responseTypes` are exposed to payment methods via the `emitResponse` prop given to their component: ```jsx const MyPaymentMethodComponent = ( { emitResponse } ) => { const { noticeContexts, responseTypes } = emitResponse; // other logic for payment method... }; ``` The following event emitters are available to extensions to register observers to: ### `onCheckoutValidation` Observers registered to this event emitter will receive nothing as an argument. Also, all observers will be executed before the checkout handles the responses from the emitters. Observers registered to this emitter can return `true` if they have nothing to communicate back to checkout, `false` if they want checkout to go back to `IDLE` status state, or an object with any of the following properties: - `errorMessage`: This will be added as an error notice on the checkout context. - `validationErrors`: This will be set as inline validation errors on checkout fields. If your observer wants to trigger validation errors it can use the following shape for the errors: - This is an object where keys are the property names the validation error is for (that correspond to a checkout field, eg `country` or `coupon`) and values are the error message describing the validation problem. This event is emitted when the checkout status is `BEFORE_PROCESSING` (which happens at validation time, after the checkout form submission is triggered by the user - or Express Payment methods). If all observers return `true` for this event, then the checkout status will be changed to `PROCESSING`. This event emitter subscriber can be obtained via the checkout context using the `useCheckoutContext` hook or to payment method extensions as a prop on their registered component: _For internal development:_ ```jsx import { useCheckoutContext } from '@woocommerce/base-contexts'; import { useEffect } from '@wordpress/element'; const Component = () => { const { onCheckoutValidation } = useCheckoutContext(); useEffect( () => { const unsubscribe = onCheckoutValidation( () => true ); return unsubscribe; }, [ onCheckoutValidation ] ); return null; }; ``` _For registered payment method components:_ ```jsx const { useEffect } = window.wp.element; const PaymentMethodComponent = ( { eventRegistration } ) => { const { onCheckoutValidation } = eventRegistration; useEffect( () => { const unsubscribe = onCheckoutValidation( () => true ); return unsubscribe; }, [ onCheckoutValidation ] ); }; ``` _For anything else:_ ```jsx const { onCheckoutValidation } = wc.blocksCheckoutEvents; useEffect( () => { const unsubscribe = onCheckoutValidation( () => true ); return unsubscribe; }, [ onCheckoutValidation ] ); ``` ### ~~`onPaymentProcessing`~~ This is now deprecated and replaced by the `onPaymentSetup` event emitter. ### `onPaymentSetup` This event emitter was fired when the payment method context status is `PROCESSING` and that status is set when the checkout status is `PROCESSING`, checkout `hasError` is false, checkout is not calculating, and the current payment status is not `FINISHED`. This event emitter will execute through each registered observer (passing in nothing as an argument) _until_ an observer returns a non-truthy value at which point it will _abort_ further execution of registered observers. When a payment method returns a non-truthy value, if it returns a valid response type the event emitter will update various internal statuses according to the response. Here's the possible response types that will get handled by the emitter: #### Success A successful response should be given when the user's entered data is correct and the payment checks are successful. A response is considered successful if, at a minimum, it is an object with this shape: ```js const successResponse = { type: 'success' }; ``` When a success response is returned, the payment method context status will be changed to `SUCCESS`. In addition, including any of the additional properties will result in extra actions: - `paymentMethodData`: The contents of this object will be included as the value for `payment_data` when checkout sends a request to the checkout endpoint for processing the order. This is useful if a payment method does additional server side processing. - `billingAddress`: This allows payment methods to update any billing data information in the checkout (typically used by Express payment methods) so it's included in the checkout processing request to the server. This data should be in the [shape outlined here](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/client/blocks/assets/js/settings/shared/default-fields.ts). - `shippingAddress`: This allows payment methods to update any shipping data information for the order (typically used by Express payment methods) so it's included in the checkout processing request to the server. This data should be in the [shape outlined here](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/client/blocks/assets/js/settings/shared/default-fields.ts). If `billingAddress` or `shippingAddress` properties aren't in the response object, then the state for the data is left alone. #### Fail A fail response should be given when there is an error with the payment processing. A response is considered a fail response when it is an object with this shape: ```js const failResponse = { type: 'failure' }; ``` When a fail response is returned by an observer, the payment method context status will be changed to `FAIL`. In addition, including any of the following properties will result in extra actions: - `message`: The string provided here will be set as an error notice in the checkout. - `messageContext`: If provided, this will target the given area for the error notice (this is where `noticeContexts` mentioned earlier come in to play). Otherwise the notice will be added to the `noticeContexts.PAYMENTS` area. - `paymentMethodData`: (same as for success responses). - `billingAddress`: (same as for success responses). #### Error An error response should be given when there is an error with the user input on the checkout form. A response is considered an error response when it is an object with this shape: ```js const errorResponse = { type: 'error' }; ``` When an error response is returned by an observer, the payment method context status will be changed to `ERROR`. In addition, including any of the following properties will result in extra actions: - `message`: The string provided here will be set as an error notice. - `messageContext`: If provided, this will target the given area for the error notice (this is where `noticeContexts` mentioned earlier come in to play). Otherwise, the notice will be added to the `noticeContexts.PAYMENTS` area. - `validationErrors`: This will be set as inline validation errors on checkout fields. If your observer wants to trigger validation errors it can use the following shape for the errors: - This is an object where keys are the property names the validation error is for (that correspond to a checkout field, eg `country` or `coupon`) and values are the error message describing the validation problem. If the response object doesn't match any of the above conditions, then the fallback is to set the payment status as `SUCCESS`. When the payment status is set to `SUCCESS` and the checkout status is `PROCESSING`, the `CheckoutProcessor` component will trigger the request to the server for processing the order. This event emitter subscriber can be obtained via the checkout context using the `usePaymentEventsContext` hook or to payment method extensions as a prop on their registered component: _For internal development:_ ```jsx import { usePaymentEventsContext } from '@woocommerce/base-contexts'; import { useEffect } from '@wordpress/element'; const Component = () => { const { onPaymentSetup } = usePaymentEventsContext(); useEffect( () => { const unsubscribe = onPaymentSetup( () => true ); return unsubscribe; }, [ onPaymentSetup ] ); return null; }; ``` _For registered payment method components:_ ```jsx const { useEffect } = window.wp.element; const PaymentMethodComponent = ( { eventRegistration } ) => { const { onPaymentSetup } = eventRegistration; useEffect( () => { const unsubscribe = onPaymentSetup( () => true ); return unsubscribe; }, [ onPaymentSetup ] ); }; ``` ### `onCheckoutSuccess` This event emitter is fired when the checkout status is `AFTER_PROCESSING` and the checkout `hasError` state is false. The `AFTER_PROCESSING` status is set by the `CheckoutProcessor` component after receiving a response from the server for the checkout processing request. Observers registered to this event emitter will receive the following object as an argument: ```js const onCheckoutProcessingData = { redirectUrl, orderId, customerId, orderNotes, paymentResult, }; ``` The properties are: - `redirectUrl`: This is a string that is the url the checkout will redirect to as returned by the processing on the server. - `orderId`: Is the id of the current order being processed. - `customerId`: Is the id for the customer making the purchase (that is attached to the order). - `orderNotes`: This will be any custom note the customer left on the order. - `paymentResult`: This is the value of [`payment_result` from the /checkout StoreApi response](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/34e17c3622637dbe8b02fac47b5c9b9ebf9e3596/src/RestApi/StoreApi/Schemas/CheckoutSchema.php#L103-L138). The data exposed on this object is (via the object properties): - `paymentStatus`: Whatever the status is for the payment after it was processed server side. Will be one of `success`, `failure`, `pending`, `error`. - `paymentDetails`: This will be an arbitrary object that contains any data the payment method processing server side sends back to the client in the checkout processing response. Payment methods are able to hook in on the processing server side and set this data for returning. This event emitter will invoke each registered observer until a response from any of the registered observers does not equal `true`. At that point any remaining non-invoked observers will be skipped and the response from the observer triggering the abort will be processed. This emitter will handle a `success` response type (`{ type: success }`) by setting the checkout status to `COMPLETE`. Along with that, if the response includes `redirectUrl` then the checkout will redirect to the given address. This emitter will also handle a `failure` response type or an `error` response type and if no valid type is detected it will treat it as an `error` response type. In all cases, if there are the following properties in the response, additional actions will happen: - `message`: This string will be added as an error notice. - `messageContext`: If present, the notice will be configured to show in the designated notice area (otherwise it will just be a general notice for the checkout block). - `retry`: If this is `true` or not defined, then the checkout status will be set to `IDLE`. This basically means that the error is recoverable (for example try a different payment method) and so checkout will be reset to `IDLE` for another attempt by the shopper. If this is `false`, then the checkout status is set to `COMPLETE` and the checkout will redirect to whatever is currently set as the `redirectUrl`. - `redirectUrl`: If this is present, then the checkout will redirect to this url when the status is `COMPLETE`. If all observers return `true`, then the checkout status will just be set to `COMPLETE`. This event emitter subscriber can be obtained via the checkout context using the `useCheckoutContext` hook or to payment method extensions as a prop on their registered component: _For internal development:_ ```jsx import { useCheckoutContext } from '@woocommerce/base-contexts'; import { useEffect } from '@wordpress/element'; const Component = () => { const { onCheckoutSuccess } = useCheckoutContext(); useEffect( () => { const unsubscribe = onCheckoutSuccess( () => true ); return unsubscribe; }, [ onCheckoutSuccess ] ); return null; }; ``` _For registered payment method components:_ ```jsx const { useEffect } = window.wp.element; const PaymentMethodComponent = ( { eventRegistration } ) => { const { onCheckoutSuccess } = eventRegistration; useEffect( () => { const unsubscribe = onCheckoutSuccess( () => true ); return unsubscribe; }, [ onCheckoutSuccess ] ); }; ``` _For anything else:_ ```jsx const { onCheckoutSuccess } = wc.blocksCheckoutEvents; useEffect( () => { const unsubscribe = onCheckoutSuccess( () => true ); return unsubscribe; }, [ onCheckoutSuccess ] ); ``` ### `onCheckoutFail` This event emitter is fired when the checkout status is `AFTER_PROCESSING` and the checkout `hasError` state is `true`. The `AFTER_PROCESSING` status is set by the `CheckoutProcessor` component after receiving a response from the server for the checkout processing request. Observers registered to this emitter will receive the same data package as those registered to `onCheckoutSuccess`. The response from the first observer returning a value that does not `===` true will be handled similarly as the `onCheckoutSuccess` except it only handles when the type is `error` or `failure`. If all observers return `true`, then the checkout status will just be set to `IDLE` and a default error notice will be shown in the checkout context. This event emitter subscriber can be obtained via the checkout context using the `useCheckoutContext` hook or to payment method extensions as a prop on their registered component: _For internal development:_ ```jsx import { useCheckoutContext } from '@woocommerce/base-contexts'; import { useEffect } from '@wordpress/element'; const Component = () => { const { onCheckoutFail } = useCheckoutContext(); useEffect( () => { const unsubscribe = onCheckoutFail( () => true ); return unsubscribe; }, [ onCheckoutFail ] ); return null; }; ``` _For registered payment method components:_ ```jsx const { useEffect } = window.wp.element; const PaymentMethodComponent = ( { eventRegistration } ) => { const { onCheckoutFail } = eventRegistration; useEffect( () => { const unsubscribe = onCheckoutFail( () => true ); return unsubscribe; }, [ onCheckoutFail ] ); }; ``` _For anything else:_ ```jsx const { onCheckoutFail } = wc.blocksCheckoutEvents; useEffect( () => { const unsubscribe = onCheckoutFail( () => true ); return unsubscribe; }, [ onCheckoutFail ] ); ``` ### `onShippingRateSuccess` This event emitter is fired when shipping rates are not loading and the shipping data context error state is `NONE` and there are shipping rates available. This event emitter doesn't care about any registered observer response and will simply execute all registered observers passing them the current shipping rates retrieved from the server. ### `onShippingRateFail` This event emitter is fired when shipping rates are not loading and the shipping data context error state is `UNKNOWN` or `INVALID_ADDRESS`. This event emitter doesn't care about any registered observer response and will simply execute all registered observers passing them the current error status in the context. ### `onShippingRateSelectSuccess` This event emitter is fired when a shipping rate selection is not being persisted to the server and there are selected rates available and the current error status in the context is `NONE`. This event emitter doesn't care about any registered observer response and will simply execute all registered observers passing them the current selected rates. ### `onShippingRateSelectFail` This event emitter is fired when a shipping rate selection is not being persisted to the server and the shipping data context error state is `UNKNOWN` or `INVALID_ADDRESS`. This event emitter doesn't care about any registered observer response and will simply execute all registered observers passing them the current error status in the context. --- ## Filtering payment methods in the Checkout block *Source: block-development/cart-and-checkout-blocks/checkout-payment-methods/filtering-payment-methods.md* # Filtering payment methods in the Checkout block ## The problem You're an extension developer, and your extension is conditionally hiding payment gateways on the checkout step. You need to be able to hide payment gateways on the Checkout block using a front-end extensibility point. ### The solution WooCommerce Blocks provides a function called `registerPaymentMethodExtensionCallbacks` which allows extensions to register callbacks for specific payment methods to determine if they can make payments. ### Importing #### Aliased import ```js import { registerPaymentMethodExtensionCallbacks } from '@woocommerce/blocks-registry'; ``` #### `wc global` ```js const { registerPaymentMethodExtensionCallbacks } = window.wc.wcBlocksRegistry; ``` ### Signature | Parameter | Description | Type | | ----------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | | `namespace` | Unique string to identify your extension. Choose something that eliminates a name collision with another extension. | `string` | | `callbacks` | An object containing callbacks registered for different payment methods | Record< string, CanMakePaymentExtensionCallback > | Read more below about [callbacks](#callbacks-registered-for-payment-methods). #### Extension namespace collision When trying to register callbacks under an extension namespace already used with `registerPaymentMethodExtensionCallbacks`, the registration will be aborted and you will be notified that you are not using a unique namespace. This will be shown in the JavaScript console. ### Usage example ```js registerPaymentMethodExtensionCallbacks( 'my-hypothetical-extension', { cod: ( arg ) => { return arg.shippingAddress.city === 'Berlin'; }, cheque: ( arg ) => { return false; }, } ); ``` ### Callbacks registered for payment methods Extensions can register only one callback per payment method: ```text payment_method_name: ( arg ) => {...} ``` `payment_method_name` is the value of the [name property](/docs/block-development/cart-and-checkout-blocks/checkout-payment-methods/payment-method-integration) used when the payment method was registered with WooCommerce Blocks. The registered callbacks are used to determine whether the corresponding payment method should be available as an option for the shopper. The function will be passed an object containing data about the current order. ```ts type CanMakePaymentExtensionCallback = ( cartData: CanMakePaymentArgument ) => boolean; ``` Each callback will have access to the information bellow ```ts interface CanMakePaymentArgument { cart: Cart; cartTotals: CartTotals; cartNeedsShipping: boolean; billingAddress: CartResponseBillingAddress; shippingAddress: CartResponseShippingAddress; selectedShippingMethods: Record< string, unknown >; paymentRequirements: Array< string >; } ``` If you need data that is not available in the parameter received by the callback you can consider [exposing your data in the Store API](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/client/blocks/docs/third-party-developers/extensibility/rest-api/extend-rest-api-add-data.md). ## Filtering payment methods using requirements ### The problem Your extension has added functionality to your store in such a way that only specific payment gateways can process orders that contain certain products. Using the example of `Bookings` if the shopper adds a `Bookable` product to their cart, for example a stay in a hotel, and you, the merchant, want to confirm all bookings before taking payment. You would still need to capture the customer's checkout details but not their payment method at that point. ### The solution To allow the shopper to check out without entering payment details, but still require them to fill in the other checkout details it is possible to create a new payment method which will handle carts containing a `Bookable` item. Using the `supports` configuration of payment methods it is possible to prevent other payment methods (such as credit card, PayPal etc.) from being used to check out, and only allow the one your extension has added to appear in the Checkout block. For more information on how to register a payment method with WooCommerce Blocks, please refer to the [Payment method integration](./payment-method-integration.md) documentation. ### Basic usage Following the documentation for registering payment methods linked above, you should register your payment method with a unique `supports` feature, for example `booking_availability`. This will be used to isolate it and prevent other methods from displaying. First you will need to create a function that will perform the checks on the cart to determine what the specific payment requirements of the cart are. Below is an example of doing this for our `Bookable` products. Then you will need to use the `register_payment_requirements` on the `ExtendSchema` class to tell the Checkout block to execute a callback to check for requirements. ### Putting it all together This code example assumes there is some class called `Pseudo_Booking_Class` that has the `cart_contains_bookable_product` method available. The implementation of this method is not relevant here. ```php /** * Check the content of the cart and add required payment methods. * * * @return array list of features required by cart items. */ function inject_payment_feature_requirements_for_cart_api() { // Cart contains a bookable product, so return an array containing our requirement of booking_availability. if ( Pseudo_Booking_Class::cart_contains_bookable_product() ) { return array( 'booking_availability' ); } // No bookable products in the cart, no need to add anything. return array(); } ``` To summarise the above: if there's a bookable product in the cart then this function will return an array containing `booking_availability`, otherwise it will return an empty array. The next step will tell the `ExtendSchema` class to execute this callback when checking which payment methods to display. To do this you could use the following code: ```php add_action('woocommerce_blocks_loaded', function() { woocommerce_store_api_register_payment_requirements( array( 'data_callback' => 'inject_payment_feature_requirements_for_cart_api', ) ); }); ``` It is important to note the comment in this code block, you must not instantiate your own version of `ExtendSchema`. If you've added your payment method correctly with the correct `supports` values then when you reach the checkout page with a `Bookable` item in your cart, any method that does not `supports` the `booking_availability` requirement should not display, while yours, the one that _does_ support this requirement _will_ display. --- ## Payment method integration *Source: block-development/cart-and-checkout-blocks/checkout-payment-methods/payment-method-integration.md* # Payment method integration ## Client Side integration The client side integration consists of an API for registering both _regular_ and _express_ payment methods. In both cases, the client side integration is done using registration methods exposed on the `blocks-registry` API. You can access this via the `wc` global in a WooCommerce environment (`wc.wcBlocksRegistry`). > Note: In your build process, you could do something similar to what is done in the blocks repository which [aliases this API as an external on `@woocommerce/blocks-registry`](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/e089ae17043fa525e8397d605f0f470959f2ae95/bin/webpack-helpers.js#L16-L35). ## Express Payment Methods Express payment methods are payment methods that consist of a one-button payment process initiated by the shopper such as Stripe, ApplePay, or GooglePay. ![Express Payment Area](https://user-images.githubusercontent.com/1429108/79565636-17fed500-807f-11ea-8e5d-9af32e43b71d.png) ### Registration To register an express payment method, you use the `registerExpressPaymentMethod` function from the blocks registry. ```js const { registerExpressPaymentMethod } = window.wc.wcBlocksRegistry; ``` If you're using an aliased import for `@woocommerce/blocks-registry`, you can import the function like this: ```js import { registerExpressPaymentMethod } from '@woocommerce/blocks-registry'; ``` The registry function expects a JavaScript object with options specific to the payment method: ```js registerExpressPaymentMethod( options ); ``` The options you feed the configuration instance should be an object in this shape (see `ExpressPaymentMethodConfiguration` typedef): ```js const options = { name: 'my_payment_method', title: 'My Mayment Method', description: 'A setence or two about your payment method', gatewayId: 'gateway-id', label: , content: , edit: , canMakePayment: () => true, paymentMethodId: 'my_payment_method', supports: { features: [], style: [], }, }; ``` #### `ExpressPaymentMethodConfiguration` | Option | Type | Description | Required | |-----------------------|------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------| | `name` | String | Unique identifier for the gateway client side. | Yes | | `title` | String | Human readable name of your payment method. Displayed to the merchant in the editor. | No | | `description` | String | One or two sentences describing your payment gateway. Displayed to the merchant in the editor. | No | | `gatewayId` | String | ID of the Payment Gateway registered server side. Used to direct the merchant to the right settings page within the editor. If this is not provided, the merchant will be redirected to the general Woo payment settings page. | No | | `content` | ReactNode | React node output in the express payment method area when the block is rendered in the frontend. Receives props from the checkout payment method interface. | Yes | | `edit` | ReactNode | React node output in the express payment method area when the block is rendered in the editor. Receives props from the payment method interface to checkout (with preview data). | Yes | | `canMakePayment` | Function | Callback to determine whether the payment method should be available for the shopper. | Yes | | `paymentMethodId` | String | Identifier accompanying the checkout processing request to the server. Used to identify the payment method gateway class for processing the payment. | No | | `supports:features` | Array | Array of payment features supported by the gateway. Used to crosscheck if the payment method can be used for the cart content. Defaults to `['products']` if no value is provided. | No | | `supports:style` | Array | This is an array of style variations supported by the express payment method. These are styles that are applied across all the active express payment buttons and can be controlled from the express payment block in the editor. Supported values for these are one of `['height', 'borderRadius']`. | No | #### The `canMakePayment` option `canMakePayment` is a callback to determine whether the payment method should be available as an option for the shopper. The function will be passed an object containing data about the current order. ```ts canMakePayment( { cart: Cart, cartTotals: CartTotals, cartNeedsShipping: boolean, shippingAddress: CartShippingAddress, billingAddress: CartBillingAddress, selectedShippingMethods: Record, paymentRequirements: string[], } ) ``` `canMakePayment` returns a boolean value. If your gateway needs to perform async initialization to determine availability, you can return a promise (resolving to boolean). This allows a payment method to be hidden based on the cart, e.g. if the cart has physical/shippable products (example: [`Cash on delivery`](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/e089ae17043fa525e8397d605f0f470959f2ae95/assets/js/payment-method-extensions/payment-methods/cod/index.js#L48-L70)); or for payment methods to control whether they are available depending on other conditions. `canMakePayment` only runs on the frontend of the Store. In editor context, rather than use `canMakePayment`, the editor will assume the payment method is available (true) so that the defined `edit` component is shown to the merchant. **Keep in mind this function could be invoked multiple times in the lifecycle of the checkout and thus any expensive logic in the callback provided on this property should be memoized.** ### Button Attributes for Express Payment Methods This API provides a way to synchronise the look and feel of the express payment buttons for a coherent shopper experience. Express Payment Methods must prefer the values provided in the `buttonAttributes`, and use it's own configuration settings as backup when the buttons are rendered somewhere other than the Cart or Checkout block. For example, in your button component, you would do something like this: ```js // Get your extension specific settings and set defaults if not available let { borderRadius = '4', height = '48', } = getButtonSettingsFromConfig(); // In a cart & checkout block context, we receive `buttonAttributes` as a prop which overwrite the extension specific settings if ( typeof buttonAttributes !== 'undefined' ) { height = buttonAttributes.height; borderRadius = buttonAttributes.borderRadius; } ... return '; $html .= ''; } return $html; } add_filter( 'woocommerce_loop_add_to_cart_link', 'YOUR_PREFIX_quantity_inputs_for_woocommerce_loop_add_to_cart_link', 10, 2 ); } ``` --- ## Rename a country *Source: code-snippets/rename-a-country.md* # Rename a country Rename a country in your countries list: ```php if ( ! function_exists( 'YOUR_PREFIX_rename_country' ) ) { /** * Rename a country * * @param array $countries Existing country names * @return array $countries Updated country name(s) */ function YOUR_PREFIX_rename_country( $countries ) { $countries['IE'] = __( 'Ireland (Changed)', 'YOUR-TEXTDOMAIN' ); return $countries; } add_filter( 'woocommerce_countries', 'YOUR_PREFIX_rename_country' ); } ``` --- ## SSL and HTTPS and WooCommerce *Source: code-snippets/ssl_and_https_and_woocommerce_websites_behind_load_balanacers_or_reverse_proxies.md* # SSL and HTTPS and WooCommerce ## Websites behind load balancers or reverse proxies WooCommerce uses the `is_ssl()` WordPress function to verify if your website using SSL or not. `is_ssl()` checks if the connection is via HTTPS or on Port 443. However, this won't work for websites behind load balancers, especially websites hosted at Network Solutions. For details, read [WordPress is_ssl() function reference notes](https://codex.wordpress.org/Function_Reference/is_ssl#Notes). Websites behind load balancers or reverse proxies that support `HTTP_X_FORWARDED_PROTO` can be fixed by adding the following code to the `wp-config.php` file, above the require_once call: ```php if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' == $_SERVER['HTTP_X_FORWARDED_PROTO'] ) { $_SERVER['HTTPS'] = 'on'; } ``` **Note:** If you use CloudFlare, you need to configure it. Check their documentation. --- ## Unhook and remove WooCommerce emails *Source: code-snippets/unhook--remove-woocommerce-emails.md* # Unhook and remove WooCommerce emails This code allows you to unhook and remove the default WooCommerce emails. ```php if ( ! function_exists( 'YOUR_PREFIX_unhook_woocommerce_emails' ) ) { /** * Callback for woocommerce_email action hook * * @param WC_Email $email_class An Email class instance. * @return void */ function YOUR_PREFIX_unhook_woocommerce_emails( $email_class ) { /** * Hooks for sending emails during store events. */ remove_action( 'woocommerce_low_stock_notification', array( $email_class, 'low_stock' ) ); remove_action( 'woocommerce_no_stock_notification', array( $email_class, 'no_stock' ) ); remove_action( 'woocommerce_product_on_backorder_notification', array( $email_class, 'backorder' ) ); // New order emails. remove_action( 'woocommerce_order_status_pending_to_processing_notification', array( $email_class->emails['WC_Email_New_Order'], 'trigger' ) ); remove_action( 'woocommerce_order_status_pending_to_completed_notification', array( $email_class->emails['WC_Email_New_Order'], 'trigger' ) ); remove_action( 'woocommerce_order_status_pending_to_on-hold_notification', array( $email_class->emails['WC_Email_New_Order'], 'trigger' ) ); remove_action( 'woocommerce_order_status_failed_to_processing_notification', array( $email_class->emails['WC_Email_New_Order'], 'trigger' ) ); remove_action( 'woocommerce_order_status_failed_to_completed_notification', array( $email_class->emails['WC_Email_New_Order'], 'trigger' ) ); remove_action( 'woocommerce_order_status_failed_to_on-hold_notification', array( $email_class->emails['WC_Email_New_Order'], 'trigger' ) ); // Processing order emails. remove_action( 'woocommerce_order_status_pending_to_processing_notification', array( $email_class->emails['WC_Email_Customer_Processing_Order'], 'trigger' ) ); remove_action( 'woocommerce_order_status_pending_to_on-hold_notification', array( $email_class->emails['WC_Email_Customer_On_Hold_Order'], 'trigger' ) ); // Completed order emails. remove_action( 'woocommerce_order_status_completed_notification', array( $email_class->emails['WC_Email_Customer_Completed_Order'], 'trigger' ) ); // Note emails. remove_action( 'woocommerce_new_customer_note_notification', array( $email_class->emails['WC_Email_Customer_Note'], 'trigger' ) ); } } add_action( 'woocommerce_email', 'YOUR_PREFIX_unhook_woocommerce_emails' ); ``` --- ## Uninstall and remove all WooCommerce Data *Source: code-snippets/uninstall_remove_all_woocommerce_data.md* # Uninstall and remove all WooCommerce Data The WooCommerce plugin can be uninstalled like any other WordPress plugin. By default, the WooCommerce data is left in place though. If you need to remove *all* WooCommerce data as well, including products, order data, coupons, etc., you need to to modify the site's `wp-config.php` *before* deactivating and deleting the WooCommerce plugin. As this action is destructive and permanent, the information is provided as is. WooCommerce Support cannot help with this process or anything that happens as a result. To fully remove all WooCommerce data from your WordPress site, open `wp-config.php`, scroll down to the bottom of the file, and add the following constant on its own line above `/* That's all, stop editing. */`. ```php define( 'WC_REMOVE_ALL_DATA', true ); /* That's all, stop editing! Happy publishing. */ ``` Then, once the changes are saved to the file, when you deactivate and delete WooCommerce, all of its data is removed from your WordPress site database. ![Uninstall WooCommerce WPConfig](https://woocommerce.com/wp-content/uploads/2020/03/uninstall_wocommerce_plugin_wpconfig.png) --- ## Useful core functions *Source: code-snippets/useful-functions.md* # Useful core functions WooCommerce core functions are available on both front-end and admin. They can be found in `includes/wc-core-functions.php` and can be used by themes in plugins. ## Conditional Functions WooCommerce conditional functions help determine the current query/page. ### is_woocommerce Returns true if on a page which uses WooCommerce templates (cart and checkout are standard pages with shortcodes and thus are not included). ```php is_woocommerce() ``` ### is_shop Returns true when viewing the product type archive (shop). ```php is_shop() ``` ### is_product Returns true when viewing a single product. ```php is_product() ``` ## Coupon Functions ### wc_get_coupon_code_by_id Get coupon code by coupon ID. ```php wc_get_coupon_code_by_id( $id ) ``` The argument `$id` is the coupon ID. ### wc_get_coupon_id_by_code Gets the coupon ID by code. ```php wc_get_coupon_id_by_code( $code, $exclude = 0 ) ``` `$code` is the coupon code and `$exclude` is to exclude an ID from the check if you're checking existence. ## User Functions ### wc_customer_bought_product Checks if a customer has bought an item. The check can be done by email or user ID or both. ```php wc_customer_bought_product( $customer_email, $user_id, $product_id ) ``` ### wc_get_customer_total_spent Gets the total spent for a customer. ```php wc_get_customer_total_spent( $user_id ) ``` `$user_id` is the user ID of the customer. ### wc_get_customer_order_count Gets the total orders for a customer. ```php wc_get_customer_order_count( $user_id ) ``` `$user_id` is the user ID of the customer. ## Formatting Functions ### wc_get_dimension Takes a measurement `$dimension` measured in WooCommerce's dimension unit and converts it to the target unit `$to_unit`. ```php wc_get_dimension( $dimension, $to_unit, $from_unit = '' ) ``` Example usages: ```php wc_get_dimension( 55, 'in' ); wc_get_dimension( 55, 'in', 'm' ); ``` ### wc_get_weight Takes a weight `$weight` weighed in WooCommerce's weight unit and converts it to the target weight unit `$to_unit`. ```php wc_get_weight( $weight, $to_unit, $from_unit = '' ) ``` Example usages: ```php wc_get_weight( 55, 'kg' ); wc_get_weight( 55, 'kg', 'lbs' ); ``` ### wc_clean Clean variables using `sanitize_text_field`. Arrays are cleaned recursively. Non-scalar values are ignored. ```php wc_clean( $var ) ``` ### wc_price Formats a passed price with the correct number of decimals and currency symbol. ```php wc_price( $price, $args = array() ) ``` The ` $args` array has an option called ` ex_tax_label` - if true then an `excluding tax` message will be appended. ## Order Functions ### wc_get_orders This function is the standard way of retrieving orders based on certain parameters. This function should be used for order retrieval so that when we move to custom tables, functions still work. ```php wc_get_orders( $args ) ``` [Arguments and usage](https://github.com/woocommerce/woocommerce/wiki/wc_get_orders-and-WC_Order_Query) ### wc_get_order This is the main function for returning orders, uses the `WC_Order_Factory` class. ```php wc_get_order( $the_order = false ) ``` The `the_order` parameter can be a post object or post ID of the order. ### wc_orders_count Returns the orders count of a specific order status. ```php wc_orders_count( $status, string $type = '' ) ``` ### wc_order_search Searches orders based on the given `$term`. ```php wc_order_search( $term ) ``` ## Page Functions ### wc_get_page_id Gets a WooCommerce page ID by name, e.g. thankyou ```php wc_get_page_id( $page ) ``` ### wc_get_endpoint_url Gets the URL for an `$endpoint`, which varies depending on permalink settings. ```php wc_get_endpoint_url( $endpoint, $value = '', $permalink = '' ) ``` ## Product Functions ### wc_get_products This function is the standard way of retrieving products based on certain parameters. ```php wc_get_products( $args ) ``` [Arguments and usage](https://github.com/woocommerce/woocommerce/wiki/wc_get_products-and-WC_Product_Query) ### wc_get_product This is the main function for returning products. It uses the `WC_Product_Factory` class. ```php wc_get_product( $the_product = false ) ``` The argument `$the_product` can be a post object or post ID of the product. ### wc_get_product_ids_on_sale Returns an array containing the IDs of the products that are on sale. ```php wc_get_product_ids_on_sale() ``` ### wc_get_featured_product_ids Returns an array containing the IDs of the featured products. ```php wc_get_featured_product_ids() ``` ### wc_get_related_products Gets the related products for product based on product category and tags. ```php wc_get_related_products( $product_id, $limit = 5, $exclude_ids = array() ) ``` ## Account Functions ### wc_get_account_endpoint_url Gets the account endpoint URL. ```php wc_get_account_endpoint_url( $endpoint ) ``` ## Attribute Functions ### wc_get_attribute_taxonomies Gets the taxonomies of product attributes. ```php wc_get_attribute_taxonomies() ``` ### wc_attribute_taxonomy_name Gets the taxonomy name for a given product attribute. ```php wc_attribute_taxonomy_name( $attribute_name ) ``` ### wc_attribute_taxonomy_id_by_name Gets a product attribute ID by name. ```php wc_attribute_taxonomy_id_by_name( $name ) ``` ## REST Functions ### wc_rest_prepare_date_response Parses and formats a date for ISO8601/RFC3339. ```php wc_rest_prepare_date_response( $date, $utc = true ) ``` Pass `$utc` as `false` to get local/offset time. ### wc_rest_upload_image_from_url Uploads an image from a given URL. ```php wc_rest_upload_image_from_url( $image_url ) ``` ### wc_rest_urlencode_rfc3986 Encodes a `$value` according to RFC 3986. ```php wc_rest_urlencode_rfc3986( $value ) ``` ### wc_rest_check_post_permissions Checks permissions of posts on REST API. ```php wc_rest_check_post_permissions( $post_type, $context = 'read', $object_id = 0 ) ``` The available values for `$context` which is the request context are `read`, `create`, `edit`, `delete` and `batch`. --- ## Using NGINX server to protect your upload directory *Source: code-snippets/using_nginx_server_to_protect_your_uploads_directory.md* # Using NGINX server to protect your upload directory If you using NGINX server for your site along with **X-Accel-Redirect/X-Sendfile** or **Force Downloads** download method, it is necessary that you add this configuration for better security: ```php # Protect WooCommerce upload folder from being accessed directly. # You may want to change this config if you are using "X-Accel-Redirect/X-Sendfile" or "Force Downloads" method for downloadable products. # Place this config towards the end of "server" block in NGINX configuration. location ~* /wp-content/uploads/woocommerce_uploads/ { if ( $upstream_http_x_accel_redirect = "" ) { return 403; } internal; } ``` And this the configuration in case you are using **Redirect only** download method: ```php # Protect WooCommerce upload folder from being accessed directly. # You may want to change this config if you are using "Redirect Only" method for downloadable products. # Place this config towards the end of "server" block in NGINX configuration. location ~* /wp-content/uploads/woocommerce_uploads/ { autoindex off; } ``` If you do not know which web server you are using, please reach out to your host along with a link to this support page. --- ## CSS SASS coding guidelines and naming conventions *Source: contribution/contributing/CSS-SASS-coding-guidelines-and-naming-conventions.md* # CSS SASS coding guidelines and naming conventions Our guidelines are based on those used in [Calypso](https://github.com/Automattic/wp-calypso) which itself follows the BEM methodology. Refer to [this doc](https://wpcalypso.wordpress.com/devdocs/docs/coding-guidelines/css.md?term=css) for full details. There are a few differences in WooCommerce however which are outlined below; ## Prefixing As a WordPress plugin WooCommerce has to play nicely with WordPress core and other plugins / themes. To minimise conflict potential all classes should be prefixed with `.woocommerce-`. ## Class names Calypso is built in React and uses component names to formulate CSS class names. WooCommerce Core has none of these components so uses a more traditional [BEM](http://getbem.com/) approach to [naming classes](http://cssguidelin.es/#bem-like-naming). When adding classes just remember; * **Block** - Standalone entity that is meaningful on its own. * **Element** - Parts of a block and have no standalone meaning. They are semantically tied to its block. * **Modifier** - Flags on blocks or elements. Use them to change appearance or behaviour. ### Example * `.woocommerce-loop {}` (block). * `.woocommerce-loop-product {}` (nested block). * `.woocommerce-loop-product--sale {}` (modifier). * `.woocommerce-loop-product__link {}` (element). * `.woocommerce-loop-product__title {}` (element). * `.woocommerce-loop-product__price {}` (element). * `.woocommerce-loop-product__rating {}` (element). * `.woocommerce-loop-product__button-add-to-cart {}` (element). * `.woocommerce-loop-product__button-add-to-cart--added {}` (modifier). **Note:** `.woocommerce-loop-product` is not the chosen classname _because_ the block is nested within `.woocommerce-loop`. It's to be specific so that we can have separate classes for single products, cart products etc. _Nested blocks do not need to inherit their parents full name_. You can read more about BEM key concepts [in the BEM methodology documentation](https://en.bem.info/methodology/key-concepts/). #### TL;DR * Follow the [WP Coding standards for CSS](https://make.wordpress.org/core/handbook/best-practices/coding-standards/css/) unless it contradicts anything here. * Follow [Calypso guidelines](https://wpcalypso.wordpress.com/devdocs/docs/coding-guidelines/css.md?term=css). * Use BEM for [class names](https://en.bem.info/methodology/naming-convention/). * Prefix all the things. --- ## Contribute to Woo *Source: contribution/contributing/README.md* # Contribute to Woo Interested in joining the Woo contributor community? The links in this doc summarize and direct you to the order of operations you will need to make your first contribution. If you are a seasoned WooCommerce developer, feel free to skip ahead and utilize the template links, or the reference docs and guides below. ## Contributing to WooCommerce Core The WooCommerce core plugin code can be found in our [monorepo](https://github.com/woocommerce/woocommerce). Here you can contribute to: - [WooCommerce Core Plugin](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce) - [WooCommerce Blocks](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/client/blocks) ### Code of Conduct Contributing to an open source project requires cooperation amongst individuals and organizations all working to make our project a stable and safe place to build and ask questions. Please thoroughly read our [Code of Conduct](https://github.com/woocommerce/woocommerce/blob/trunk/SECURITY.md) to get familiar with our standards and processes. ### Contributor Guidelines Our [contributor guidelines](https://github.com/woocommerce/woocommerce/blob/trunk/.github/CONTRIBUTING.md) layout the first steps to contributing. - [Prerequisites and developer tools to get started](https://github.com/woocommerce/woocommerce/blob/trunk/README.md#getting-started) - [PNPM commands, plugin development environment packages, and troubleshooting](https://github.com/woocommerce/woocommerce/blob/trunk/DEVELOPMENT.md) - [Coding standards, E2E testing links](https://github.com/woocommerce/woocommerce/blob/trunk/.github/CONTRIBUTING.md) ### Templates, Bug Reports, and Feature Requests - [Pull Request template](https://github.com/woocommerce/woocommerce/blob/trunk/.github/PULL_REQUEST_TEMPLATE.md) - [Core Issue template](https://github.com/woocommerce/woocommerce/blob/trunk/.github/ISSUE_TEMPLATE.md) - [Bug Report template](https://github.com/woocommerce/woocommerce/blob/trunk/.github/ISSUE_TEMPLATE/1-bug-report.yml) - [Enhancement template](https://github.com/woocommerce/woocommerce/blob/trunk/.github/ISSUE_TEMPLATE/2-enhancement.yml) - [Feature requests](https://woocommerce.com/feature-requests/woocommerce/) ### Security Security and safety for data management are incredibly important to us at Woo. Please check out the [Automattic security policy](https://automattic.com/security/) to learn about our foundational requirements. Please report any vulnerabilities or security issues by reading through Woo's [security policy](https://github.com/woocommerce/woocommerce/blob/trunk/SECURITY.md). --- ## Critical flows within the WooCommerce Core API *Source: contribution/contributing/api-critical-flows.md* # Critical flows within the WooCommerce Core API In our documentation, we've pinpointed the essential user flows within the WooCommerce Core API. These flows serve as the compass for our testing initiatives, aiding us in concentrating our efforts where they matter most. They also provide invaluable insights into assessing the ramifications of modifications and determining issue priorities. It's important to note that these flows remain dynamic, evolving in lockstep with the platform. They regularly undergo updates, additions, and re-prioritization to stay aligned with the evolving needs of our system. ## Products | Route | Flow name | Endpoint | Test File | |----------|----------------------------|--------------------------------|-------------------------------------------------------------| | Products | Can view all products | `/wp-json/wc/v3/products` | `tests/api-core-tests/tests/products/product-list.test.js` | | Products | Can search products | `/wp-json/wc/v3/products` | `tests/api-core-tests/tests/products/product-list.test.js` | | Products | Can add a simple product | `/wp-json/wc/v3/products` | `tests/api-core-tests/tests/products/products-crud.test.js` | | Products | Can add a variable product | `/wp-json/wc/v3/products` | `tests/api-core-tests/tests/products/products-crud.test.js` | | Products | Can add a virtual product | `/wp-json/wc/v3/products` | `tests/api-core-tests/tests/products/products-crud.test.js` | | Products | Can view a single product | `/wp-json/wc/v3/products/{id}` | `tests/api-core-tests/tests/products/products-crud.test.js` | | Products | Can update a product | `/wp-json/wc/v3/products/{id}` | `tests/api-core-tests/tests/products/products-crud.test.js` | | Products | Can delete a product | `/wp-json/wc/v3/products/{id}` | `tests/api-core-tests/tests/products/products-crud.test.js` | ## Orders | Route | Flow name | Endpoints | Test File | |--------|------------------------------------------------------------------|------------------------------|-----------------------------------------------------------| | Orders | Can create an order | `/wp-json/wc/v3/orders` | `tests/api-core-tests/tests/orders/orders-crud.test.js` | | Orders | Can view a single order | `/wp-json/wc/v3/orders/{id}` | `tests/api-core-tests/tests/orders/orders-crud.test.js` | | Orders | Can update an order | `/wp-json/wc/v3/orders/{id}` | `tests/api-core-tests/tests/orders/orders-crud.test.js` | | Orders | Can delete an order | `/wp-json/wc/v3/orders/{id}` | `tests/api-core-tests/tests/orders/orders-crud.test.js` | | Orders | Can view all orders | `/wp-json/wc/v3/orders` | `tests/api-core-tests/tests/orders/orders.test.js` | | Orders | Can search orders | `/wp-json/wc/v3/orders` | `tests/api-core-tests/tests/orders/order-search.test.js` | | Orders | Can add new Order complex - multiple product types & tax classes | `/wp-json/wc/v3/orders` | `tests/api-core-tests/tests/orders/order-complex.test.js` | ## Refunds | Route | Flow name | Endpoints | Test File | |---------|---------------------|--------------------------------------|-----------------------------------------------------| | Refunds | Can refund an order | `/wp-json/wc/v3/orders/{id}/refunds` | `tests/api-core-tests/tests/refunds/refund.test.js` | ## Coupons | Route | Flow name | Endpoints | Test File | |---------|---------------------------|--------------------------------------|------------------------------------------------------| | Coupons | Can create a coupon | `/wp-json/wc/v3/coupons` | `tests/api-core-tests/tests/coupons/coupons.test.js` | | Coupons | Can update a coupon | `/wp-json/wc/v3/coupons/{id}` | `tests/api-core-tests/tests/coupons/coupons.test.js` | | Coupons | Can delete a coupon | `/wp-json/wc/v3/coupons/{id}` | `tests/api-core-tests/tests/coupons/coupons.test.js` | | Coupons | Can add a coupon to order | `/wp-json/wc/v3/orders/{id}/coupons` | `tests/api-core-tests/tests/coupons/coupons.test.js` | ## Shipping | Route | Flow name | Endpoints | Test File | |------------------|-----------------------------------------------|----------------------------------------------|--------------------------------------------------------------| | Shipping zones | Can create shipping zones | `/wp-json/wc/v3/shipping/zones` | `tests/api-core-tests/tests/shipping/shipping-zones.test.js` | | Shipping methods | Can create shipping method to a shipping zone | `/wp-json/wc/v3/shipping/zones/{id}/methods` | n/a | | Shipping classes | Can create a product shipping class | `/wp-json/wc/v3/products/shipping_classes` | `tests/api-core-tests/tests/products/products-crud.test.js` | --- ## Common issues *Source: contribution/contributing/common-issues.md* # Common issues This page aims to document a comprehensive list of known issues, commonly encountered problems, and their solutions or workarounds. If you have encountered an issue that is not mentioned here and should be, please don't hesitate to add to the list. ## Composer error on `Automattic\Jetpack\Autoloader\AutoloadGenerator` ```bash [ErrorException] Declaration of Automattic\Jetpack\Autoloader\AutoloadGenerator::dump(Composer\Config $config, Composer\Repository\Inst alledRepositoryInterface $localRepo, Composer\Package\PackageInterface $mainPackage, Composer\Installer\InstallationMa nager $installationManager, $targetDir, $scanPsrPackages = false, $suffix = NULL) should be compatible with Composer\A utoload\AutoloadGenerator::dump(Composer\Config $config, Composer\Repository\InstalledRepositoryInterface $localRepo, Composer\Package\RootPackageInterface $rootPackage, Composer\Installer\InstallationManager $installationManager, $targ etDir, $scanPsrPackages = false, $suffix = '') ``` A recent [change](https://github.com/composer/composer/commit/b574f10d9d68acfeb8e36cad0b0b25a090140a3b#diff-67d1dfefa9c7b1c7e0b04b07274628d812f82cd82fae635c0aeba643c02e8cd8) in composer 2.0.7 made our autoloader incompatible with the new `AutoloadGenerator` signature. Try to downgrading to composer 2.0.6 by using `composer self-update 2.0.6`. ## VVV: HostsUpdater vagrant plugin error ```bash ...vagrant-hostsupdater/HostsUpdater.rb:126:in ``digest': no implicit conversion of nil into String (TypeError) ``` You might be running an unsupported version of Vagrant. At the time of writing, VVV works with Vagrant 2.2.7. Please check VVV's [requirements](https://github.com/Varying-Vagrant-Vagrants/VVV#minimum-system-requirements). ## VVV: `install-wp-tests.sh` error ```bash mysqladmin: CREATE DATABASE failed; error: 'Access denied for user 'wp'@'localhost' to database 'wordpress-one-tests'' ``` To fix: - Open MySQL with `sudo mysql`. - Run `GRANT ALL PRIVILEGES ON * . * TO 'wp'@'localhost';`. Exit by typing `exit;`. - Run the `install-wp-tests.sh` script again. ## Timeout / 404 errors while running e2e tests ```bash Store owner can complete onboarding wizard › can complete the product types section TimeoutError: waiting for function failed: timeout 30000ms exceeded 1 | export const waitForElementCount = function ( page, domSelector, count ) { > 2 | return page.waitForFunction( | ^ 3 | ( domSelector, count ) => { 4 | return document.querySelectorAll( domSelector ).length === count; 5 | }, ``` Timeouts or 404 errors in the e2e tests signal that the existing build might be broken. Run `npm install && npm run clean && npm run build` to generate a fresh build. It should also be noted that some of our npm scripts also remove the current build, so it's a good practice to always run a build before running e2e tests. ## Docker container couldn't be built when attempting e2e test ```bash Thu Dec 3 11:55:56 +08 2020 - Docker container is still being built Thu Dec 3 11:56:06 +08 2020 - Docker container is still being built Thu Dec 3 11:56:16 +08 2020 - Docker container is still being built Thu Dec 3 11:56:26 +08 2020 - Docker container couldn't be built npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! @woocommerce/e2e-environment@0.1.6 test:e2e: `bash ./bin/wait-for-build.sh && ./bin/e2e-test-integration.js` npm ERR! Exit status 1 ``` Ensure that Docker is running. While the script says `Docker container is still being built`, it is not actually responsible for running Docker; it's just waiting for an existing docker instance to respond. Run `npm run docker:up` if it's not. ## Set up WooCommerce Payments dev mode Add this to `wp-config.php`: ```php define( 'WCPAY_DEV_MODE', true ); ``` Also see [this document](https://woocommerce.com/document/woopayments/testing-and-troubleshooting/sandbox-mode/). ## WooCommerce Admin install timestamp To get the install timestamp (used in `wc_admin_active_for()` in `NoteTraits` for example) try this SQL: ```sql SELECT * FROM wp_options WHERE option_name = 'woocommerce_admin_install_timestamp' ``` ## Reset the onboarding wizard Delete the `woocommerce_onboarding_profile` option: ```sql DELETE FROM wp_options WHERE option_name = 'woocommerce_onboarding_profile' ``` ## Enable tracks debugging in the console ```javascript localStorage.setItem( 'debug', 'wc-admin:tracks' ); ``` and set Chrome's log level "verbose" to checked. ## Running PHP unit tests using Vagrant (VVV) 1. SSH into Vagrant box (`vagrant ssh`) 2. `cd /srv/www//public_html/wp-content/plugins/woocommerce-admin` 3. Set up: `bin/install-wp-tests.sh wc-admin-tests root root` 4. Fast tests: `./vendor/bin/phpunit --group fast` 5. All tests: `./vendor/bin/phpunit` You might need to `composer install` if `phpunit` doesn't exist. --- ## How to assess the impact of a pull request *Source: contribution/contributing/deciding-pr-high-impact.md* # How to assess the impact of a pull request Deciding if a Pull Request should be declared High-Impact is a complex task. To achieve it, we need to assess and estimate the impact that the changes introduced in the Pull Request have in WooCommerce, which is usually a subjective task and sometimes inaccurate, due to the huge knowledge it demands of the WooCommerce product details, technical details and even customers issues history. On this page, we will share some guidelines to help you assess the impact degree of a Pull Request. ## You should mark a Pull Request as High-Impact if - It adds a **new feature** to WooCommerce, except if it's behind a feature flag. - Modifies **critical functionality** (see the [critical flows list](https://github.com/woocommerce/woocommerce/wiki/Critical-Flows)). - It fixes a **high-priority bug** (this includes Blocks fix releases core version bumps). - It contains a **security fix**. - Updates **SQL queries**. - Touches any of the **$_REQUEST** family of variables. - Any kind of **data migration/update**. - Changes to **emails** sent from WooCommerce. - Changes to WooCommerce **hooks/actions/filters**. - Changes to **REST API endpoints**. - It's a **big PR** (i.e. adds several changes in many files). - It has **i18n changes** (for example, any file from `woocommerce/i18n` is modified). ## You should not mark a Pull Request as High-Impact if - It only updates automated tests, things related to infrastructure not included in the WooCommerce release package, or other projects in the monorepo not included in the release package. - It only contains readme or changelog changes. - Fixes a low-priority bug such as a typo etc. - Doesn't need to be verified in multiple environment types. - Regular scheduled (not a fix release) core version bumps for the Blocks package (as testing will already be scheduled). - It's part of a feature that hasn't been released as a whole yet (i.e. it's behind a feature flag currently in progress). ## My PR is High-Impact. What's next? If your PR is High-Impact, be sure to label it with `impact: high` and the WooCommerce Core team will keep special considerations for testing it. --- ## Deprecation in core *Source: contribution/contributing/deprecation-in-core.md* # Deprecation in core Deprecation is a method of discouraging usage of a feature or practice in favour of something else without breaking backwards compatibility or totally prohibiting its usage. To quote the Wikipedia article on Deprecation: > While a deprecated software feature remains in the software, its use may raise warning messages recommending alternative practices; deprecated status may also indicate the feature will be removed in the future. Features are deprecated rather than immediately removed, to provide backward compatibility and give programmers time to bring affected code into compliance with the new standard. There are various reasons why a function, method, or feature may be deprecated. To name a few: - We may want to remove a function we do not use any longer. - We may decide to change or improve how a function works, but due to breaking backwards compatibility we need to deprecate the old function instead. - We may want to standardise naming conventions. - We may find opportunities to merge similar functions to avoid reusing the same code in difference places. Whilst deprecation notices are not ideal or attractive, they are just _warnings_ - not errors. _*Store owners:* deprecation warnings do not mean your store is broken, it just serves as a reminder that code will need to be updated._ ## How do we deprecate functions? When we deprecate something in WooCommerce, we take a few actions to make it clear to developers and to maintain backwards compatibility. 1. We add a docblock to the function or method showing what version the function was deprecated in, e.g., `@deprecated 2.x.x`. 2. We add a warning notice using our own `wc_deprecated_function` function that shows what version, what function, and what replacement is available. More on that in a bit. 3. We remove usage of the deprecated function throughout the codebase. The function or method itself is not removed from the codebase. This preserves backwards compatibility until removed - usually over a year or several major releases into the future. We mentioned `wc_deprecated_function` above - this is our own wrapper for the `_deprecated_function` WordPress function. It works very similar except for that it forces a log entry instead of displaying it - regardless of the value of `WP_DEBUG` during AJAX events - so that AJAX requests are not broken by the notice. ## What happens when a deprecated function is called? If an extension or theme uses a deprecated function, you may see a warning like the following example: ```bash Notice: woocommerce_show_messages is deprecated since version 2.1! Use wc_print_notices instead. in /srv/www/wordpress-default/wp-includes/functions.php on line 3783 ``` This tells you what is deprecated, since when, where, and what replacement is available. Notices and warnings are usually shown inline, but there are some plugins you can use to collect and show them nicely in the footer of your site. Consider, for example, [Query Monitor](https://wordpress.org/plugins/query-monitor/). ### Warnings in production (store owners - read this!) Showing PHP notices and warnings (or any error for that matter) is highly discouraged on your production stores. They can reveal information about your setup that a malicious user could exploit to gain access to your store. Make sure they are hidden from public view and optionally logged instead. In WordPress you can do this by adding or modifying some constants in `wp-config.php`: ```php define( 'WP_DEBUG', false ); ``` On some hosts, errors may still be visible due to the hosts configuration. To force them to not display you might need to add this to `wp-config.php` as well: ```php @ini_set( 'display_errors', 0 ); ``` To log notices instead of displaying them, use: ```php define( 'WP_DEBUG', true ); define( 'WP_DEBUG_LOG', true ); define( 'WP_DEBUG_DISPLAY', false ); ``` The default location of the WordPress error log is `wp-content/debug.log`. Note that this log can be publicly accessible, which could also pose a security risk. To keep it private, you can use a plugin or define a custom path in `wp-config.php`. ```php 1 string use numbered args. e.g. `Test %s string %s.` would be `Test %1$s string %2$s.` 3. Use sentence case. e.g. `Some Thing` should be `Some thing`. 4. Avoid HTML. If needed, insert the HTML using sprintf. For more information, see WP core document [i18n for WordPress Developers](https://codex.wordpress.org/I18n_for_WordPress_Developers). --- ## WooCommerce Git flow *Source: contribution/contributing/woocommerce-git-flow.md* # WooCommerce Git flow For core development, we use the following structure and flow. **1.** Five weeks before the release we tag an alpha. We publish it for community testing, but this milestone doesn't imply a feature freeze. Issues found in the alpha are fixed with pull requests targetting `trunk` as usual. ![Git Flow WooCommerce uses for core development - tagging alpha](https://developer.woocommerce.com/wp-content/uploads/sites/2/2025/02/woo-git-flow-1.png) **2.** Three weeks before the release we create a `release/x.y` branch off trunk and declare feature freeze: no additional pull requests will be accepted for the `x.y` release. We also tag a beta from the `release/x.y` branch. ![Git Flow WooCommerce uses for core development - tagging beta](https://developer.woocommerce.com/wp-content/uploads/sites/2/2025/02/woo-git-flow-2.png) **3.** If issues are found in the beta they are fixed with pull requests targeting the `release/x.y` branch. ![Git Flow WooCommerce uses for core development - fixing issues in the beta](https://developer.woocommerce.com/wp-content/uploads/sites/2/2025/02/woo-git-flow-3.png) **4.** At the end of every week during the beta cycle (at least, optionally more often) the release branch is merged back to `trunk` so that any fixed applied to the beta are present in future releases too: ![Git Flow WooCommerce uses for core development - merging beta to trunk](https://developer.woocommerce.com/wp-content/uploads/sites/2/2025/02/woo-git-flow-4.png) **5.** One week before the release we tag a Release Candidate. As with the beta, fixes for further issues discovered are handled with pull requests to the release branch. ![Git Flow WooCommerce uses for core development - tagging RC](https://developer.woocommerce.com/wp-content/uploads/sites/2/2025/02/woo-git-flow-5.png) **6.** Finally, on the release day the release branch is merged one more time to `trunk`. ## Point releases Point releases are those tagged `x.y.z` where `z` is not zero. These are handled similarly to the beta and release candidate of the main release: * Pull requests are merged to the `release/x.y` branch (no dedicated branch is created). * The release branch is merged back to `trunk` when the development for a point release is in progress, and also when the release happens. ## Branch naming Prefixes determine the type of branch, and include: * fix/ * feature/ * add/ * update/ * release/ When creating a **fix branch**, use the correct prefix and the issue number. Example: ```text fix/12345 ``` Alternatively you can summarise the change: ```text fix/shipping-tax-rate-saving ``` --- ## Contributing technical documentation *Source: contribution/contributing-docs/contributing-docs.md* # Contributing technical documentation Thanks for helping improve WooCommerce's developer documentation. Our docs are powered by Docusaurus, and live inside the [`woocommerce/docs/`](https://github.com/woocommerce/woocommerce/tree/trunk/docs) folder of the monorepo. This guide walks you through the structure, tooling, and process for contributing effectively. ## Getting started > This guide presumes that you're familiar with basic Git and GitHub functionality, that you're signed into a GitHub account, and that you have Git setup locally. If you're new to GitHub, we recommend reading their [quickstart](https://docs.github.com/en/get-started/quickstart/hello-world) and [working with forks](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) guides before getting started. ### Initial setup 1. Fork the [WooCommerce monorepo](https://github.com/woocommerce/woocommerce) on GitHub. If asked, you can safely check the `copy the trunk branch only` option. 2. [Clone the fork that you just created](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository). This will allow you to edit it locally. ### Making changes 1. Prior to making any changes, ensure your `trunk` branch is up to date with the monorepo's `trunk` [by syncing it](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork). 2. For each modification you'd like to make, create a new branch off `trunk` in your fork that starts with `docs/`. For example, if you're adding a doc about improving extension performance, you could call your branch `docs/improve-extension-performance`. 3. Create or edit markdown files inside the appropriate folder under `docs/`. 4. If needed, update the folder's `_category_.json` (for sidebar label/position). 5. Run a build to verify changes, confirm that the sitemaps and llms-txt files are updated, and detect markdown linting errors and broken links (link checking only happens on build): ```bash npm run build ``` ### Opening a pull request 1. Commit and push your changes to your fork. 2. [Open a pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) to woocommerce/woocommerce, targeting the trunk branch. 3. Use a descriptive title, and fill out the PR template. Include: * Rationale for new files or categories * A note about any sidebar or structure changes 4. The WooCommerce Developer Advocacy team will review and merge your changes. ## Docs folder anatomy ### Tooling and configuration * **Supporting tooling and config** lives in: * [`woocommerce/docs/_docu-tools/`](https://github.com/woocommerce/woocommerce/blob/trunk/docs/_docu-tools/) * **Top-level sidebar and navbar** are configured in: * [`sidebars.ts`](https://github.com/woocommerce/woocommerce/blob/trunk/docs/_docu-tools/sidebars.ts) * [`docusaurus.config.ts`](https://github.com/woocommerce/woocommerce/blob/trunk/docs/_docu-tools/docusaurus.config.ts) ### Documentation files * **Docs location**: All documentation lives inside of [`woocommerce/docs/`](https://github.com/woocommerce/woocommerce/blob/trunk/docs/) * **Each folder is sidebar or top nav category**, for example: `getting-started`, `code-snippets`, etc. * **Sidebar configuration** for each category is managed using a `_category_.json` file inside each category folder: ```json { "position": 7, "label": "Code snippets" } ``` ## Adding images All documentation images are stored in: ```bash docs/_docu-tools/static/img/doc_images/ ``` To include an image in a markdown file, reference it like this: ```markdown ![Alt text](/img/doc_images/your-image-name.png) ``` ## Creating New Categories Before creating a new category, you should consider whether it is truly necessary to do so. Where possible, you should default to creating content in an existing category. If you do need to create a new category, follow these steps: 1. Inside the `/docs` folder, create a sub-folder with a descriptive name. For example, if you wanted to create a `Checkout design guidelines` section, you'd create a folder called `/docs/checkout-design-guidelines`. 2. Create a `_category_.json` file inside each category folder and give it a position it should have in the sidebar as well as a label: ```json { "position": 10, "label": "Checkout design guidelines" } ``` When creating new categories, please include rationale about why the category was created in your pull request's description. ## Writing guidelines and references * Use short, URL-friendly file names (kebab-case, no spaces) * Avoid pasting from rich text editors like Google Docs, which may introduce invalid characters * Check our [docs style guide](style-guide) for detailed writing guidelines * Reference the [Docusaurus documentation](https://docusaurus.io/docs) for additional guidance --- ## Technical Documentation Style Guide *Source: contribution/contributing-docs/style-guide.md* # Technical Documentation Style Guide This style guide is intended to provide guidelines for creating effective and user-friendly tutorials and how-to guides for WooCommerce technical documentation that will live in repo and be editable and iterative by open source contributors and WooCommerce teams. ## Writing style ### Language style - It's important to use clear and concise language that is easy to understand. Use active voice and avoid using jargon or technical terms that may be unfamiliar to the user. The tone should be friendly and approachable, and should encourage the user to take action. - Articles are written in the 3rd-person voice. Example: "Add an embed block to your page." - Use American English for spelling and punctuation styles, or consider using a different word that doesn't have a different spelling in other English variants. - Use sentence case (not title case) for docs titles and subheadings. Example: "Introduction to the launch experience" rather than "Introduction to the Launch Experience." - When referring to files or directories, the text formatting eliminates the need to include articles such as "the" and clarifying nouns such as "file" or "directory". Example: "files stored in ~~the~~ `/wp-content/uploads/` ~~directory~~" or "edit ~~the~~ `/config/config.yml` ~~file~~ with" ### Writing tips - Our target audience has a range of roles and abilities. When creating a tutorial or how-to guide, it's important to consider the intended audience. Are they beginners or advanced users? What is their technical background? Understanding the audience can help guide the level of detail and the choice of language used in the guide. - Use language understable even by readers with little technical knowledge and readers whose first language might not be English. - Consider that this might be the first WooCommerce documentation page the reader has seen. They may have arrived here via a Google search or another website. Give the reader enough context about the topic and link words and phrases to other relevant Docs articles as often as possible. - Consider notes and sections that provide insights, tips, or cautionary information to expand on topics with context that would be relevant to the reader. - When providing specific direction, best practices, or requirements, we recommend including a description of the potential consequences or impacts of not following the provided guidance. This can help seed additional search keywords into the document and provide better context when support links to the documentation. - Always write a conceptual, high-level introduction to the topic first, above any H2 subheading. ### Tutorials Tutorials are comprehensive and designed to teach a new skill or concept. > You are the teacher, and you are responsible for what the student will do. Under your instruction, the student will execute a series of actions to achieve some end. > > [Divio Framework on Tutorial Writing](https://documentation.divio.com/tutorials/) ### How-to guides How-to guides are focused and specific, providing instructions on how to accomplish a particular task or solve a particular problem. > How-to guides are wholly distinct from tutorials and must not be confused with them: > > - A tutorial is what you decide a beginner needs to know. > - A how-to guide is an answer to a question that only a user with some experience could even formulate. > > [Divio Framework on How-to-Guide Writing](https://documentation.divio.com/how-to-guides/) ## Custom Linting Rules At WooCommerce, we're dedicated to maintaining a consistent and high-quality standard for our technical documentation. Our documents primarily adhere to the linting rules provided by `markdownlint`. To assist our contributors, we've detailed our custom configurations and exceptions below. Note: While we've outlined specific rules above, all other default linting rules from `markdownlint` apply unless otherwise stated. We've only highlighted custom configurations or exceptions here. For a complete list of `markdownlint` rules, you can refer to [this link](https://github.com/DavidAnson/markdownlint/blob/3561fc3f38b05b3c55f44e371c2cd9bda194598a/doc/Rules.md). 1. **Headings Style**: - Use the ATX-style (`#`) for headers. ```markdown # This is an H1 ## This is an H2 ``` [Reference: MD003](https://github.com/DavidAnson/markdownlint/blob/3561fc3f38b05b3c55f44e371c2cd9bda194598a/doc/Rules.md#md003---heading-style) 2. **List Indentation**: - Indent list items with 4 spaces. ```markdown - Item 1 - Subitem 1.1 ``` [Reference: MD007](https://github.com/DavidAnson/markdownlint/blob/3561fc3f38b05b3c55f44e371c2cd9bda194598a/doc/Rules.md#md007---unordered-list-indentation) 3. **Line Length**: - No specific restriction on the line length, but keep paragraphs and sentences readable. [Reference: MD013](https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md013---line-length) 4. **Multiple Headings with the Same Content**: - Multiple headings with the same content are permissible as long as they are not siblings. [Reference: MD024](https://github.com/DavidAnson/markdownlint/blob/3561fc3f38b05b3c55f44e371c2cd9bda194598a/doc/Rules.md#md024---no-multiple-headings-with-the-same-content) 5. **Inline HTML**: - Only the `video` element is allowed when using inline HTML. ```markdown ``` [Reference: MD033](https://github.com/DavidAnson/markdownlint/blob/3561fc3f38b05b3c55f44e371c2cd9bda194598a/doc/Rules.md#md033---inline-html) 6. **Tabs and Whitespace**: - We're flexible with the use of hard tabs and trailing whitespace. However, for consistency, we recommend using spaces over tabs and avoiding trailing whitespaces. [Reference: no-hard-tabs & whitespace](https://github.com/DavidAnson/markdownlint/blob/3561fc3f38b05b3c55f44e371c2cd9bda194598a/doc/Rules.md) ## Formatting ### Visual style - Use the H2 style for main headings to be programmatically listed in the articles table of contents. - File names and directory paths should be stylized as code per the [HTML spec](https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-code-element). Example: `/wp-content/uploads/` - References to a single directory should have a trailing slash (eg. "/" appended) to the name. Example: "uploads/" - References to repositories should appear without forward slashes and not be formatted in any way. The first appearance of a repository in article content should link to the URL of the repository source whenever possible. Example: "[woocommerce-blocks](https://github.com/woocommerce/woocommerce-blocks)" followed by "woocommerce-blocks" - Inline references to functions and command line operations should be formatted as inline code. Example: "Use `dig` to retrieve DNS information." - Functions should be styled with "Inline code" formatting and retain upper and lower case formatting as established from their source. Example: `WP_Query` (not WP_query) ### Visual aids Visual aids such as screenshots, diagrams, code snippets and videos can be very helpful in a how-to guide. They provide a visual reference that can help the user understand the instructions more easily. When including visual aids, be sure to label them clearly and provide a caption or description that explains what is being shown. ### Acronyms Phrases that are more familiarly known in their acronym form can be used. The first time an acronym appears on any page, the full phrase must be included, followed by its acronym in parentheticals. Example: We've enhanced the querying functionality in WooCommerce with the introduction of High Performance Order Storage (HPOS). After that, the acronym can be used for the remainder of the page. When deciding if a term is common, consider the impact on translation and future internationalization (i18n) efforts. ## Patterning ### Article content When creating a how-to guide, it's important to use a consistent and easy-to-follow format. Here is a suggested template for a software how-to guide: **Introduction**: Provide an overview of the task or feature that the guide covers. **Prerequisites**: List any prerequisites that are required to complete the task or use the feature. **Step-by-step instructions**: Provide detailed, step-by-step instructions for completing the task or using the feature. Use numbered steps and include screenshots or other visual aids where appropriate. **Troubleshooting**: Include a troubleshooting section that addresses common issues or errors that users may encounter. **Conclusion**: Summarize the key points covered in the guide and provide any additional resources or references that may be helpful. ## Terminology ### Reference to components and features - "**WordPress Admin dashboard**" should be presented in its complete form the first time it appears in an article, followed by its abbreviated form in parentheses ("WP Admin"). Thereafter the abbreviated form can be used for any reference to the WordPress Admin dashboard within the same article. - When referring to the URL of the WordPress Admin dashboard, the shortened form `wp-admin` can be used. ## Testing Before publishing a tutorial or guide, it's important to test it thoroughly to ensure that the instructions are accurate and easy to follow. ## Structure ### Atomizing the docs Articles that cover too many topics in one place can make it difficult for users to find the information they are looking for. "Atomizing" the Docs refers to breaking down extensive articles into a group of smaller related articles. This group of articles often has a main "landing page" with a high-level overview of the group of articles, and the descriptive text provides links to the related articles that a user will find relevant. These groups of articles can be considered an information "molecule" formed by the smaller, atomized articles. Breaking out smaller chunks of content into their own articles makes it easier to link to specific topics rather than relying on links to more extensive articles with anchor tags. This more specific linking approach is helpful to our Support team but is also useful for cross-linking articles throughout the Docs site. --- ## Releasing WooCommerce *Source: contribution/releases/README.md* # Releasing WooCommerce The WooCommerce release process is managed by a rotating release lead. The documentation below outlines the process for managing releases. ## Process Overview * [Backporting](/docs/contribution/releases/backporting) * [Release Candidates](/docs/contribution/releases/rc) * [Schedule](/docs/contribution/releases/schedule) --- ## Backporting in WooCommerce *Source: contribution/releases/backporting.md* # Backporting in WooCommerce Backporting is the process of applying a change from `trunk` to an open release branch. When a release branch is created, it is copied from the `trunk` branch at the time of code freeze. Changes are applied to `trunk` and then backported to the release branch as needed. ## Requesting backports (Contributors) ### Cherry picking to a frozen release If you have a pull request that should be backported to a frozen release, you should target `trunk` as your base branch. You can then request that the change is backported by adding the `cherry pick to frozen release` label to the pull request. Make sure to add the `Milestone` of the version you're targetting to the PR. Note that adding this label does not guarantee that the change will be backported. The change must be qualified for backporting. > If you're nearing the deadline for the final release, you may want to get in touch with the release lead directly to make them aware of the changes. ### Cherry picking to trunk On occassion, more urgent changes may occur where we need to target the release branch directly as our base branch. When this happens, you should add the label `cherry pick to trunk` if this change is also meant to be included in `trunk`. ## Qualifying changes Changes are qualified for backporting if they are: - A bug fix. - A change that impacts the performance of WooCommerce. - A new feature that is time sensitive and impacts WooCommerce's business goals. - A new feature that is contractually required by WooCommerce. ## Manually backporting pull requests (Release Lead) ### Cherry picking to a frozen release Before cutting a new RC, you should manually backport any PRs with the respective labels. 1. Check out the release branch `git checkout release/x.y`. 2. Find all the [PRs labeled to be cherry picked](https://github.com/woocommerce/woocommerce/pulls?q=is%3Apr+label%3A%22cherry+pick+to+frozen+release%22) to the release branch. Filter by the current release milestone (`X.Y.0`) to limit to PRs relevant to this release. 3. Cherry-pick each PR (in chronological order) using `git cherry-pick [SHA]`. 4. After cherry-picking all PRs, push to the release branch using `git push`. 5. Remove the `cherry pick to frozen release` label and update the milestone to the current release for all cherry-picked PRs. The SHA for a pull request can be found in the pull request activity once the PR has been merged. --- ## WooCommerce Release Candidates *Source: contribution/releases/rc.md* # WooCommerce Release Candidates Release candidates are pre-release WooCommerce versions made available for testing by plugin authors and users. They are versioned incrementally, starting with `-rc.1`, then `-rc.2`, and so on (released if any regressions have been discovered or any crucial features need to make it into the final release). The date of the very first release candidate is announced along with the final release date as part of the published release checklist and [release schedule](https://developer.woocommerce.com/release-calendar/). > Note on timeline: The expected timeframe between the RC1 and a final release is three weeks. > Note on RC1: RC1 can be released without additional consideration, as it aligns with the code freeze timing. > Note on RC2: RC2 can be released two weeks after RC1 (and one week before the final release). On the technical side of the release process, we rely on release branches (named e.g., `release/9.9`) for code freeze and stabilization. In those branches, we tag release candidates, fix regressions (via CFEs), and tag final releases in isolation from ongoing development for greater release stability. --- ## WooCommerce Release Schedule *Source: contribution/releases/schedule.md* # WooCommerce Release Schedule The schedule can be found on [this page](https://developer.woocommerce.com/release-calendar/), which also explains the types of events in the calendar like `Releases`, `Release Candidates (RC)` and `Feature Freeze Dates`. While the calendar reflects the events made public, there are specific steps in the release process that are internal, and this page aims to provide necessary context about those events. ## Detailed release schedule This section will use the publicly available release schedule as anchors and clarify where the internal events fit in. ### Feature Freeze (start of the release cycle) This step is mostly automated and creates a dedicated release branch where the future release undergoes testing and stabilization. At this point, the Developer Advocacy team publishes pre-release updates ([example](https://developer.woocommerce.com/2025/05/12/woocommerce-9-9-pre-release-updates/)) ### RC1 (Feature Freeze + 1 week) This step is where various testing processes are happening: - internally: regression testing with Woo maintained extensions, regression testing in multiple environments, and exploration testing (including by the contributing teams). - community: the Developer Advocacy team updates the pre-release announcement so the community can [begin testing](/docs/contribution/testing/beta-testing/) the release. ### RC2 (RC1 + 1 week) This step is where we release regressions fixes discovered in RC1, as part of the release stabilization. At this point, the Developer Advocacy team continues to update the pre-release announcement. ### Final Release (RC2 + 1 week) This step is where the stable release version becomes available to everyone. At this point, the Developer Advocacy team publishes release highlights that are prepared in-advance ([example](https://developer.woocommerce.com/2025/06/09/woocommerce-9-9-its-fast-period/)). ## Delays Due to business needs, the release dates may be subject to change. Below we will describe some of the internal processes for how we wrangling this situation. Once the need for changes in the release schedule is confirmed, the Woo team creates an internal Slack thread to communicate the necessary details. This thread provides an opportunity for teams to share additional context, which may help verify or challenge schedule changes. Once the feedback and release schedule changes have been finalized, we procced to: - ask the Developer Advocacy team to communicate the changes publicly ([example](https://developer.woocommerce.com/2025/06/02/woocommerce-9-9-release-is-delayed/)) - update [the calendar](https://developer.woocommerce.com/release-calendar/) with the new release dates > Note: To reduce disruption for internal teams and contributors, we avoid changing the time between release candidates (RCs) and the final release. Instead, we adjust the overall release schedule. These intervals are based on several factors, including team capacity. --- ## Testing *Source: contribution/testing/README.md* # Testing Properly setting up your test environment and writing tests when contributing to WooCommerce core are essential parts of our development pipeline. The links below are also included in our [Contributing Guidelines](https://github.com/woocommerce/woocommerce/blob/trunk/.github/CONTRIBUTING.md) on GitHub. If you have any questions about testing please reach out to the developer community in our public channels([Developer Blog](https://developer.woocommerce.com/blog/), [GitHub Discussions](https://github.com/woocommerce/woocommerce/discussions), or [Community Slack](https://woocommerce.com/community-slack/)). ## Unit Testing [End-to-end tests](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce/tests/e2e-pw) are powered by `Playwright`. The test site is spun up using `wp-env` ([recommended](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-env/)). ## API Testing [API tests](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce/tests/e2e-pw/tests/api-tests) are based on `Playwright` and `wp-env`. ## Calls for Testing Keep tabs on calls for testing on our developer blog, and make sure to read our beta testing instructions to help us build new features and enhancements. --- ## Beta Testing *Source: contribution/testing/beta-testing.md* # Beta Testing Beta testing is vital to ensuring our releases are as bug free as possible and to ensure we hear user feedback before putting changes live. Anyone can beta test; shop owners, plugin developers, theme developers, translators, any and all help is welcome. We do understand however that the process of testing can be a little bit of a hassle since you need to download a tagged release and then upload it to your testing site manually. Sometimes this can also cause issues with plugin directory naming as well, because tagged releases will not have a directory named `woocommerce`. This can break extensions. To make this process easier, we have released a beta testing plugin to help install the WooCommerce beta versions, and to ensure the directory names are kept correct. Please ensure you test responsibly, it is not a good idea to run beta versions on a production site unless you have experience and can anticipate changes and failures. ## Installing the Beta Tester Plugin To get started, go to Plugins > Add New > Upload plugin, and install the beta tester plugin which you can download from: [WooCommerce Beta Tester](https://woocommerce.com/products/woocommerce-beta-tester/) This plugin will give you access to all of the WooCommerce versions uploaded to [WordPress.org]. This includes beta releases and release candidates. ## Giving Feedback During testing, if you come across a bug or want to propose or contribute an enhancement, [submit an issue on Github](https://github.com/woocommerce/woocommerce/issues/new?assignees=&labels=type%3A+enhancement%2Cstatus%3A+awaiting+triage&template=2-enhancement.yml&title=%5BEnhancement%5D%3A+). Ensure you read our guidelines on contributing and note which version you are using specifically. It is also possible to easily submit a new GitHub issue through the [beta tester plugin](https://woocommerce.com/products/woocommerce-beta-tester/). Sometimes we may also ask for feedback via other means, such as the surveys and calls for testing on the [developer blog](https://developer.woocommerce.com/blog/). We appreciate immediate feedback, comments, and responses to surveys as it helps us judge how well received our releases and betas are and will help us shape future versions. Thanks for testing WooCommerce! --- ## WooCommerce core critical flows *Source: contribution/testing/core-critical-flows.md* # WooCommerce core critical flows We have identified what we consider to be our most critical user flows within WooCommerce Core. These flows will help us focus and prioritize our testing efforts. They will also help us consider the impact of changes and priority of issues. These flows will continually evolve as the platform evolves with flows updated, added or re-prioritised. ## Shopper critical flow areas - [Shopper > Shop](#shopper---shop) - [Shopper > Product](#shopper---product) - [Shopper > Cart](#shopper---cart) - [Shopper > Checkout](#shopper---checkout) - [Shopper > Email](#shopper---email) - [Shopper > My Account](#shopper---my-account) ## Merchant critical flow areas - [Merchant > Onboarding](#merchant---onboarding) - [Merchant > Dashboard](#merchant---dashboard) - [Merchant > Settings](#merchant---settings) - [Merchant > Coupons](#merchant---coupons) - [Merchant > Marketing](#merchant---marketing) - [Merchant > Analytics](#merchant---analytics) - [Merchant > Products](#merchant---products) - [Merchant > Orders](#merchant---orders) - [Merchant > Customers](#merchant---customers) - [Merchant > Email](#merchant---email) - [Merchant > Plugins](#merchant---plugins) - [Merchant > My Subscriptions](#merchant---my-subscriptions) - [Merchant > Pages](#merchant---pages) - [Merchant > Posts](#merchant---posts) ### Shopper - Shop | User Type | Flow Area | Flow Name | Test File | | --------- | --------- | ------------------------------------------- | -------------------------------------------- | | Shopper | Shop | Search Store | shopper/shop-search-browse-sort.spec.js | | Shopper | Shop | Browse by categories | shopper/shop-search-browse-sort.spec.js | | Shopper | Shop | Can sort items | shopper/shop-search-browse-sort.spec.js | | Shopper | Shop | Add Simple Product to Cart (from shop page) | shopper/cart.spec.js | | Shopper | Shop | Display shop catalog | shopper/shop-search-browse-sort.spec.js | | Shopper | Shop | Products by tag | shopper/product-tags-attributes.spec.js | | Shopper | Shop | Products by attribute | shopper/product-tags-attributes.spec.js | | Shopper | Shop | Use product filters | shopper/shop-products-filer-by-price.spec.js | ### Shopper - Product | User Type | Flow Area | Flow Name | Test File | | --------- | --------- | ---------------------------------------------------- | ---------------------------------------- | | Shopper | Product | Add Simple Product to Cart | shopper/product-simple.spec.js | | Shopper | Product | Add Grouped Product to Cart | shopper/product-grouped.spec.js | | Shopper | Product | Variable Product info updates depending on variation | shopper/product-variable.spec.js | | Shopper | Product | Add Variable Product to Cart | shopper/product-variable.spec.js | | Shopper | Product | Display up-sell product | products/product-linked-products.spec.js | | Shopper | Product | Display related products | products/product-linked-products.spec.js | | Shopper | Product | Display reviews | merchant/product-reviews.spec.js | | Shopper | Product | Add review | merchant/product-reviews.spec.js | | Shopper | Product | View product images | shopper/product-simple.spec.js | | Shopper | Product | View product descriptions | shopper/product-simple.spec.js | ### Shopper - Cart | User Type | Flow Area | Flow Name | Test File | | --------- | --------- | ------------------------------------------ | ------------------------------------------- | | Shopper | Cart | Add to cart redirects to cart when enabled | shopper/cart-redirection.spec.js | | Shopper | Cart | View cart | shopper/cart.spec.js | | Shopper | Cart | Update product quantity within limits | shopper/cart.spec.js | | Shopper | Cart | Remove products from cart | shopper/cart.spec.js | | Shopper | Cart | Apply all coupon types | shopper/cart-coupons.spec.js | | Shopper | Cart | Display shipping options by address | shopper/calculate-shipping.spec.js | | Shopper | Cart | View empty cart | shopper/cart.spec.js | | Shopper | Cart | Display correct tax | shopper/cart-checkout-calculate-tax.spec.js | | Shopper | Cart | Respect coupon usage constraints | shopper/cart-checkout-coupons.spec.js | | Shopper | Cart | Display cross-sell products | products/product-linked-products.spec.js | | Shopper | Cart | Proceed to checkout | shopper/checkout.spec.js | ### Shopper - Checkout | User Type | Flow Area | Flow Name | Test File | | --------- | --------- | ---------------------------------------- | ------------------------------------------- | | Shopper | Checkout | Correct item in Order Review | shopper/checkout.spec.js | | Shopper | Checkout | Can add shipping address | shopper/checkout.spec.js | | Shopper | Checkout | Guest can place order | shopper/checkout.spec.js | | Shopper | Checkout | Create an account | shopper/checkout-create-account.spec.js | | Shopper | Checkout | Login to existing account | shopper/checkout-login.spec.js | | Shopper | Checkout | Existing customer can place order | shopper/checkout.spec.js | | Shopper | Checkout | Use all coupon types | shopper/checkout-coupons.spec.js | | Shopper | Checkout | View checkout | shopper/checkout.spec.js | | Shopper | Checkout | Receive warnings when form is incomplete | shopper/checkout.spec.js | | Shopper | Checkout | Add billing address | shopper/checkout.spec.js | | Shopper | Checkout | Respect coupon usage constraints | shopper/cart-checkout-coupons.spec.js | | Shopper | Checkout | Display correct tax in checkout | shopper/cart-checkout-calculate-tax.spec.js | | Shopper | Checkout | View order confirmation page | shopper/checkout.spec.js | ### Shopper - Email | User Type | Flow Area | Flow Name | Test File | | --------- | --------- | ------------------------------------- | --------------------------------------- | | Shopper | Email | Customer Account Emails Received | shopper/account-email-receiving.spec.js | | Shopper | Email | Customer Order Detail Emails Received | shopper/order-email-receiving.spec.js | ### Shopper - My Account | User Type | Flow Area | Flow Name | Test File | | --------- | ---------- | ------------------------- | ----------------------------------------- | | Shopper | My Account | Create an account | shopper/my-account-create-account.spec.js | | Shopper | My Account | Login to existing account | shopper/my-account.spec.js | | Shopper | My Account | View Account Details | shopper/my-account.spec.js | | Shopper | My Account | Update Addresses | shopper/my-account-addresses.spec.js | | Shopper | My Account | View Orders | shopper/my-account-pay-order.spec.js | | Shopper | My Account | Pay for Order | shopper/my-account-pay-order.spec.js | | Shopper | My Account | View Downloads | shopper/my-account-downloads.spec.js | ### Merchant - Onboarding | User Type | Flow Area | Flow Name | Test File | | --------- | ------------- | -------------------------------------------------------------- | ---------------------------------------- | | Merchant | Core Profiler | Introduction & opt-in | activate-and-setup/core-profiler.spec.js | | Merchant | Core Profiler | User profile information | activate-and-setup/core-profiler.spec.js | | Merchant | Core Profiler | Business information | activate-and-setup/core-profiler.spec.js | | Merchant | Core Profiler | Extensions page | activate-and-setup/core-profiler.spec.js | | Merchant | Core Profiler | WooPayments included in extensions for eligible criteria | activate-and-setup/core-profiler.spec.js | | Merchant | Core Profiler | WooPayments not included in extensions for ineligible criteria | activate-and-setup/core-profiler.spec.js | | Merchant | Core Profiler | Install all default extensions | activate-and-setup/core-profiler.spec.js | | Merchant | Core Profiler | Complete site setup | activate-and-setup/core-profiler.spec.js | | Merchant | Core Profiler | Skip introduction and confirm business location | activate-and-setup/core-profiler.spec.js | ### Merchant - Dashboard | User Type | Flow Area | Flow Name | Test File | | --------- | -------------- | ------------------------------------------------------ | --------- | | Merchant | WC Home | Completing profiler redirects to home | | | Merchant | WC Home | Complete all steps on task list | | | Merchant | WC Home | Hide the task list | | | Merchant | WC Home | Store management displayed after task list finished | | | Merchant | WC Home | Direct access to analytics reports from stats overview | | | Merchant | WC Home | Preserve task list completion status after upgrade | | | Merchant | WC Home | Interact with extended task list | | | Merchant | Activity Panel | Interact with activity button | | | Merchant | Inbox | Interact with notes and perform CTAs | | | Merchant | Inbox | Dismiss single note and all notes | | ### Merchant - Settings | User Type | Flow Area | Flow Name | Test File | | --------- | --------- |----------------------------------------|------------------------------------------| | Merchant | Settings | Update General Settings | merchant/settings-general.spec.js | | Merchant | Settings | Add Tax Rates | merchant/settings-tax.spec.js | | Merchant | Settings | Add Shipping Zones | merchant/create-shipping-zones.spec.js | | Merchant | Settings | Add Shipping Classes | merchant/create-shipping-classes.spec.js | | Merchant | Settings | Enable local pickup for checkout block | merchant/settings-shipping.spec.js | | Merchant | Settings | Update payment settings | admin-tasks/payment.spec.js | | Merchant | Settings | Handle Product Brands | merchant/create-product-brand.spec.js | ### Merchant - Coupons | User Type | Flow Area | Flow Name | Test File | | --------- | --------- | --------------------- | ------------------------------------------ | | Merchant | Coupons | Add all coupon types | merchant/create-coupon.spec.js | | Merchant | Coupons | Add restricted coupon | merchant/create-restricted-coupons.spec.js | ### Merchant - Marketing | User Type | Flow Area | Flow Name | Test File | | --------- | --------- | -------------------------- | -------------------------------- | | Merchant | Marketing | Display marketing overview | admin-marketing/overview.spec.js | ### Merchant - Analytics | User Type | Flow Area | Flow Name | Test File | | --------- | --------- | -------------------------------------------------- | ------------------------------------------ | | Merchant | Analytics | View revenue report | admin-analytics/analytics.spec.js | | Merchant | Analytics | View overview report | admin-analytics/analytics-overview.spec.js | | Merchant | Analytics | Confirm correct summary numbers on overview report | admin-analytics/analytics-data.spec.js | | Merchant | Analytics | Use date filter on overview page | admin-analytics/analytics-data.spec.js | | Merchant | Analytics | Customize performance indicators on overview page | admin-analytics/analytics-overview.spec.js | | Merchant | Analytics | Use date filter on revenue report | admin-analytics/analytics-data.spec.js | | Merchant | Analytics | Download revenue report as CSV | admin-analytics/analytics-data.spec.js | | Merchant | Analytics | Use advanced filters on orders report | admin-analytics/analytics-data.spec.js | | Merchant | Analytics | Analytics settings | admin-analytics/analytics-data.spec.js | | Merchant | Analytics | Set custom date range on revenue report | admin-analytics/analytics-data.spec.js | ### Merchant - Products | User Type | Flow Area | Flow Name | Test File | | --------- | -------------- | ------------------------------ | ------------------------------------------------------------------------- | | Merchant | Products | View all products | merchant/product-search.spec.js | | Merchant | Products | Search products | merchant/product-search.spec.js | | Merchant | Products | Add simple product | merchant/product-create-simple.spec.js | | Merchant | Products | Add variable product | merchant/products/add-variable-product/create-variable-product.spec.js | | Merchant | Products | Edit product details | merchant/product-edit.spec.js | | Merchant | Products | Add virtual product | merchant/product-create-simple.spec.js | | Merchant | Products | Import products CSV | merchant/product-import-csv.spec.js | | Merchant | Products | Add downloadable product | merchant/product-create-simple.spec.js | | Merchant | Products | View product reviews list | merchant/product-reviews.spec.js | | Merchant | Products | View all products reviews list | merchant/product-reviews.spec.js | | Merchant | Products | Edit product review | merchant/product-reviews.spec.js | | Merchant | Products | Trash product review | merchant/product-reviews.spec.js | | Merchant | Products | Bulk edit products | merchant/product-edit.spec.js | | Merchant | Products | Remove products | merchant/product-delete.spec.js | | Merchant | Products | Manage product images | merchant/product-images.spec.js | | Merchant | Products | Manage product inventory | merchant/product-create-simple.spec.js | | Merchant | Products | Manage product attributes | merchant/product-create-simple.spec.js | | Merchant | Products | Manage global attributes | | | Merchant | Products | Add up-sell | products/product-linked-products.spec.js | | Merchant | Products | Add cross-sell | products/product-linked-products.spec.js | | Merchant | Products (New) | Disable new product experience | merchant/products/block-editor/disable-block-product-editor.spec.js | | Merchant | Products (New) | Add simple product | merchant/products/block-editor/create-simple-product-block-editor.spec.js | | Merchant | Products (New) | Edit simple product | merchant/products/block-editor/product-edit-block-editor.spec.js | | Merchant | Products (New) | Manage product images | merchant/products/block-editor/product-images-block-editor.spec.js | | Merchant | Products (New) | Manage product inventory | merchant/products/block-editor/product-inventory-block-editor.spec.js | | Merchant | Products (New) | Manage product attributes | merchant/products/block-editor/product-attributes-block-editor.spec.js | ### Merchant - Orders | User Type | Flow Area | Flow Name | Test File | | --------- | --------- | ---------------------------------------------------------------- | -------------------------------------- | | Merchant | Orders | View all orders | merchant/order-status-filter.spec.js | | Merchant | Orders | Can add new order basic | merchant/order-edit.spec.js | | Merchant | Orders | View single order | merchant/order-edit.spec.js | | Merchant | Orders | Update order status to completed | merchant/order-edit.spec.js | | Merchant | Orders | Update order status to cancelled | merchant/order-edit.spec.js | | Merchant | Orders | Update order details | merchant/order-edit.spec.js | | Merchant | Orders | Customer payment page | merchant/customer-payment-page.spec.js | | Merchant | Orders | Refund order | merchant/order-refund.spec.js | | Merchant | Orders | Apply coupon | merchant/order-coupon.spec.js | | Merchant | Orders | Can add new order complex - multiple product types & tax classes | merchant/create-order.spec.js | | Merchant | Orders | Search orders | merchant/order-search.spec.js | | Merchant | Orders | Filter orders by order status | merchant/order-status-filter.spec.js | | Merchant | Orders | Bulk change order status | merchant/order-bulk-edit.spec.js | | Merchant | Orders | Add order notes | merchant/order-edit.spec.js | ### Merchant - Customers | User Type | Flow Area | Flow Name | Test File | | --------- | --------- | --------------------- | ------------------------------ | | Merchant | Customers | Display customer list | merchant/customer-list.spec.js | ### Merchant - Email | User Type | Flow Area | Flow Name | Test File | | --------- | --------- | -------------------------------------------------- | ----------------------------- | | Merchant | Email | Receive and check content of new order email | merchant/order-emails.spec.js | | Merchant | Email | Receive and check content of cancelled order email | merchant/order-emails.spec.js | | Merchant | Email | Receive and check content of failed order email | merchant/order-emails.spec.js | | Merchant | Email | Resent new order email | merchant/order-emails.spec.js | | Merchant | Email | Send invoice/order details to customer via Email | merchant/order-emails.spec.js | ### Merchant - Plugins | User Type | Flow Area | Flow Name | Test File | | --------- | --------- | ---------------------- | -------------------------------------- | | Merchant | Plugins | Can update WooCommerce | smoke-tests/update-woocommerce.spec.js | ### Merchant - My Subscriptions | User Type | Flow Area | Flow Name | Test File | | --------- | ---------------- | --------------------------------------- | --------------------------------- | | Merchant | My Subscriptions | Can initiate WooCommerce.com Connection | merchant/settings-woo-com.spec.js | ### Merchant - Pages | User Type | Flow Area | Flow Name | Test File | | --------- | --------- | --------------------- | ---------------------------- | | Merchant | Pages | Can create a new page | merchant/create-page.spec.js | ### Merchant - Posts | User Type | Flow Area | Flow Name | Test File | | --------- | --------- | --------------------- | ---------------------------- | | Merchant | Posts | Can create a new post | merchant/create-post.spec.js | --- ## Writing high quality testing instructions *Source: contribution/testing/writing-high-quality-testing-instructions.md* # Writing high quality testing instructions ## Introduction Having clear testing Instructions on pull requests is the first level of quality engineering in WooCommerce, which is key for testing early and minimizing the impact of unexpected effects in the upcoming versions of WooCommerce. This page contains the following sections: - [What is a test?](#what-is-a-test) - [What to cover with the testing instructions](#what-to-cover-with-the-testing-instructions) - [Flow to write good testing instructions](#flow-to-write-good-testing-instructions) - [Examples](#examples) ## What is a test? A test is a method that we can use to check that something meets certain criteria. It is typically defined as a procedure which contains the steps required to put the system under test in a certain state before executing the action to be checked. Therefore, a test consists of the following stages: - **Preconditions:** All the steps that need to be performed to put the system in the desired state before executing the action we want to check. A test could have many preconditions. - **Action:** This is the exact step that causes the change we want to check in the system. It should be only one because each test should ideally cover one thing at a time. - **Validation:** Relates to the steps to be performed in order to validate the result of performing the action in the system. A test could validate more than one thing. For example, in the process of adding an item to the cart: - The **preconditions** would be all the steps involved in: - The product creation process. - Logging as a shopper. - Heading to the shop page where the products are listed. - The **action** would be clicking the _"Add to cart"_ button in the desired product. - The **validation** stage would include checking that the cart icon (if any) shows 1 more item and the product we selected is now included in the cart. Specifying the preconditions, actions and validations can be quite beneficial when understanding the scope of a test, because: - The **preconditions** describe what we have to do so that we can execute the test successfully. - The **action** lets us know the purpose of the test, in other words, it is the key to understanding what we need to test. - The **validation** stage lets us know what to expect when executing the test. In this context, we will refer to testing instructions as the tests we need to execute in order to validate that the changes delivered in a pull request or release work as expected. This means the testing instructions could refer to a test or more, involving the happy path and potential edge cases. ## What to cover with the testing instructions As stated in the previous section, a test (in our context, a testing instruction) is a method to check that a new change or set of changes meets certain criteria. Therefore, a PR could have testing instructions for multiple scenarios, in fact, it is recommended to include testing instructions for as many scenarios as needed to cover the changes introduced in the PR. In other words, please **add as many testing instructions as needed to cover the acceptance criteria**, understanding acceptance criteria as _the conditions that a software product must satisfy to be accepted by a user, customer or other stakeholders_ or, in the context of a PR, the conditions that this PR must satisfy to be accepted by users, developers and the WooCommerce community as per requirements. ## Flow to write good testing instructions 1. **Outline the user flows** you want to cover. 2. **Define the environment** where the testing instructions should be executed (server, PHP version, WP version, required plugins, etc), and start writing the testing instructions as if you were starting from a fresh install. 3. Identify the **preconditions**, **action** and **validation** steps. 4. Write **as many preconditions as you need** to explain how to set up the state of WooCommerce so that you can execute the desired action to test every flow. 1. Try to be detailed when explaining the interactions the user needs to perform in WooCommerce. 2. If there are several preconditions for a user flow that is explained in a public guide, feel free to simply link the guide in the testing instructions instead of writing several steps. For example, _"Enable dev mode in WooCommerce Payments by following the steps mentioned [in the sandbox mode documentation](https://woocommerce.com/document/woocommerce-payments/testing-and-troubleshooting/sandbox-mode/)"_. 5. Write **the action step**, which should cover the specific action that we want to test as part of this user flow. 6. Write **as many validation steps** as needed in order to assess that the actual result meets expectations. 1. Bear in mind to check only the steps needed to validate that this change works. ### Considerations for writing high-quality testing instructions - Define the testing instructions in a way that they can be **understood and followed by everybody**, even for people new to WooCommerce. - Make sure to describe every detail and **avoid assuming knowledge**, the spectrum of readers might be wide and some people would not know the concepts behind what is being assumed. For example, instead of saying _"Enable the [x] experiment"_, say something like: ```text - Install the WooCommerce Beta Tester plugin. - Go to `Tools > WCA Test Helper > Experiments`. - Toggle the [x] experiment. ``` - Always try to explain in detail **where the user should head to**, for example instead of saying "Go to the Orders page as admin", say "Go to [url]" or even "Go to WooCommerce > Orders". - Try to use real test data. For example, instead of saying _"Enter a name for the product"_, say something like _"Enter 'Blue T-Shirt' as the product name"_. This will make it more self-explanatory and remove potential doubts related to assuming knowledge. - Make sure you **keep your testing instructions updated** if they become obsolete as part of a new commit. - If the testing instructions require to add custom code, please **provide the code snippet**. - If the testing instructions require to install a plugin, please **provide a link to this plugin, or the zip file** to install it. - If the testing instructions require to hit an API endpoint, please provide the **link to the endpoint documentation**. - Ideally **provide screenshots and/or videos** that supported what the testing instructions are explaining. If you are using links to collaborative tools then also provide an equivalent screenshot/video for those who do not have access. ## Examples ### Good quality testing instructions #### Example 1 ![Sample of good quality instructions](https://developer.woocommerce.com/wp-content/uploads/2023/12/213682695-3dc51613-b836-4e7e-93ef-f75078ab48ac.png) #### Example 2 ![Another sample of good quality instructions](https://developer.woocommerce.com/wp-content/uploads/2023/12/213682778-b552ab07-a518-48a7-9358-16adc5762aca.png) ### Improving real testing instructions In this section, you will see some real examples of testing instructions that have room for improvement (before) and how we can tweak them (after). Before: ![Instructions needing improvement](https://developer.woocommerce.com/wp-content/uploads/2023/12/213682396-8c52d20e-1fca-4ac1-8345-f381c15a102a.png) After: ![Improved instructions](https://developer.woocommerce.com/wp-content/uploads/2023/12/213682480-c01e0e84-5969-4456-8f43-70cbb8509e8d.png) Improvements: ![Changes made](https://developer.woocommerce.com/wp-content/uploads/2023/12/213682597-8d06e638-35dd-4ff8-9236-63c6ec5d05b8.jpg) Before: ![example before providing improved instructions](https://developer.woocommerce.com/wp-content/uploads/2023/12/216365611-b540a814-3b8f-40f3-ae64-81018b9f97fb.png) After: ![example after providing improved instructions](https://developer.woocommerce.com/wp-content/uploads/2023/12/216366043-967e5daa-6a23-4ab8-adda-5f3082d1ebf7.png) Improvements: ![example of improvements](https://developer.woocommerce.com/wp-content/uploads/2023/12/216366152-b331648d-bcef-443b-b126-de2621a20862.png) Before: ![example before providing improved instructions](https://developer.woocommerce.com/wp-content/uploads/2023/12/216388785-8806ea74-62e6-42da-8887-c8e291e7dfe2-1.png) After: ![example after providing improved instructions](https://developer.woocommerce.com/wp-content/uploads/2023/12/216388842-e5ab433e-d288-4306-862f-72f6f81ab2cd.png) Improvements: ![example of improvements](https://developer.woocommerce.com/wp-content/uploads/2023/12/216388874-c5b21fc3-f693-4a7e-a58a-c5d1b6606682.png) --- ## Accessibility Best Practices *Source: extensions/best-practices-extensions/accessibility.md* # Accessibility Best Practices In many places around the world, ecommerce stores are required to be accessible to people with disabilities. As a WooCommerce extension developer, your code directly impacts the accessibility of the shops that use it and can impact merchants’ compliance with accessibility laws. This page is a resource for developers who want to ensure their extensions comply with accessibility standards and best practices. It is recommended that all WooCommerce extensions follow accessibility best practices to provide the best experience for merchants and their customers. ## Accessibility Laws Two examples of laws that require ecommerce websites to be accessible include the [Americans with Disabilities Act (ADA)](https://ada.gov/) in the United States and the [European Accessibility Act (EAA)](https://employment-social-affairs.ec.europa.eu/policies-and-activities/social-protection-social-inclusion/persons-disabilities/union-equality-strategy-rights-persons-disabilities-2021-2030/european-accessibility-act_en), a directive that applies to all EU member countries. There are also laws requiring website accessibility in Australia, Canada, Israel, and many other countries. The W3C maintains a list of [web accessibility laws worldwide](https://www.w3.org/WAI/policies/) if you want to research laws in your country. Most laws require websites to conform to Web Content Accessibility Guidelines (WCAG) as a measure of accessibility. To ensure that merchants can use your extension on their websites, it should also be WCAG conformant. If your extension is not WCAG conformant, you may receive complaints about accessibility issues from merchants. You may also be included in lawsuits or legal cases about accessibility problems caused by your extension. ## Web Content Accessibility Guidelines (WCAG) The [Web Content Accessibility Guidelines (WCAG)](https://www.w3.org/TR/WCAG22) are internationally recognized standards designed to make websites accessible to everyone, including people with disabilities. WCAG is made up of *success criteria* (specific, testable rules) that fall under four key principles: ### Four Key Principles - **Perceivable** Content should be available in ways users can perceive, even if they do not have all five senses. Information should be transformable from one form to another (e.g., text alternatives for images or captions for videos). - **Operable** Users should be able to interact with the site easily using alternative devices (such as a keyboard only or screen reader without a mouse), and there should be no time-sensitive actions. - **Understandable** Content and interfaces should be clear and consistent with simple language and predictable navigation. - **Robust** Websites should work well with assistive technologies and across different devices. ### Compliance Levels WCAG has three levels of compliance: - **Level A** – Basic - **Level AA** – Mid-range (required by most laws) - **Level AAA** – Highest Most regulations require **Level AA**, meaning you need to meet all **A** and **AA** success criteria. ### Current Version The current version of WCAG is **2.2**. **Extension developers should aim for WCAG 2.2 Level AA compliance** as a best practice. ## Manual compatibility testing It’s important to test your extension for [WCAG conformance](https://www.w3.org/TR/WCAG22) as you design and develop new features. Testing for accessibility is just as important as testing for security or WordPress coding standards. If you have not previously tested your extension for accessibility, start testing today and add accessibility bug fixes in future releases. ### Automated Testing The easiest way to start accessibility testing is with an automated testing tool. Automated tools can quickly identify problems like empty buttons, ambiguous links, color contrast failures, missing alternative text, and more. [Accessibility Checker](https://wordpress.org/plugins/accessibility-checker/) is a free WordPress plugin that you can use to test your extension. Simply install it in your test environment and add blocks or shortcodes created by your extension to a page. When you save the page, Accessibility Checker will scan the blocks or rendered shortcodes and provide a list of issues to address. The [WAVE browser extension](https://wave.webaim.org/extension/) is another free automated testing tool. This browser extension can be used on any website and is helpful if you want to find accessibility problems on your extension’s admin pages. ### Keyboard Testing After resolving issues from automated testing, the next step is to ensure that your extension can be used without a mouse and with a keyboard alone. To test your extension for keyboard accessibility, go to the part of a test site controlled by your extension. Using the Tab key, move forward through the web page, ensuring that the following is true: - All interactive components (buttons, links, inputs, etc.) can be reached by hitting the Tab key. - Focus moves intuitively from left to right and top to bottom down the page. - You can move backward through the components by hitting Shift + Tab. - There are no keyboard traps. A keyboard trap is when focus lands on an element and then cannot go forward or backward. - There is a visible focus outline on all interactive components. Also confirm that you have never set `:focus` in your stylesheet to `outline:none;` or `outline:0;`. It is never correct to remove focus outlines. - If a button triggers a modal or dialog, focus should be moved into the dialog, and it should not be possible to tab out of the dialog without closing it. - Buttons should be able to be triggered with both the Spacebar and the Return key. - Links should be able to be triggered with the Return key. Test all aspects of your extension on the front end and in the admin for full functionality without a mouse. ### Test When Zoomed Ensure your extension can be used by low-vision users interacting with their browser zoomed in. Set your browser zoom to both 200% and 400% and ensure that no content overlaps or is lost and that the extension’s components can be used while the site is zoomed in. ### Screen Reader Testing Familiarize yourself with screen readers, the assistive technology used by people who are blind or visually impaired, and use them to test your extension. There are two free screen readers that you can use for testing: - **VoiceOver**: If you have a Mac, VoiceOver is built into your computer and is a great place to start, but it is not the preferred screen reader for blind desktop users. - **NVDA**: NVDA is a free, open-source screen reader that you can download and use on your PC. This is the preferred screen reader for testing, as most blind people are Windows users. To test your extension for screen reader accessibility, turn on the screen reader and use your keyboard to navigate the web page. Have the screen reader read the entire web page for you and listen for the following: - All content and elements are read out – nothing meaningful is skipped. - Images and graphics have accurate descriptions. - Decorative images are skipped. - Elements have the correct role: buttons should be announced as buttons, links as links, etc. - Links and buttons have names that describe their purpose. - Form fields are correctly labeled. - States of components like tabs, accordions, and toggle buttons are announced as collapsed, selected, pressed, etc. - Status changes are announced by the screen reader (e.g., success and error messages, changes in the number of visible products, opening of a modal or dialog, etc.). - Hidden content is not read out. ### Test for Motion Sensitivity Website animations and motion can be distracting or cause physical illness for some users. To ensure your extension passes WCAG success criteria for motion: - Provide a pause button for any auto-playing content that lasts longer than 5 seconds (e.g., videos, background videos, sliders/carousels, or animated GIFs). - Avoid flashing content that exceeds three flashes per second, as it can potentially trigger seizures. - Ensure all animations are disabled if the user has enabled the “reduce motion” setting in their operating system. You can test if your animations respect the “prefers reduced motion” setting by enabling it on your operating system. Here’s how to do this: - **On Windows 11**: Go to *Settings > Accessibility > Visual Effects > Animation Effects*. - **On macOS**: Navigate to *System Preferences > Accessibility > Display > Reduce motion*. Learn more about coding [prefers-reduced-motion media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion). ### User Testing Consider bringing in actual users with disabilities to test your extensions for accessibility. Seeing someone who relies on a screen reader or other assistive device use your products can be very enlightening. Ask your customer base to see if you already have people with disabilities in your audience available to test and provide feedback. Here’s a great resource on [how to run user testing sessions](https://www.w3.org/WAI/test-evaluate/involving-users/). If you don’t want to run your own user testing sessions, there are [WooExperts](https://woocommerce.com/for-agencies/) who can help with this. ## Additional Considerations ### Make Your Website Accessible In addition to testing your extension for accessibility best practices, you should be thinking about the accessibility of your own website and documentation. If your business meets certain revenue thresholds, the ecommerce store where people buy your products must be accessible and WCAG conformant. ### Include Captions for Your Videos If your documentation includes videos, those videos must be captioned and include transcripts. This is important to ensure everyone can access your documentation and learn to use your extension. ### Guiding Your Customers Even if your extension is perfectly accessible, merchants can introduce accessibility problems when configuring your extension or adding content. Whenever possible, define accessible defaults. For example, the default colors in your extension should always pass WCAG AA color contrast requirements. Merchants may change these colors to a combination that fails contrast, but you can say your extension is “accessibility-ready” if the default passes. Consider adding functionality in the admin that alerts merchants when they make a choice that negatively impacts accessibility. For example, you can flag color combinations that fail contrast checks, similar to how it’s done in WordPress core. Other things you might warn merchants about include empty field labels, headings out of order, empty alt text, or choosing settings you must maintain for backward compatibility but know are not accessible. ### Create an Accessibility Conformance Report Many organizations, especially in government and education, require an Accessibility Conformance Report (ACR) before purchasing software. An ACR can speed up the procurement process if your extension is sold to large businesses, government, or educational institutions. An Accessibility Conformance Report is a document that outlines the accessibility compliance of a digital product, against recognized accessibility standards like WCAG, Section 508 (US), and Europe’s EN 301 549 accessibility standard. ACRs are created using a Voluntary Product Accessibility Template (VPAT), a standardized format for assessing and reporting accessibility features. You can download this template for free and fill it in yourself, or hire an accessibility specialist to audit your extension and complete the VPAT for you. ### Get Help With Compliance WCAG compliance doesn’t have to feel overwhelming! You can test and fix one issue at a time, incrementally improving your extension. However, accessibility goes faster if you don’t have to memorize WCAG or learn how to test first. Consider hiring an accessibility specialist to help with auditing or user testing your extension. The right company or consultant will help you find problems quickly, prioritize fixes, and train your team so fewer accessibility issues will be added in the future. You can find accessibility-focused developers in our [WooExperts directory](https://woocommerce.com/for-agencies/). ## Learning More About Accessibility We recommend these resources if you want to learn more about website accessibility: - [WordPress Accessibility Meetup](https://www.meetup.com/wordpress-accessibility-meetup/) - [WP Accessibility Day Conference](https://wpaccessibility.day/) - [WordPress Accessibility Ready Requirements](https://make.wordpress.org/themes/handbook/review/accessibility/) - [WordCamp Europe Accessibility Testing Workshop](https://europe.wordcamp.org/2023/accessibility-testing-workshop/) - [Web Accessibility Specialist Certification from the International Association of Accessibility Professionals](https://www.accessibilityassociation.org/specialist) --- ## Compatibility and interoperability for WooCommerce extensions *Source: extensions/best-practices-extensions/compatibility.md* # Compatibility and interoperability for WooCommerce extensions Ensuring your WooCommerce extension is compatible and interoperable with the core platform, various components, and other extensions is fundamental to providing a seamless experience for users. This document covers the importance of compatibility, the process for self-declared compatibility checks, manual testing for compatibility issues, and troubleshooting common problems. ## Compatibility importance Compatibility ensures that your extension works as expected across different environments, including various versions of WordPress and WooCommerce, as well as with other plugins and themes. Ensuring compatibility is crucial for: - **User experience**: Preventing conflicts that can lead to functionality issues or site downtime. - **Adoption and retention**: Users are more likely to install and keep updates if they're assured of compatibility. - **Security and performance**: Compatible extensions are less likely to introduce vulnerabilities or degrade site performance. ## Self-declared compatibility checks Developers should declare their extension's compatibility with the latest versions of WordPress and WooCommerce, as well as with specific components like Cart & Checkout blocks, High Performance Order Storage (HPOS), Product Editor, and Site Editor. This process involves: 1. **Testing**: Before release, thoroughly test the extension with the latest versions of WordPress and WooCommerce, as well as the specified components. 2. **Declaration**: Update the extension's documentation and metadata to reflect its compatibility with these platforms and components. 3. **Communication**: Inform users of the compatible versions in the extension's change log, website, or repository. ## Manual compatibility testing Manual testing is essential to identify and resolve potential compatibility issues. Follow these steps for effective manual compatibility testing: 1. **Set up a testing environment** that mirrors a typical user setup, including the latest versions of WordPress and WooCommerce. 2. **Test with core components**: Verify the extension's functionality with core components like Cart & Checkout blocks, HPOS, Product Editor, and Site Editor. 3. **Cross-plugin compatibility**: Activate your extension alongside other commonly used plugins to check for conflicts. 4. **Theme compatibility**: Test your extension with several popular themes to ensure it works correctly and maintains a consistent appearance. ## Troubleshooting and resolving compatibility issues Despite thorough testing, compatibility issues may arise. Here are common problems and steps to resolve them: ### Conflicts with other extensions - **Diagnosis**: Use tools like [Health Check & Troubleshooting plugin](https://wordpress.org/plugins/health-check/) to identify conflicting plugins. - **Resolution**: Contact the other plugin's developer for collaboration, or implement conditional logic in your extension to avoid the conflict. ### Theme Compatibility Issues - **Diagnosis**: Check for styling or layout issues when your extension is used with different themes. - **Resolution**: Use more generic CSS selectors and provide configuration options for better theme integration. ### Updates breaking compatibility - **Preventive measures**: Subscribe to the [WooCommerce developer blog](https://developer.woocommerce.com) to stay informed about upcoming changes. - **Quick fixes**: Prepare patches or minor updates to address compatibility issues as soon as possible after a core update. ### No Errors with multiple extensions activated - **Best practice**: Regularly test your extension in a multi-plugin environment to ensure it does not cause or suffer from conflicts. ## Conclusion Maintaining compatibility and interoperability is a continuous effort that requires regular testing, updates, and communication with your users and the broader developer community. By following these guidelines, you can enhance the reliability, user satisfaction, and success of your WooCommerce extension. --- ## WooCommerce extension development best practices *Source: extensions/best-practices-extensions/extension-development-best-practices.md* # WooCommerce extension development best practices Want to create a plugin to extend WooCommerce? You're in the right place. WooCommerce extensions are the same as regular WordPress plugins. For more information, visit [Writing a plugin](https://developer.wordpress.org/plugins/). Your WooCommerce extension should: - Adhere to all WordPress plugin coding standards, as well as [best practice guidelines](https://developer.wordpress.org/plugins/plugin-basics/best-practices/) for harmonious existence within WordPress and alongside other WordPress plugins. - Have a single core purpose and use WooCommerce features as much as possible. - Not do anything malicious, illegal, or dishonest - for example, inserting spam links or executable code via third-party systems if not part of the service or explicitly permitted in the service's terms of use. - Not subvert or override Marketplace connections in core — for example, extensions cannot create branded top-level menu items or introduce their own telemetry. - Adhere to WooCommerce [compatibility and interoperability guidelines](https://woocommerce.com/document/marketplace-overview/#section-9). Merchants make use of WooCommerce extensions daily, and should have a unified and pleasant experience while doing so without advertising invading their WP Admin or store. ## Best Practices 1. **Check if WooCommerce is active**. Most WooCommerce plugins do not need to run unless WooCommerce is already active. [Learn how to check if WooCommerce is active](/docs/extensions/core-concepts/check-if-woo-is-active). 2. **The main plugin file should adopt the name of the plugin**. For example: A plugin with the directory name `plugin-name` would have its main file named `plugin-name.php`. 3. **The text domain should match your plugin directory name**. For example: A plugin with a directory name of `plugin-name` would have the text domain `plugin-name`. Do not use underscores. 4. **Internationalization**: Follow guidelines for [Internationalization for WordPress Developers](https://codex.wordpress.org/I18n_for_WordPress_Developers) 5. **Localization**: All text strings within the plugin code should be in English. This is the WordPress default locale, and English should always be the first language. If your plugin is intended for a specific market (e.g., Spain or Italy), include appropriate translation files for those languages within your plugin package. Learn more at [Using Makepot to translate your plugin](https://codex.wordpress.org/I18n_for_WordPress_Developers#Translating_Plugins_and_Themes). 6. **Follow WordPress PHP Guidelines**. WordPress has a [set of guidelines](http://make.wordpress.org/core/handbook/coding-standards/php/) to keep all WordPress code consistent and easy to read. This includes quotes, indentation, brace style, shorthand php tags, yoda conditions, naming conventions, and more. Please review the guidelines. 7. **Avoid creating custom database tables**. Whenever possible, use WordPress [post types](http://codex.wordpress.org/Post_Types#Custom_Post_Types), [taxonomies](http://codex.wordpress.org/Taxonomies), and [options](http://codex.wordpress.org/Creating_Options_Pages). For more, check out our [primer on data storage](/docs/best-practices/data-management/data-storage). 8. **Prevent Data Leaks** by ensuring you aren't providing direct access to PHP files. [Find out how](/docs/best-practices/security/prevent-data-leaks). 9. **All plugins need a [standard WordPress README](http://wordpress.org/plugins/about/readme.txt)**. See an example in the [WordPress plugin README file standard](https://wordpress.org/plugins/readme.txt). 10. **All plugins need a changelog file.** See an example of a changelog file and different changelog entry types in the [changelog.txt documentation](/docs/extensions/core-concepts/changelog-txt). 11. **Follow our conventions for your Plugin header comment**. View our [example WordPress plugin header comment](/docs/extensions/core-concepts/example-header-plugin-comment) and follow the conventions listed, including: `Author:`, `Author URI:` , `Developer:`, `Developer URI`, `WC requires at least:`and `WC tested up to:`, and `Plugin URI:`. 12. **Make it extensible**: use WordPress actions and filters to allow for modification/customization, and if your plugin creates a front-end output, we recommend having a templating engine in place so users can create custom template files in their theme's WooCommerce folder to overwrite the plugin's template files.For more information, check out Pippin's post on [Writing Extensible Plugins with Actions and Filters](http://code.tutsplus.com/tutorials/writing-extensible-plugins-with-actions-and-filters--wp-26759). 13. **Avoid external libraries**. The use of entire external libraries is typically not suggested as this can open up the product to security vulnerabilities. If an external library is absolutely necessary, developers should be thoughtful about the code used and assume ownership as well as of responsibility for it. Try to only include the strictly necessary part of the library, or use a WordPress-friendly version or opt to build your own version. For example, if needing to use a text editor such as TinyMCE, we recommend using the WordPress-friendly version, TinyMCE Advanced. 14. **Avoid third-party systems**: Loading code from documented services is allowed, but communication must be secure. Executing outside code within a plugin is not allowed. Using third-party CDNs for non-service-related JavaScript and CSS is prohibited. Iframes should not be used to connect admin pages. 15. **Remove unused code**. With version control, there's no reason to leave commented-out code; it's annoying to scroll through and read. Remove it and add it back later if needed. 16. **Use Comments** to describe the functions of your code. If you have a function, what does the function do? There should be comments for most if not all functions in your code. Someone/You may want to modify the plugin, and comments are helpful for that. We recommend using [PHP Doc Blocks](http://en.wikipedia.org/wiki/PHPDoc) similar to [WooCommerce](https://github.com/woocommerce/woocommerce/). 17. **Avoid God Objects**. [God Objects](http://en.wikipedia.org/wiki/God_object) are objects that know or do too much. The point of object-oriented programming is to take a large problem and break it into smaller parts. When functions do too much, it's hard to follow their logic, making bugs harder to fix. Instead of having massive functions, break them down into smaller pieces. 18. **Separate Business Logic & Presentation Logic.** It's a good practice to separate business logic (i.e., how the plugin works) from [presentation logic](http://en.wikipedia.org/wiki/Presentation_logic) (i.e., how it looks). Two separate pieces of logic are more easily maintained and swapped if necessary. An example is to have two different classes - one for displaying the end results, and one for the admin settings page. 19. **Use Transients to Store Offsite Information**. If you provide a service via an API, it's best to store that information so future queries can be done faster and the load on your service is lessened. [WordPress transients](http://codex.wordpress.org/Transients_API) can be used to store data for a certain amount of time. 20. **Log data that can be useful for debugging purposes**, with two conditions: Allow any logging as an 'opt in', and Use the [WC_Logger](https://woocommerce.com/wc-apidocs/class-WC_Logger.html) class. A user can then view logs on their system status page. Learn [how to add a link to logged data](/docs/code-snippets/link-to-logged-data) in your extension. 21. **Test Your Code with [WP_DEBUG](http://codex.wordpress.org/Debugging_in_WordPress)** mode on, so you can see all PHP warnings sent to the screen. This will flag things like making sure a variable is set before checking the value. 22. **Integrate the [Quality Insights Toolkit (QIT)](https://qit.woo.com/docs/) into your development workflow**: The QIT allows the ability to test your extensions against new releases of PHP, WooCommerce, and WordPress, as well as other active extensions, at the same time. Additionally, the toolkit includes a [command-line interface (CLI) tool](https://qit.woo.com/docs/installation-setup/cli-installation) that allows you to run and view tests against development builds. This helps to catch errors before releasing a new version. --- ## GDPR compliance guidelines for WooCommerce extensions *Source: extensions/best-practices-extensions/gdpr-compliance.md* # GDPR compliance guidelines for WooCommerce extensions ## Introduction The General Data Protection Regulation (GDPR) is in effect, granting EU residents increased rights over their personal data. Developers must ensure that WooCommerce extensions are compliant with these regulations. ## Data sharing and collection ### Third-party data sharing - Assess and document any third-party data sharing. - Obtain and manage user consent for data sharing. - Link to third-party privacy policies in your plugin settings. ### Data collection - List the personal data your plugin collects. - Secure consent for data collection and manage user preferences. - Safeguard data storage and restrict access to authorized personnel. ## Data access and storage ### Accessing personal data - Specify what personal data your plugin accesses from WooCommerce orders. - Justify the necessity for accessing each type of data. - Control access to personal data based on user roles and permissions. ### Storing personal data - Explain your data storage mechanisms and locations. - Apply encryption to protect stored personal data. - Perform regular security audits. ## Personal data handling ### Data Exporter and erasure hooks - Integrate data exporter and erasure hooks to comply with user requests. - Create a user-friendly interface for data management requests. ### Refusal of data erasure - Define clear protocols for instances where data erasure is refused. - Communicate these protocols transparently to users. ## Frontend and backend data exposure ### Data on the frontend - Minimize personal data displayed on the site's frontend. - Provide configurable settings for data visibility based on user status. ### Data in REST API rndpoints - Ensure REST API endpoints are secure and disclose personal data only as necessary. - Establish clear permissions for accessing personal data via the API. ## Privacy documentation and data management ### Privacy policy documentation - Maintain an up-to-date privacy policy detailing your plugin's data handling. - Include browser storage methods and third-party data sharing in your documentation. ### Data cleanup - Implement data cleanup protocols for plugin uninstallation and deletion of orders/users. - Automate personal data removal processes where appropriate. ## Conclusion - Keep a record of GDPR compliance measures and make them accessible to users. - Update your privacy policy regularly to align with any changes in data processing activities. --- ## Privacy Standards for WooCommerce extensions *Source: extensions/best-practices-extensions/privacy-standards.md* # Privacy Standards for WooCommerce extensions Privacy and data protection are becoming increasingly important online, and WooCommerce extensions are no exception. This document outlines the key markers of quality regarding privacy for WooCommerce extensions, the current methods for testing compliance, and the standards developers should adhere to ensure user trust and legal compliance. ## Standards for privacy To maintain high privacy standards, developers of WooCommerce extensions should adhere to the following: ### Presence of privacy policy page - A privacy policy should be readily available and easy to understand. It must clearly describe the types of data collected by the extension, the purpose of data collection, how data is processed and stored, and the users' rights concerning their data. ### No unauthorized external requests - All resources required by the extension should be hosted within the plugin folder/zip file unless there's a clear, justified reason for external requests, which should then be disclosed in the privacy policy. - Unauthorized or undisclosed external requests, especially those that could expose user data to third-party services without consent, are strictly against privacy standards. ### GDPR compliance For guidance on ensuring your WooCommerce extension complies with the General Data Protection Regulation (GDPR), please refer to our detailed documentation on [GDPR compliance](./gdpr-compliance.md). ## Conclusion Adhering to privacy standards is essential for WooCommerce extension developers to build trust with users and ensure compliance with legal requirements. By establishing a clear privacy policy and avoiding unauthorized external requests, developers can demonstrate their commitment to privacy and data protection. Regular testing and compliance checks will help maintain these standards and protect user data effectively. --- ## Support and documentation for WooCommerce extensions *Source: extensions/best-practices-extensions/support-and-documentation.md* # Support and documentation for WooCommerce extensions Effective support and comprehensive documentation are fundamental to the success of any WooCommerce extension. They not only enhance the user experience but also facilitate easier maintenance and development. This document outlines the best practices for creating documentation, providing support, and establishing feedback mechanisms. ## Creating effective documentation Good documentation serves as a guide, helping users understand and utilize your extension to its full potential. It can significantly reduce the volume of support requests by answering common questions and troubleshooting typical issues. ### Importance of good documentation - **User Autonomy**: Allows users to solve problems independently. - **Reduced Support Load**: Comprehensive documentation can answer many user questions, reducing the need for direct support. - **Improved User Satisfaction**: Users are more satisfied with the product when they can easily find information. ### How to create effective documentation - **User-friendly language**: Write in clear, simple language accessible to users of all skill levels. - **Comprehensive coverage**: Cover all features of your extension, including setup, configuration, troubleshooting, and FAQs. - **Accessibility**: Ensure documentation is easy to navigate with a clear structure and search functionality. - **Regular updates**: Keep the documentation up to date with the latest version of the extension. ### Standards for documentation - **Presence of public documentation**: Ensure that users have access to basic documentation that covers essential information about the extension. - **Quality of public documentation**: Documentation should be clear, concise, and free of technical jargon. - **Developer-oriented documentation**: Provide detailed documentation aimed at developers who wish to extend or integrate with your extension. - **Advanced internal documentation**: Maintain in-depth documentation for internal use, covering complex features and developer notes. ## Providing support Offering timely and effective support is crucial for resolving user issues and maintaining a positive reputation. ### Best practices for offering support - **Service level agreements (SLA)**: Establish clear SLAs for response times to manage user expectations. - **Knowledgeable staff**: Ensure your team is well-trained and familiar with the extension and common issues. - **Proactive support**: Monitor for common issues and reach out to users who might be affected. ### Standards for support - **Support - SLA**: Define an SLA for how quickly support requests will be acknowledged and resolved. - **Support quality**: Regularly review support interactions to ensure quality, accuracy, and helpfulness. ## Feedback mechanisms Feedback is invaluable for continuous improvement of your extension and support services. ### How to establish and utilize feedback channels - **Surveys and feedback forms**: Implement post-support surveys and feedback forms on your documentation pages. - **Community forums**: Engage with users on community forums where they can share feedback, ask questions, and offer suggestions. - **Social media and email**: Encourage feedback through social media channels and support emails. ### Utilizing feedback to improve - **Act on feedback**: Regularly review feedback to identify areas for improvement in your extension and support services. - **Update documentation**: Use feedback to fill gaps in your documentation and clarify existing content. - **Train support staff**: Use common questions and feedback to train your support staff and improve their knowledge base. ## Conclusion Support and documentation are critical components of the overall user experience for WooCommerce extensions. By investing in high-quality documentation, providing excellent support, and actively seeking and utilizing user feedback, developers can enhance user satisfaction, reduce support workload, and continuously improve their extensions. --- ## Core concepts *Source: extensions/core-concepts/README.md* # Core concepts This section covers the fundamental principles, best practices, and essential knowledge you need to develop robust, maintainable WooCommerce extensions. Learn about everything from basic setup and architecture to advanced development patterns. Whether you're building your first WooCommerce extension or maintaining existing ones, these guides will help you follow best practices and create high-quality code. ## Getting started [Check if WooCommerce is active](./check-if-woo-is-active.md) to learn the proper way to ensure WooCommerce is installed and active before your code runs. This prevents errors and ensures your extension works reliably. You'll also want to understand the [core WooCommerce classes](./class-reference.md) and how to work with them, from the main `WooCommerce` class to `WC_Product`, `WC_Customer`, and `WC_Cart`. ## Development patterns [Adding actions and filters](./adding-actions-and-filters.md) to master the art of extending WooCommerce through hooks. Learn when and how to add actions and filters, following WordPress and WooCommerce standards. For long-term success, discover strategies for [writing maintainable code](./maintainability.md) and establishing update processes that keep your extensions current and secure. You'll also need to [manage deactivation and uninstallation](./handling-deactivation-and-uninstallation.md) to ensure your extension cleans up properly when deactivated or uninstalled, including scheduled actions, admin notes, and tasks. ## Plugin structure and standards See the [example header plugin comment](./example-header-plugin-comment.md) format for your extension's main plugin file header, including all required metadata. You'll also want to learn the standard [changelog format](./changelog-txt.md) for documenting changes in your extension's changelog file, and understand the [WooCommerce plugin API callback](./woocommerce-plugin-api-callback.md) for proper integration with WooCommerce's plugin API for seamless functionality. --- ## How to add actions and filters *Source: extensions/core-concepts/adding-actions-and-filters.md* # How to add actions and filters Like many WordPress plugins, WooCommerce provides a range of actions and filters through which developers can extend and modify the platform. Often, when writing new code or revising existing code, there is a desire to add new hooks-but this should always be done with thoughtfulness and care. This document aims to provide high-level guidance on the matter. Practices we generally allow, support and encourage include: * [Using existing hooks (or other alternatives) in preference to adding new hooks](#prefer-existing-hooks-or-other-alternatives) * [Adding lifecycle hooks](#adding-lifecycle-hooks) * [Optional escape hooks](#escape-hooks) * [Modifying the inputs and outputs of global rendering functions](#modifying-function-input-and-output-global-rendering-functions) * [Preferring the passing of objects over IDs](#prefer-passing-objects-over-ids) On the flip side, there are several practices we discourage: * [Tying lifecycle hooks to methods of execution](#tying-lifecycle-hooks-to-methods-of-execution) * [Using filters as feature flags](#using-filters-as-feature-flags) * [Placing filter hooks inside templates and data stores](#placement-of-filter-hooks) * [Enumeration values within hook names](#enumeration-values-inside-hook-names) Beyond those items, we generally otherwise adhere to WordPress coding standards. In regards to hooks, that specifically means following the: * [Documentation standards for hooks](https://make.wordpress.org/core/handbook/best-practices/inline-documentation-standards/php/#4-hooks-actions-and-filters) * [Guidance on Dynamic hook names](https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/#interpolation-for-naming-dynamic-hooks) Please note that we provide example code throughout this guide to help illustrate some of the principles. However, to keep things concise, we usually omit unnecessary detail, including doc blocks (in practice, though, hooks should always be accompanied by doc blocks!). ## Prefer existing hooks (or other alternatives) Hooks come with a long-term obligation: the last thing we want is to add a new hook that developers come to depend on, only to strip it away again. However, this can lead to difficulties when the time comes to refactor a piece of code that contains hooks, sometimes delaying meaningful change or limiting how easily we can implement a change without compromising on backward compatibility commitments. For those reasons, we always prefer that-wherever reasonable-an existing hook or alternative approach in preference to adding a new hook. ## Adding lifecycle hooks Lifecycle hooks can be used to communicate that a lifecycle event is about to start, or that it has concluded. Examples of such events include: * Main product loop * Dispatching emails * Rendering a template * Product or order status changes In general, lifecycle hooks: * Come in pairs ('before' and 'after') * Are always actions, never filters * The 'before' hook will generally always provide callbacks with the arguments array, if there is one * The 'after' hook will generally also provide callbacks with the function's return value, if there is one Note that lifecycle hooks primarily exist to let other systems observe, rather than to modify the result. Of course, this does not stop the function author from additionally providing a filter hook that serves this function. For example, noting that it is the process of fetching the promotions which we view as the "lifecycle event", and not the function itself: ```php function woocommerce_get_current_promotions( ...$args ) { /* Any initial prep, then first lifecycle hook... */ do_action( 'woocommerce_before_get_current_promotions', $args ); /* ...Do actual work, then final lifecycle hook... */ do_action( 'woocommerce_after_get_current_promotions', $result, $args ); /* ...Return the result, optionally via a filter... */ return apply_filters( 'woocommerce_get_current_promotions', $result, $args ); } ``` ## Escape hooks In some cases, it may be appropriate to support short-circuiting of functions or methods. This is what we call an escape hook, and can be useful as a means of overriding code when a better way of doing so is not available. * Escape hooks are always filters * They should always supply null as the initial filterable value * If the value is changed to a non-null value, then the function should exit early by returning that new value For type safety, care should be taken to ensure that, if a function is short-circuited, the return type matches the function signature and/or return type stated in the function doc block. Example: ```php function get_product_metrics( $args ): array { $pre = apply_filters( 'pre_woocommerce_get_product_metrics', null, $args ); if ( $pre !== null ) { return (array) $pre; } /* ...Default logic... */ return $metrics; } ``` ## Modifying function input and output (global rendering functions) In the case of global rendering or formatting functions (so-called "template tags"), where it is not readily possible to implement better alternatives, it is permissible to add filters for both the function arguments and the function's return value. This should be done sparingly, and only where necessary. Remember that while providing opportunities for other components to perform extensive customization, it can potentially derail other components which expect unmodified output. Example: ```php function woocommerce_format_sale_price( ...$args ): string { /* Prep to fill in any missing $args values... */ $args = (array) apply_filters( 'woocommerce_format_sale_price_args', $args ); /* ...Actual work to determine the $price string... */ return (string) apply_filters( 'woocommerce_format_sale_price', $price, $args ); } ``` ## Prefer passing objects over IDs Some actions or filters provide an object ID (such as a product ID) as their primary value, while others will provide the actual object itself (such as a product object). For consistency, it is preferred that objects be passed. Example: ```php function get_featured_product_for_current_customer( ) { /* ...Logic to find the featured product for this customer... */ return apply_filters( 'woocommerce_featured_product_for_current_customer', $product, /* WC_Product */ $customer ); } ``` ## Tying lifecycle hooks to methods of execution There can sometimes be multiple paths leading to the same action. For instance, an order can be updated via the REST API, through the admin environment, or on the front end. It may additionally happen via ajax, or via a regular request. It is important however not to tie hooks for high-level processes to specific execution paths. For example, an action that fires when an order is created must not only be fired when this happens in the admin environment via an ajax request. Instead, prefer a more generic hook that passes context about the method of execution to the callback. Example of what we wish to avoid: ```php /** * Pretend this function is only called following an ajax request * (perhaps it is itself hooked in using a `wp_ajax_*` action). */ function on_ajax_order_creation() { /* Avoid this! */ do_action( 'woocommerce_on_order_creation' ); } ``` ## Using filters as feature flags It is sometimes tempting to use a filter as a sort of feature flag, that enables or disables a piece of functionality. This should be avoided! Prefer using an option: * Options persist in the database. * Options are already filterable (ideal for a temporary override). Example of what we wish to avoid: ```php /* Avoid this */ $super_products_enabled = (bool) apply_filters( 'woocommerce_super_products_are_enabled', true ); /* Prefer this */ $super_products_enabled = get_option( 'woocommerce_super_products_are_enabled', 'no' ) === 'yes'; ``` ## Placement of filter hooks Filters should not be placed inside templates-only actions. If it is important that a value used within a template be filterable, then the relevant logic should be moved to whichever function or method decides to load the template-the result being passed in as a template variable. It is also preferred that filter hooks not be placed inside data-store classes, as this can reduce the integrity of those components: since, by design, they are replaceable by custom implementations-the risk of accidentally breaking those custom stores is higher. ## Enumeration values inside hook names Though there is a case for dynamic hook names (where part of the hook name is created using a variable), a good rule of thumb is to avoid this if the variable contains what might be considered an enumeration value. This might for instance include a case where an error code forms part of the hook name. Example (of what we wish to avoid): ```php if ( is_wp_error( $result ) ) { /* Avoid this */ $error_code = $result->get_error_code(); do_action( "woocommerce_foo_bar_{$error_code}_problem", $intermediate_result ); /* Prefer this */ do_action( 'woocommerce_foo_bar_problem', $result ); } ``` The primary reason for avoiding this is that the more values there are in the enumeration set, the more filters developers have to include in their code. ## Summary This document is a high-level guide to the inclusion and placement of hooks, not an exhaustive list. There will occasionally be exceptions, and there may be good rules and methodologies we are missing: if you have suggestions or ideas for improvement, please reach out! --- ## Formatting for Changelog.txt *Source: extensions/core-concepts/changelog-txt.md* # Formatting for Changelog.txt ## The Changelog.txt file WooCommerce extensions use a standard changelog format. Your `changelog.txt` file should look like this: ```php *** WooCommerce Extension Name Changelog *** YYYY-MM-DD - version 1.1.0 * Added - Useful new feature * Fixed - Important bug fix YYYY-MM-DD - version 1.0.1 * Fixed a bug YYYY-MM-DD - version 1.0.0 * Initial release ``` ## Changelog Entry Types To showcase the different types of work done in a product update, use any of the following words to denote what type of change each line is: - add - added - feature - new - developer - dev - tweak - changed - update - delete - remove - fixed - fix ![Example changelog as shown on WooCommerce.com](https://woocommerce.com/wp-content/uploads/2023/12/image-9.png) --- ## How to check if WooCommerce is active *Source: extensions/core-concepts/check-if-woo-is-active.md* # How to check if WooCommerce is active When developing for WooCommerce, ensuring that WooCommerce is installed and active before your code runs is crucial. This prevents errors related to missing WooCommerce functions or classes. There are a few methods to achieve this. The first is to execute your code on the `woocommerce_loaded` action. This approach guarantees that WooCommerce and its functionalities are fully loaded and available for use. This is fired around the same time as the core `plugins_loaded` action. ```php add_action( 'woocommerce_loaded', 'prefix_woocommerce_loaded' ); function prefix_woocommerce_loaded() { // Custom code here. WooCommerce is active and all plugins have been loaded... } ``` **Note**: At this stage, WordPress has not yet initialized the current user data. Another method is to execute your code on the `woocommerce_init` action. This is executed right _after_ WooCommerce is active and initialized. This action (and the `before_woocommerce_init` action) fires in the context of the WordPress `init` action so at this point current user data has been initialized. ```php add_action( 'woocommerce_init', 'prefix_woocommerce_init' ); function prefix_woocommerce_init() { // Custom code here. WooCommerce is active and initialized... } ``` **Note**: The `before_woocommerce_init` hook is also an option, running just _before_ WooCommerce's initialization Using the above hooks grants access to WooCommerce functions, enabling further condition checks. For instance, you might want to verify WooCommerce's version to ensure compatibility with your code: ```php add_action( 'woocommerce_init', 'prefix_woocommerce_init' ); function prefix_woocommerce_init() { // Only continue if we have access to version 8.7.0 or higher. if ( version_compare( wc()->version, '8.7.0', '<' ) ) { return; } // Custom code here. WooCommerce is active and initialized... } ``` Choosing the right hook based on your development needs ensures your WooCommerce extensions or customizations work seamlessly and efficiently. --- ## Classes in WooCommerce *Source: extensions/core-concepts/class-reference.md* # Classes in WooCommerce ## List of Classes in WooCommerce For a list of Classes in WooCommerce, please see the [WooCommerce Code Reference](https://woocommerce.github.io/code-reference/packages/WooCommerce-Classes.html). ## Common Classes ### WooCommerce The main class is `woocommerce` which is available globally via the `$woocommerce` variable. This handles the main functions of WooCommerce and init's other classes, stores site-wide variables, and handles error/success messages. The woocommerce class initializes the following classes when constructed: - `WC_Query` - stored in `$woocommerce->query` - `WC_Customer` - stored in `$woocommerce->customer` - `WC_Shipping` - stored in `$woocommerce->shipping` - `WC_Payment_Gateways` - stored in `$woocommerce->payment_gateways` - `WC_Countries` - stored in `$woocommerce->countries` Other classes are auto-loaded on demand. View the [WooCommerce Class Code Reference](https://woocommerce.github.io/code-reference/classes/WooCommerce.html) for a full list of methods contained in this class. ### WC_Product WooCommerce has several product classes responsible for loading and outputting product data. This can be loaded through PHP using: `$product = wc_get_product( $post->ID );` In the loop this is not always necessary since calling `the_post()` will automatically populate the global `$product` variable if the post is a product. View the [WC_Product Code Reference](https://woocommerce.github.io/code-reference/classes/WC-Product.html) for a full list of methods contained in this class. ### WC_Customer The customer class allows you to get data about the current customer, for example: ```php global $woocommerce; $customer_country = $woocommerce->customer->get_country(); ``` View the [WC_Customer Code Reference](https://woocommerce.github.io/code-reference/classes/WC-Customer.html) for a full list of methods contained in this class. ### WC_Cart The cart class loads and stores the users cart data in a session. For example, to get the cart subtotal you could use: ```php global $woocommerce; $cart_subtotal = $woocommerce->cart->get_cart_subtotal(); ``` View the [WC_Cart Code Reference](https://woocommerce.github.io/code-reference/classes/WC-Cart.html) for a full list of methods contained in this class. --- ## Example WordPress plugin header comment for WooCommerce extensions *Source: extensions/core-concepts/example-header-plugin-comment.md* # Example WordPress plugin header comment for WooCommerce extensions This is a WordPress plugin header comment. It's used to provide WordPress with metadata about a plugin. ```php /** * Plugin Name: WooCommerce Extension * Plugin URI: https://woocommerce.com/products/woocommerce-extension/ * Description: Your extension's description text. * Version: 1.0.0 * Author: Your Name * Author URI: http://yourdomain.com/ * Developer: Your Name * Developer URI: http://yourdomain.com/ * Text Domain: woocommerce-extension * Domain Path: /languages * * WC requires at least: 8.0 * WC tested up to: 8.3 * * License: GNU General Public License v3.0 * License URI: http://www.gnu.org/licenses/gpl-3.0.html * Woo: 12345:342928dfsfhsf8429842374wdf4234sfd */ ``` Here's what each line should contain: * Plugin Name: The name of your plugin. * Plugin URI: The home page of the plugin or the product page on WooCommerce.com. * Description: A short description of the plugin. * Version: The current version number of the plugin. * Author: The name of the plugin author. * Author URI: The author's website or profile page. * Developer: The name of the developer if different from the author. * Developer URI: The developer's website or profile page. * Text Domain: The text domain is used for internationalization. * Domain Path: The domain path is used to show where the MO files are located. * WC requires at least: The minimum version of WooCommerce required for the plugin to work. * WC tested up to: The latest version of WooCommerce that the plugin has been tested with. * License: The license of the plugin. * License URI: The URL where the license is explained in detail. * Woo: A unique identifier for a plugin sold on WooCommerce.com. When submitting your extension or adding a new version, **we will automatically add this to the header of your main file**. You are not required to add it manually, but you can opt to include it before uploading. This header comment is placed at the top of the main plugin file, so WordPress can read it. --- ## Managing extension deactivation and uninstallation *Source: extensions/core-concepts/handling-deactivation-and-uninstallation.md* # Managing extension deactivation and uninstallation ## Introduction There are a number of cleanup tasks you'll need to handle when a merchant deactivates or uninstalls your extension. This guide provides a brief overview of WooCommerce-specific items you'll want to make sure you account for when defining your extension's deactivation and uninstallation logic. ## Removing Scheduled Actions If your extension uses Action Scheduler to queue any background jobs, it's important to unschedule those actions when your extension is uninstalled or deactivated. `as_unschedule_all_actions( $hook, $args, $group );` You can read more about using Action Scheduler for managing background processing in the [Action Scheduler API Reference](https://actionscheduler.org/api/). ## Removing Admin Notes If you have created any Notes for merchants, you should delete those notes when your extension is deactivated or, at the very least, when it is uninstalled. ```php function my_great_extension_deactivate() { ExampleNote::possibly_delete_note(); } register_deactivation_hook( __FILE__, 'my_great_extension_deactivate' ); ``` The example above assumes that you have followed the pattern this guide recommends for creating Notes as dedicated classes that include the `NoteTraits` trait included with WooCommerce Admin. This approach provides your Note with some baked in functionality that streamlines note operations such as creation and deletion. ## Removing Admin Tasks When your extension is deactivated or uninstalled, you should take care to unregister any tasks that your extension created for merchants. ```php // Unregister task. function my_extension_deactivate_task() { remove_filter( 'woocommerce_get_registered_extended_tasks', 'my_extension_register_the_task', 10, 1 ); } register_deactivation_hook( __FILE__, 'my_extension_deactivate_task' ); ``` Keep in mind that merchant tasks are managed via a hybrid approach that involves both PHP and JavaScript, so the client-side registration only happens when your extension's JavaScript runs. ## Unregistering navigation When your extension deactivates and uninstalls, any registration you've done with the WooCommerce Navigation will be handled automatically. ## WordPress cleanup tasks There are additional measures you may need to consider when your extension is deactivated or uninstalled, depending on the types of modifications it makes to the underlying WordPress environment when it activates and runs. You can read more about handling deactivation and uninstallation in the [WordPress Plugin Developer Handbook](https://developer.wordpress.org/plugins/intro/). --- ## Maintaining and updating WooCommerce extensions *Source: extensions/core-concepts/maintainability.md* # Maintaining and updating WooCommerce extensions Maintaining and updating WooCommerce extensions is crucial for ensuring they remain compatible, secure, and functional within the ever-evolving WordPress ecosystem. This document outlines best practices for ensuring easy maintainability, adhering to update frequency and process, and conducting manual update checks. ## Ensuring easy maintainability Maintainable code is essential for the long-term success of any WooCommerce extension. It ensures that your extension can be easily updated, debugged, and extended, both by you and others in the future. ### Importance of writing maintainable code - **Future-proofing**: Maintainable code helps in adapting to future WooCommerce and WordPress updates. - **Collaboration**: Makes it easier for teams to work together on the extension. - **Cost-effective**: Reduces the time and resources required for adding new features or fixing issues. ### Strategies to achieve maintainability - **Modular code**: Break down your extension into smaller, focused modules or components. - **Coding standards**: Follow the [WordPress Coding Standards](https://developer.wordpress.org/coding-standards/) to ensure consistency. - **Documentation**: Document your code extensively to explain "why" behind the code, not just "how" to use it. - **Refactoring**: Regularly refactor your code to improve its structure without altering the external behavior. ## Update frequency and process Keeping your extension up-to-date is vital for security, compatibility, and performance. Regular updates also signal to users that the extension is actively maintained. ### Best practices for regular updates - **Scheduled updates**: Plan regular updates (e.g., monthly) to incorporate bug fixes, security patches, and new features. - **Version control**: Use version control systems like Git to manage changes and collaborate efficiently. - **Compatibility checks**: Before releasing an update, thoroughly test your extension with the latest versions of WordPress and WooCommerce to ensure compatibility. - **Changelogs**: Maintain clear changelogs for each update to inform users about new features, fixes, and changes. ### Recommended update frequency - We recommend that extensions receive an update **at least once every 30 days**. This frequency ensures that extensions can quickly adapt to changes in WooCommerce, WordPress, or PHP, and address any security vulnerabilities or bugs. ## Manual update checks While automated update systems like the WordPress Plugin Repository offer a way to distribute updates, developers should also have a process for manually tracking and managing updates. ### How developers can manually track and manage updates - **User feedback**: Monitor forums, support tickets, and user feedback for issues that may require updates. - **Security monitoring**: Stay informed about the latest security vulnerabilities and ensure your extension is not affected. - **Performance testing**: Regularly test your extension for performance and optimize it in updates. - **Compatibility testing**: Manually test your extension with beta releases of WordPress and WooCommerce to anticipate compatibility issues before they arise. ## Conclusion Maintainability and regular updates are key to the success and longevity of WooCommerce extensions. By writing maintainable code, adhering to a consistent update process, and actively monitoring the extension's performance and compatibility, developers can ensure their products remain valuable and functional for users over time. --- ## WooCommerce Plugin API callbacks *Source: extensions/core-concepts/woocommerce-plugin-api-callback.md* # WooCommerce Plugin API callbacks ## Overview This document provides a guide on how to use the WooCommerce Plugin API to initiate callbacks for plugin actions, especially for gateways and classes not initialized by default. ## Callback URL Structure Before WooCommerce 2.0, use: `https://example.com/?wc-api=CALLBACK` In WooCommerce 2.0 or later, use the endpoint: `https://example.com/wc-api/CALLBACK/` ## Behavior When the callback URL is accessed, WooCommerce will: - Initialize the `CALLBACK` class, if available - Trigger the `woocommerce_api_callback` action - Exit WordPress ## Hooking into the API Callback To hook into the callback, add an action in your plugin: ```php add_action( 'woocommerce_api_callback', 'your_callback_handler_function' ); ``` ## Redirecting After Callback It's possible to redirect users after the action has been executed using your custom handler function. --- ## How to add custom product types to Add Products onboarding list *Source: extensions/extension-onboarding/adding-custom-products-to-add-products-onboarding-list.md* # How to add custom product types to Add Products onboarding list ## Introduction WooCommerce allows developers to extend the product type onboarding list, offering a more customizable and engaging experience during the Add Products onboarding task. This tutorial will guide you through adding custom product types to your WooCommerce store using the `experimental_woocommerce_tasklist_product_types` JavaScript filter. ## Prerequisites - A basic understanding of JavaScript and PHP. - WooCommerce 8.8 or later installed on your WordPress site. ## Step 1: Adding a JavaScript Filter To add a new product type to the onboarding list, we'll utilize the `@wordpress/hooks` package, specifically the addFilter function. If you're not already familiar, `@wordpress/hooks` allows you to modify or extend features within the WordPress and WooCommerce ecosystem without altering the core code. First, ensure you have the `@wordpress/hooks` package installed. If not, you can add it to your project using `npm` or `yarn`: `npm install @wordpress/hooks` or: `yarn add @wordpress/hooks` Next, add the following JavaScript code to your project. This code snippet demonstrates how to add a "custom product" type to the onboarding list: ```javascript /** * External dependencies */ import { addFilter } from '@wordpress/hooks'; import { Icon, chevronRight } from '@wordpress/icons'; import { __ } from '@wordpress/i18n'; import FolderMultipleIcon from 'gridicons/dist/folder-multiple'; addFilter( 'experimental_woocommerce_tasklist_product_types', 'custom-product', (productTypes) => [ ...productTypes, { key: 'custom-product', title: __('Custom product', 'custom-product'), content: __('Create an awesome custom product.', 'custom-product'), before: , after: , onClick: () => { } }, ] ); ``` This filter adds a new product type called "Custom Product" with a brief description and icons before and after the title for a visually appealing presentation. ## Step 2: Optional - Customizing the onClick Handler By default, if no onClick handler is supplied, the onboarding task will utilize the default CSV template handler. To customize this behavior, you can specify your own onClick handler within the product type object. ## Step 3: Modifying the CSV Template Path (Optional) If you wish to use a different CSV template for your custom product type, you can modify the template path using the woocommerce_product_template_csv_file_path filter in PHP. Here's an example of how to change the template path: ```php add_filter('woocommerce_product_template_csv_file_path', function($path) { // Specify your custom template path here return $newPath; }); ``` ## Conclusion With WooCommerce, extending the product type onboarding list is straightforward and offers significant flexibility for customizing the onboarding experience. By following the steps outlined in this tutorial, you can enhance your WooCommerce store and make the Add Products task more relevant and helpful to your specific needs. --- ## How to create custom product tours *Source: extensions/extension-onboarding/creating-custom-product-tours.md* # How to create custom product tours ## Introduction WooCommerce allows developers to extend or replace the product tour, offering a more customizable and engaging experience during product creation. This tutorial will guide you through adding a custom product tour to your WooCommerce store using the `experimental_woocommerce_admin_product_tour_steps` JavaScript filter. This works in conjunction with the ability to customize the product type onboarding list. ## Prerequisites - A basic understanding of JavaScript and PHP. - WooCommerce 8.8 or later installed on your WordPress site. ## Adding a JavaScript Filter To alter or create a product tour, we'll utilize the `@wordpress/hooks` package, specifically the `addFilter` function. If you're not already familiar, `@wordpress/hooks` allows you to modify or extend features within the WordPress and WooCommerce ecosystem without altering the core code. First, ensure you have the `@wordpress/hooks` package installed. If not, you can add it to your project using `npm` or `yarn`: `npm install @wordpress/hooks` or: `yarn add @wordpress/hooks` Next, add the following JavaScript code to your project. This code snippet demonstrates how to replace the product tour with an entire custom one: ```javascript /** * External dependencies */ import { addFilter } from '@wordpress/hooks'; import { __ } from '@wordpress/i18n'; addFilter( experimental_woocommerce_admin_product_tour_steps, 'custom-product', (tourSteps, tourType) => { if ('custom-product' !== tourType) { return tourSteps; } return [ { referenceElements: { desktop: '#title',// The element to highlight }, focusElement: { desktop: '#title',// A form element to be focused }, meta: { name: 'product-name', // Step name heading: __( 'Product name', 'custom-product' ), descriptions: { desktop: __( 'Start typing your new product name here. This will be what your customers will see in your store.', 'custom-product' ), }, }, }, ]; } ); ``` This filter replaces the entire product tour for a `custom-product` product type. Using built-in JavaScript array manipulation functions, you can also customize the default tour (by altering, adding, or removing steps). The `tourType` is set by the `tutorial_type` GET parameter. ## Conclusion With WooCommerce, extending and customizing the product tour is straightforward and offers significant flexibility for customizing the onboarding experience. By following the steps outlined in this tutorial, you can enhance your WooCommerce store and make the Add Products tour more relevant and helpful to your specific needs. --- ## How to implement merchant onboarding *Source: extensions/extension-onboarding/handling-merchant-onboarding.md* # How to implement merchant onboarding ## Introduction Onboarding is a critical part of the merchant's user experience. It helps set them up for success and ensures they're not only using your extension correctly but also getting the most out of it. There are a few especially useful features that you can take advantage of as a developer to help onboard merchants who are using your extension: - Setup tasks - Store management links - Admin notes --- ## Using setup tasks Setup tasks appear on the WooCommerce Admin home screen and prompt a merchant to complete certain steps in order to set up your extension. Adding tasks is a two-step process that requires: - Registering the task (and its JavaScript) using PHP - Using JavaScript to build the task, set its configuration, and add it to the task list ### Registering the task with PHP To register your task as an extended task list item, you'll need to start by creating a new PHP class that extends the Task class. This class will define the properties and behavior of your custom task. ```php { // Implement your task UI/feature here. return
; }; registerPlugin( 'add-task-content', { render: () => ( { ( { onComplete, query, task } ) => ( ) } ), } ); registerPlugin( 'add-task-list-item', { scope: 'woocommerce-tasks', render: () => ( { ( { defaultTaskItem: DefaultTaskItem } ) => ( // Add a custom wrapper around the default task item.
) }
), } ); ``` In the example above, the extension does a few different things. Let's break it down: #### Handle imports First, import any functions, components, or other utilities from external dependencies. ```js import { createElement } from '@wordpress/element'; import { WooOnboardingTask, WooOnboardingTaskListItem, } from '@woocommerce/onboarding'; import { registerPlugin } from '@wordpress/plugins'; ``` #### Construct the component Next, we create a [functional component](https://reactjs.org/docs/components-and-props.html) that returns our task card. The intermixed JavaScript/HTML syntax we're using here is called JSX. If you're unfamiliar with it, you can [read more about it in the React docs](https://reactjs.org/docs/introducing-jsx.html). ```js import { onboardingStore } from '@woocommerce/data'; import { useDispatch } from '@wordpress/data'; const Task = ( { onComplete, task } ) => { const { actionTask } = useDispatch( onboardingStore ); const { isActioned } = task; return ( { __( "This task's completion status is dependent on being actioned. The action button below will action this task, while the complete button will optimistically complete the task in the task list and redirect back to the task list. Note that in this example, the task must be actioned for completion to persist.", 'plugin-domain' ) }{ ' ' }

{ __( 'Task actioned status: ', 'plugin-domain' ) }{ ' ' } { isActioned ? 'actioned' : 'not actioned' }

); }; ``` In the example above, we're using the `Card` and `CardBody` components to construct our task's component. The `div` inside the `CardBody` uses a [JavaScript expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Expressions) (`{}`) to embed a ternary operator that uses the component's state to determine whether to display the task as complete or incomplete. #### Register the Plugin for Task Content Next, we register the Task component as a plugin named "add-task-content" using [SlotFills](https://developer.wordpress.org/block-editor/reference-guides/slotfills/). This plugin nests the Task component within a WooOnboardingTask component and passes the necessary properties. We also specify the scope of the plugin as "woocommerce-tasks" to make it effective only within WooCommerce's task list. ```js registerPlugin( 'add-task-content', { render: () => ( { ( { onComplete, // eslint-disable-next-line @typescript-eslint/no-unused-vars query, task, } ) => } ), scope: 'woocommerce-tasks', } ); ``` #### Register the Plugin for Task List Item Customization Finally, we register another plugin named "my-task-list-item-plugin." This plugin is used to customize the appearance of task list items. It also targets WooCommerce's task list and wraps the DefaultTaskItem component within a custom wrapper with additional styling. ```js registerPlugin( 'my-task-list-item-plugin', { scope: 'woocommerce-tasks', render: () => ( { ( { defaultTaskItem: DefaultTaskItem } ) => ( // Add a custom wrapper around the default task item.
) }
), } ); ``` In summary, the JavaScript file for a simple task extends and customizes the functionality of WooCommerce's task list, allowing users to better manage tasks and personalize the appearance of task list items. ### Registering the task with JavaScript In addition to registering the task in php, you'll also need to register and enqueue the transpiled JavaScript file containing your task component and its configuration. A common way to do this is to create a dedicated registration function that hooks into the `admin_enqueue_scripts` action in WordPress. Below is an annotated example of how this registration might look: ```php /** * Register the scripts to fill the task content on the frontend. */ function add_task_register_script() { if ( ! class_exists( 'Automattic\WooCommerce\Internal\Admin\Loader' ) || ! \Automattic\WooCommerce\Admin\PageController::is_admin_or_embed_page() ) { return; } $asset_file = require __DIR__ . '/dist/index.asset.php'; wp_register_script( 'add-task', plugins_url( '/dist/index.js', __FILE__ ), // task registration JS $asset_file['dependencies'], $asset_file['version'], true ); wp_enqueue_script( 'add-task' ); } add_action( 'admin_enqueue_scripts', 'add_task_register_script' ); ``` By following these steps, your custom task should appear in the WooCommerce onboarding tasklist. For a complete example of adding a custom task as a WordPress plugin, you can check out the [add-task examples directory](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce/client/admin/docs/examples/extensions/add-task). To learn more about the tasklist, you can refer to the [tasklist documentation](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/client/admin/docs/features/onboarding-tasks.md). --- ## Using Store Management Links When a merchant completes all of the items on the onboarding task list, WooCommerce replaces it with a section containing a list of handy store management links. Discoverability can be a challenge for extensions, so this section is a great way to bring more attention to key features of your extension and help merchants navigate to them. The store management section has a relatively narrow purpose, so this section does not currently support external links. Instead, it is meant for navigating quickly within WooCommerce. Adding your own store management links is a simple process that involves: - Installing dependencies for icon support - Enqueuing an admin script in your PHP - Hooking in via a JavaScript filter to provide your link object ### Installing the Icons package Store management links use the `@wordpress/icons` package. If your extension isn't already using it, you'll need to add it to your extension's list of dependencies. `npm` `install` ` @wordpress``/icons ` `--save` ### Enqueuing the JavaScript The logic that adds your custom link to the store management section will live in a JavaScript file. We'll register and enqueue that file with WordPress in our PHP file: ```js function custom_store_management_link() { wp_enqueue_script( 'add-my-custom-link', plugins_url( '/dist/add-my-custom-link.js', __FILE__ ), array( 'wp-hooks' ), 10 ); } add_action( 'admin_enqueue_scripts', 'custom_store_management_link' ); ``` The first argument of this call is a handle, the name by which WordPress will refer to the script we're enqueuing. The second argument is the URL where the script is located. The third argument is an array of script dependencies. By supplying the `wp-hooks` handle in that array, we're ensuring that our script will have access to the `addFilter` function we'll be using to add our link to WooCommerce's list. The fourth argument is a priority, which determines the order in which JavaScripts are loaded in WordPress. We're setting a priority of 10 in our example. It's important that your script runs before the store management section is rendered. With that in mind, make sure your priority value is lower than 15 to ensure your link is rendered properly. ### Supply your link via JavaScript Finally, in the JavaScript file you enqueued above, hook in to the `woocommerce_admin_homescreen_quicklinks` filter and supply your task as a simple JavaScript object. ```js import { megaphone } from '@wordpress/icons'; import { addFilter } from '@wordpress/hooks'; addFilter( 'woocommerce_admin_homescreen_quicklinks', 'my-extension', ( quickLinks ) => { return [ ...quickLinks, { title: 'My link', href: 'link/to/something', icon: megaphone, }, ]; } ); ``` --- ## Using Admin Notes Admin Notes are meant for displaying insightful information about your WooCommerce store, extensions, activity, and achievements. They're also useful for displaying information that can help with the day-to-day tasks of managing and optimizing a store. A good general rule is to use Admin Notes for information that is: 1. Timely 2. Relevant 3. Useful With that in mind, you might consider using Admin Notes to celebrate a particular milestone that a merchant has passed, or to provide additional guidance about using a specific feature or flow. Conversely, you shouldn't use Admin Notes to send repeated messages about the same topic or target all users with a note that is only relevant to a subset of merchants. It's okay to use Admin Notes for specific promotions, but you shouldn't abuse the system. Use your best judgement and remember the home screen is meant to highlight a store's most important actionable tasks. Despite being a part of the new React-powered admin experience in WooCommerce, Admin Notes are available to developers via a standard PHP interface. The recommended approach for using Admin Notes is to encapsulate your note within its own class that uses the [NoteTraits](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Admin/Notes/NoteTraits.php) trait included with WooCommerce Admin. Below is a simple example of what this might look like: ```php set_title( 'Getting Started' ); // Set our note's content. $note->set_content( sprintf( 'Extension activated on %s.', $activated_time_formatted ) ); // In addition to content, notes also support structured content. // You can use this property to re-localize notes on the fly, but // that is just one use. You can store other data here too. This // is backed by a longtext column in the database. $note->set_content_data( (object) array( 'getting_started' => true, 'activated' => $activated_time, 'activated_formatted' => $activated_time_formatted ) ); // Set the type of the note. Note types are defined as enum-style // constants in the Note class. Available note types are: // error, warning, update, info, marketing. $note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL ); // Set the type of layout the note uses. Supported layout types are: // 'plain', 'thumbnail' $note->set_layout( 'plain' ); // Set the image for the note. This property renders as the src // attribute for an img tag, so use a string here. $note->set_image( '' ); // Set the note name and source. You should store your extension's // name (slug) in the source property of the note. You can use // the name property of the note to support multiple sub-types of // notes. This also gives you a handy way of namespacing your notes. $note->set_source( 'inbox-note-example'); $note->set_name( self::NOTE_NAME ); // Add action buttons to the note. A note can support 0, 1, or 2 actions. // The first parameter is the action name, which can be used for event handling. // The second parameter renders as the label for the button. // The third parameter is an optional URL for actions that require navigation. $note->add_action( 'settings', 'Open Settings', '?page=wc-settings&tab=general' ); $note->add_action( 'learn_more', 'Learn More', 'https://example.com' ); return $note; } } function my_great_extension_activate() { // This uses the functionality from the NoteTraits trait to conditionally add your note if it passes all of the appropriate checks. ExampleNote::possibly_add_note(); } register_activation_hook( __FILE__, 'my_great_extension_activate' ); function my_great_extension_deactivate() { // This uses the functionality from the NoteTraits trait to conditionally remove your note if it passes all of the appropriate checks. ExampleNote::possibly_delete_note(); } register_deactivation_hook( __FILE__, 'my_great_extension_deactivate' ); ``` ### Breaking it down Let's break down the example above to examine what each section does. #### Namespacing and feature availability checks First, we're doing some basic namespacing and feature availability checks, along with a safeguard to make sure this file only executes within the WordPress application space. ```php namespace My\Wonderfully\Namespaced\Extension\Area; defined ( 'ABSPATH' ) || exit; if ( ! class_exists( 'Automattic\WooCommerce\Admin\Notes\Notes') || ! class_exists( 'Automattic\WooCommerce\Admin\Notes\NoteTraits') ) { return; } if ( ! class_exists( 'WC_Data_Store' ) ) { return; } ``` #### Using Note and NoteTraits objects Next, we define a simple class that will serve as a note provider for our note. To create and manage note objects, we'll import the `Note` and `NotesTraits` classes from WooCommerce Admin. ```php class ExampleNote { use Automatic\WooCommerce\Admin\Notes\Note; use Automatic\WooCommerce\Admin\Notes\NoteTraits; } ``` #### Provide a unique note name Before proceeding, create a constant called `NOTE_NAME` and assign a unique note name to it. The `NoteTraits` class uses this constant for queries and note operations. `const NOTE_NAME = 'my-prefix-example-note';` #### Configure the note's details Once you've set your note's name, you can define and configure your note. The `NoteTraits` class will call `self::get_note()` when performing operations, so you should encapsulate your note's instantiation and configuration in a static function called `get_note()` that returns a `Note` object. ```php public static function get_note() { // We'll fill this in with logic that instantiates a Note object // and sets its properties. } ``` Inside our `get_note()` function, we'll handle any logic for collecting data our Note may need to display. Our example note will include information about when the extension was activated, so this bit of code is just for demonstration. You might include other logic here depending on what data your note should contain. ```php $activated_time = current_time( 'timestamp', 0); $activated_time_formatted = date( 'F jS', $activated_time ); ``` Next, we'll instantiate a new `Note` object. `$note = new Note();` Once we have an instance of the Note class, we can work with its API to set its properties, starting with its title. `$note->set_title( 'Getting Started' );` Then we'll use some of the timestamp data we collected above to set the note's content. ```php $note->set_content( sprintf( 'Extension activated on %s.', $activated_time_formatted ) ); ``` In addition to regular content, notes also support structured content using the `content_data` property. You can use this property to re-localize notes on the fly, but that is just one use case. You can store other data here too. This is backed by a `longtext` column in the database. ```php $note->set_content_data( (object) array( 'getting_started' => true, 'activated' => $activated_time, 'activated_formatted' => $activated_time_formatted ) ); ``` Next, we'll set the note's `type` property. Note types are defined as enum-style class constants in the `Note` class. Available note types are _error_, _warning_, _update_, _info_, and _marketing_. When selecting a note type, be aware that the _error_ and _update_ result in the note being shown as a Store Alert, not in the Inbox. It's best to avoid using these types of notes unless you absolutely need to. `$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );` Admin Notes also support a few different layouts. You can specify `banner`, `plain`, or `thumbnail` as the layout. If you're interested in seeing the different layouts in action, take a look at [this simple plugin](https://gist.github.com/octaedro/864315edaf9c6a2a6de71d297be1ed88) that you can install to experiment with them. We'll choose `plain` as our layout, but it's also the default, so we could leave this property alone and the effect would be the same. `$note->set_layout( 'plain' );` If you have an image that you want to add to your Admin Note, you can specify it using the `set_image` function. This property ultimately renders as the `src` attribute on an `img` tag, so use a string here. `$note->set_image( '' );` Next, we'll set the values for our Admin Note's `name` and `source` properties. As a best practice, you should store your extension's name (i.e. its slug) in the `source` property of the note. You can use the `name` property to support multiple sub-types of notes. This gives you a handy way of namespacing your notes and managing them at both a high and low level. ```php $note->set_source( 'inbox-note-example'); $note->set_name( self::NOTE_NAME ); ``` Admin Notes can support 0, 1, or 2 actions (buttons). You can use these actions to capture events that trigger asynchronous processes or help the merchant navigate to a particular view to complete a step, or even simply to provide an external link for further information. The `add_action()` function takes up to three arguments. The first is the action name, which can be used for event handling, the second renders as a label for the action's button, and the third is an optional URL for actions that require navigation. ```php $note->add_action( 'settings', 'Open Settings', '?page=wc-settings&tab=general' ); $note->add_action( 'learn_more', 'Learn More', 'https://example.com' ); ``` Finally, remember to have the `get_note()` function return the configured Note object. `return $note;` #### Adding and deleting notes To add and delete notes, you can use the helper functions that are part of the `NoteTraits` class: `possibly_add_note()` and its counterpart `possibly_delete_note()`. These functions will handle some of the repetitive logic related to note management and will also run checks to help you avoid creating duplicate notes. Our example extension ties these calls to activation and deactivation hooks for the sake of simplicity. While there are many events for which you may want to add Notes to a merchant's inbox, deleting notes upon deactivation and uninstallation is an important part of managing your extension's lifecycle. ```php function my_great_extension_activate() { ExampleNote::possibly_add_note(); } register_activation_hook( __FILE__, 'my_great_extension_activate' ); function my_great_extension_deactivate() { ExampleNote::possibly_delete_note(); } register_deactivation_hook( __FILE__, 'my_great_extension_deactivate' ); ``` --- ## Integrating with coming soon mode *Source: extensions/extension-onboarding/integrating-coming-soon-mode.md* # Integrating with coming soon mode This guide provides examples for third-party developers and hosting providers on how to integrate their systems with WooCommerce's coming soon mode. For more details, please read the [developer blog post](https://developer.woocommerce.com/2024/06/18/introducing-coming-soon-mode/). For site visibility settings, please refer to the [admin documentation](https://woocommerce.com/document/configuring-woocommerce-settings/coming-soon-mode/). ## Introduction WooCommerce's coming soon mode allows you to temporarily make your site invisible to the public while you work on it. This guide will show you how to integrate this feature with your system, clear server cache when site visibility settings change, and sync coming soon mode with other plugins. ## Prerequisites - Familiarity with PHP and WordPress development. ## Step-by-step instructions ### Clear server cache on site visibility settings change When the site's visibility settings change, it may be necessary to clear a server cache to apply the changes and re-cache customer-facing pages. The [`update_option`](https://developer.wordpress.org/reference/hooks/update_option/) hook can be used to achieve this. ```php add_action( 'update_option_woocommerce_coming_soon', 'clear_server_cache', 10, 3 ); add_action( 'update_option_woocommerce_store_pages_only', 'clear_server_cache', 10, 3 ); function clear_server_cache( $old_value, $new_value, $option ) { // Implement your logic to clear the server cache. if ( function_exists( 'your_cache_clear_function' ) ) { your_cache_clear_function(); } } ``` ### Clear server cache on template changes By default, Coming-soon pages are set with `Cache-Control: max-age=60` header. This setting enables CDNs and other caching mechanisms to cache the page for 60 seconds, balancing the need for efficient performance with reasonable update times. When the user changes the coming soon template, it's recommended that any cache be purged so the changes take effect immediately when the client-side cache expires. You can use the `save_post_wp_template`, `save_post_wp_template_part`, and `save_post_wp_global_styles` hooks to detect when a template is updated and trigger the cache purge. ```php add_action( 'save_post_wp_template', 'purge_cache_on_template_change', 10, 3 ); add_action( 'save_post_wp_template_part', 'purge_cache_on_template_change', 10, 3 ); add_action( 'save_post_wp_global_styles', 'purge_cache_on_template_change', 10, 3 ); function purge_cache_on_template_change( $post_id, $post, $update ) { // Check if the template is associated with the coming soon mode. if ( 'coming-soon' === $post->post_name ) { // Implement your logic to clear the server cache. if ( function_exists( 'your_cache_clear_function' ) ) { your_cache_clear_function(); } } } ``` ### Syncing coming soon mode with other plugins The coming soon mode can be programmatically synced from a plugin or application. Here are some example use cases: - Integrating with a maintenance mode plugin. - Integrating with a hosting provider's coming soon mode. #### Trigger from WooCommerce You can use the following example to run a code such as setting your plugin's status when coming soon mode option is updated: ```php add_action( 'update_option_woocommerce_coming_soon', 'sync_coming_soon_to_other_plugins', 10, 3 ); function sync_coming_soon_to_other_plugins( $old_value, $new_value, $option ) { $is_enabled = $new_value === 'yes'; // Implement your logic to sync coming soon status. if ( function_exists( 'your_plugin_set_coming_soon' ) ) { your_plugin_set_coming_soon( $is_enabled ); } } ``` #### Trigger from other plugins You can use the following example to enable or disable WooCommerce coming soon mode from another plugin by directly updating `woocommerce_coming_soon` option: ```php function sync_coming_soon_from_other_plugins( $is_enabled ) { // Check user capability. if ( ! current_user_can( 'manage_options' ) ) { wp_die( 'You do not have sufficient permissions to access this page.' ); } // Set coming soon mode. if ( isset( $is_enabled ) ) { update_option( 'woocommerce_coming_soon', $is_enabled ? 'yes' : 'no' ); } } ``` #### 2-way sync with plugins If 2-way sync is needed, use the following example where `update_option` will not recursively call `sync_coming_soon_from_other_plugins`: ```php add_action( 'update_option_woocommerce_coming_soon', 'sync_coming_soon_to_other_plugins', 10, 3 ); function sync_coming_soon_to_other_plugins( $old_value, $new_value, $option ) { $is_enabled = $new_value === 'yes'; // Implement your logic to sync coming soon status. if ( function_exists( 'your_plugin_set_coming_soon' ) ) { your_plugin_set_coming_soon( $is_enabled ); } } function sync_coming_soon_from_other_plugins( $is_enabled ) { // Check user capability. if ( ! current_user_can( 'manage_options' ) ) { wp_die( 'You do not have sufficient permissions to access this page.' ); } if ( isset( $is_enabled ) ) { // Temporarily remove the action to prevent a recursive call. remove_action( 'update_option_woocommerce_coming_soon', 'sync_coming_soon_to_other_plugins', 10, 3 ); // Set coming soon mode. update_option( 'woocommerce_coming_soon', $is_enabled ? 'yes' : 'no' ); // Re-add the action. add_action( 'update_option_woocommerce_coming_soon', 'sync_coming_soon_to_other_plugins', 10, 3 ); } } ``` #### One-way binding with option override We could also programmatically bind the coming soon option from another plugin by overriding the `woocommerce_coming_soon` option. This is advantageous since it simplifies state management and prevents possible out-of-sync issues. In the following example, we're binding the coming soon option from another plugin by overriding the `woocommerce_coming_soon` option. ```php add_filter( 'pre_option_woocommerce_coming_soon', 'override_option_woocommerce_coming_soon' ); function override_option_woocommerce_coming_soon( $current_value ) { // Implement your logic to sync coming soon status. if ( function_exists( 'your_plugin_is_coming_soon' ) ) { return your_plugin_is_coming_soon() ? 'yes' : 'no'; } return $current_value; } add_filter( 'pre_update_option_woocommerce_coming_soon', 'override_update_woocommerce_coming_soon', 10, 2 ); function override_update_woocommerce_coming_soon( $new_value, $old_value ) { // Check user capability. if ( ! current_user_can( 'manage_options' ) ) { wp_die( 'You do not have sufficient permissions to access this page.' ); } // Implement your logic to sync coming soon status. if ( function_exists( 'your_plugin_set_coming_soon' ) ) { your_plugin_set_coming_soon( $new_value === 'yes' ); } } ``` ### Custom exclusions filter It is possible for developers to add custom exclusions that bypass the coming soon protection. This is useful for exclusions like always bypassing the screen on a specific IP address, or making a specific landing page available. #### Disabling coming soon in all pages If there is another feature that behaves similarly to WooCommerce's coming soon mode, it can cause unintended conflicts. The coming soon mode can be disabled by excluding all customer-facing pages. The following is an example: ```php add_filter( 'woocommerce_coming_soon_exclude', function() { return true; }, 10 ); ``` #### Disabling coming soon except for a specific page Use the following example to exclude a certain page based on the page's ID. Replace `` with your page identifier: ```php add_filter( 'woocommerce_coming_soon_exclude', function( $is_excluded ) { if ( get_the_ID() === ) { return true; } return $is_excluded; }, 10 ); ``` #### Custom share links The following example shows how to integrate with a custom share code. We recommend using cookies or other storage to persist the access when users navigate across the site: ```php add_filter( 'woocommerce_coming_soon_exclude', function( $exclude ) { // Implement your logic to get and validate share code. if ( function_exists( 'your_plugin_get_share_code' ) && function_exists( 'your_plugin_is_valid_share_code' ) ) { $share_code = your_plugin_get_share_code(); if ( your_plugin_is_valid_share_code( $share_code ) ) { return true; } } return $exclude; } ); ``` ### Extend "Apply to store pages only" setting When using the `Apply to store pages only` setting, you may want to add a custom page to the list of store pages which will be restricted by coming soon mode. You can use the following example to add a custom page: ```php add_filter( 'woocommerce_store_pages', function( $pages ) { $page = get_page_by_path( 'your-page-slug' ); if ( $page ) { $pages[] = $page->ID; } return $pages; } ); ``` --- ## Get started building extensions *Source: extensions/getting-started-extensions/README.md* # Get started building extensions This section provides guides and resources for building, testing, and distributing WooCommerce extensions. ## Getting started - [Design a simple extension](/extensions/getting-started-extensions/how-to-design-a-simple-extension) - Learn extension architecture and best practices - [Build your first extension](/extensions/getting-started-extensions/building-your-first-extension) - Create your first WooCommerce extension - [Core concepts](/extensions/core-concepts/) - Master fundamental concepts like plugin headers, lifecycle management, and security ## Submit to the WooCommerce Marketplace Join the WooCommerce Marketplace and get your extension in front of 3.6M+ active stores worldwide. Learn more about [why extension developers are choosing the WooCommerce Marketplace](https://woocommerce.com/partners/) and [submit your extension](https://woocommerce.com/document/submitting-your-product-to-the-woo-marketplace/) ### Quality Insights Toolkit (QIT) #### Available to all developers with a WooCommerce.com vendor profile QIT (Quality Insights Toolkit) is a testing platform developed by WooCommerce for plugins and themes. It allows developers to quickly run a variety of managed tests out-of-the-box, as well as integrate their own custom E2E tests to ensure their extensions are reliable, secure, and compatible. #### Key features - **Managed test suites:** Run pre-configured end-to-end tests, activation tests, security scans, PHPStan analysis, API tests, and more - **Custom E2E testing:** Write and run your own Playwright-based E2E tests directly through QIT - **Continuous quality checks:** Seamlessly integrate QIT into your development workflows via CLI, GitHub Actions, and more - **Marketplace integration:** Currently in closed beta for extensions listed on the WooCommerce Marketplace [Learn more about QIT](https://qit.woo.com/docs/) ## Development tools - [Extension scaffolds](/getting-started/scaffolding/#extension-scaffolds) - Learn how to scaffold new extensions with our [create-woo-extension](https://www.npmjs.com/package/@woocommerce/create-woo-extension) package. - [WooCommerce CLI](/wc-cli/cli-overview) - Command-line tools for WooCommerce development --- ## How to build your first extension *Source: extensions/getting-started-extensions/building-your-first-extension.md* # How to build your first extension This guide will teach you how to use [create-woo-extension](https://www.npmjs.com/package/@woocommerce/create-woo-extension) to scaffold a WooCommerce extension. There are various benefits to using create-woo-extension over manually creating one from scratch, including: There is less boilerplate code to write, and fewer dependencies to manually setup. Modern features such as Blocks are automatically supported, and unit testing, linting, and Prettier IDE configuration are ready to use. Once your extension is set up, we will show you how to instantly spin up a development environment using [wp-env](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-env/). ## Requirements Before getting started, you will need the following tools installed on your device: - [Node.js](https://nodejs.org/en/learn/getting-started/how-to-install-nodejs) with NPM - [Docker](https://docs.docker.com/engine/install/) (must be running to use wp-env) - [Composer](https://getcomposer.org/doc/00-intro.md) This guide also presumes that you are familiar with working with the command line. ## Bootstrapping Your Extension Open your terminal and run ```sh npx @wordpress/create-block -t @woocommerce/create-woo-extension my-extension-name ``` If you would like to set a custom extension name, you can replace `my-extension-name` with any slug. Please note that your slug must not have any spaces. If you see a prompt similar to `Need to install the following packages`: `@wordpress/create-block@4.34.0. Ok to proceed?`, press `Y`. Once the package finishes generating your extension, navigate into the extension folder using ```sh cd my-extension-name ``` You should then install the extension dependencies using `npm install` and build it using `npm run build`. Congratulations! You just spun up a WooCommerce extension! Your extension will have the following file structure: - `my-extension-name` - `block.json` - contains metadata used to register your custom blocks with WordPress. Learn more. - `build` - the built version of your extension which is generated using npm run build. You shouldn't directly modify any of the files in this folder. - `composer.json` - contains a list of your PHP dependencies which is referenced by Composer. - `composer.lock` - this file allows you to control when and how to update these dependencies - `includes` - The primary purpose of an "includes" folder in PHP development is to store reusable code or files that need to be included or required in multiple parts of a project. This is a PHP developer convention. - `languages` - contains POT files that are used to localize your plugin. - `my-extension-name.php` - your plugin entry point that registers your plugin with WordPress. - `node-modules` - help you form the building blocks of your application and write more structured code - `package.json` - is considered the heart of a Node project. It records metadata, and installs functional dependencies, runs scripts, and defines the entry point of your application. - `README.md` - An introduction and instructional overview of your application. Any special instructions, updates from the author, and details about the application can be written in text here. - `src` - keeps the root directory clean and provides a clear separation between the source code and other assets - `tests` - can hold unit tests for your application, keeps them separate from source files - `vendor` - holds project dependencies, and 3rd party code that you did not write - `webpack.config.js` - webpack is a module bundler. Its main purpose is to bundle JavaScript files for usage in a browser ## Setting Up a Developer Environment We recommend using `wp-env` to spin up local development environments. You can [learn more about wp-env here](https://make.wordpress.org/core/2020/03/03/wp-env-simple-local-environments-for-wordpress/). If you do not already have wp-env installed locally, you can install it using `npm -g i @wordpress/env`. Once you have installed `wp-env`, and while still inside your `my-extension-name` folder, run `wp-env` start. After a few seconds, a test WordPress site with your WooCommerce and your extension installed will be running on `localhost:8888`. If you did not set a custom name for your extension, you can visit `wp-admin/admin.php?page=wc-admin&path=%2Fmy-extension-name` to see the settings page generated by /src/index.js. The default username/password combination for `wp-env` is `admin` / `password`. ## Next Steps Now that you’ve bootstrapped your extension, you can finally add some features! Here are some simple ones you could include: [How to add a custom field to simple and variable products](https://developer.woocommerce.com/docs/how-to-add-a-custom-field-to-simple-and-variable-products/) ## Reference [Visit @woocommerce/create-woo-extension on NPM for package reference](https://www.npmjs.com/package/@woocommerce/create-woo-extension) [Check out wp-env's command reference to learn more about advanced functionality](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-env/#command-reference) --- ## How to design a simple extension *Source: extensions/getting-started-extensions/how-to-design-a-simple-extension.md* # How to design a simple extension ## Introduction Building a WooCommerce extension that provides a first-class experience for merchants and shoppers requires a hybrid development approach combining PHP and modern JavaScript. The PHP handles the lifecycle and server-side operations of your extension, while the modern JavaScript lets you shape the appearance and behavior of its user interface. ## The main plugin file Your extension's main PHP file is a bootstrapping file. It contains important metadata about your extension that WordPress and WooCommerce use for a number of ecosystem integration processes, and it serves as the primary entry point for your extension's functionality. While there is not a particular rule enforced around naming this file, using a hyphenated version of the plugin name is a common best practice. (i.e. my-extension.php) ## Declaring extension metadata Your extension's main plugin file should have a header comment that includes a number of important pieces of metadata about your extension. WordPress has a list of header requirements to which all plugins must adhere, but there are additional considerations for WooCommerce extensions: - The `Author` and `Developer` fields are required and should be set to either your name or your company name. - The `Developer URI` field should be your official webpage URL. - The `Plugin URI` field should contain the URL of the extension's product page in the WooCommerce Marketplace or the extension's official landing page on your website. - For extensions listed in the WooCommerce Marketplace, to help facilitate the update process, add a `Woo` field and an appropriate value. WooCommerce Marketplace vendors can find this snippet by logging in to the Vendors Dashboard and navigating to `Extensions > All Extensions`. Then, select the product and click Edit product page. This snippet will be in the upper-right-hand corner of the screen. Below is an example of what the header content might look like for an extension listed in the WooCommerce Marketplace. ```php /** * Plugin Name: My Great WooCommerce Extension * Plugin URI: https://woocommerce.com/products/woocommerce-extension/ * Description: Your extension's description text. * Version: 1.0.0 * Author: Your Name * Author URI: http://yourdomain.com/ * Developer: Your Name * Developer URI: http://yourdomain.com/ * Text Domain: my-extension * Domain Path: /languages * * Woo: 12345:342928dfsfhsf8429842374wdf4234sfd * * License: GNU General Public License v3.0 * License URI: http://www.gnu.org/licenses/gpl-3.0.html */ ``` ## Preventing data leaks As a best practice, your extension's PHP files should contain a conditional statement at the top that checks for WordPress' ABSPATH constant. If this constant is not defined, the script should exit. `defined( 'ABSPATH' ) || exit;` This check prevents your PHP files from being executed via direct browser access and instead only allows them to be executed from within the WordPress application environment. ## Managing extension lifecycle Because your main PHP file is the primary point of coupling between your extension and WordPress, you should use it as a hub for managing your extension's lifecycle. At a very basic level, this means handling: - Activation - Execution - Deactivation Starting with these three broad lifecycle areas, you can begin to break your extension's functionality down further to help maintain a good separation of concerns. ## Handling activation and deactivation A common pattern in WooCommerce extensions is to create dedicated functions in your main PHP file to serve as activation and deactivation hooks. You then register these hooks with WordPress using the applicable registration function. This tells WordPress to call the function when the plugin is activated or deactivated. Consider the following examples: ```php function my_extension_activate() { // Your activation logic goes here. } register_activation_hook( __FILE__, 'my_extension_activate' ); ``` ```php function my_extension_deactivate() { // Your deactivation logic goes here. } register_deactivation_hook( __FILE__, 'my_extension_deactivate' ); ``` ## Maintaining a separation of concerns There are numerous ways to organize the code in your extension. You can find a good overview of best practices in the WordPress Plugin Developer Handbook. Regardless of the approach you use for organizing your code, the nature of WordPress' shared application space makes it imperative that you build with an eye toward interoperability. There are a few common principles that will help you optimize your extension and ensure it is a good neighbor to others: - Use namespacing and prefixing to avoid conflicts with other extensions. - Use classes to encapsulate your extension's functionality. - Check for existing declarations, assignments, and implementations. ## The core extension class As mentioned above, encapsulating different parts of your extension's functionality using classes is an important measure that not only helps with interoperability, but which also makes your code easier to maintain and debug. Your extension may have many different classes, each shouldering some piece of functionality. At a minimum, your extension should define a central class which can handle the setup, initialization and management of a single instance of itself. ## Implementing a singleton pattern Unless you have a specific reason to create multiple instances of your main class when your extension runs, you should ensure that only one instance exists in the global scope at any time. A common way of doing this is to use a Singleton pattern. There are several ways to go about setting up a singleton in a PHP class. Below is a basic example of a singleton that also implements some of the best practices mentioned above about namespacing and pre-declaration checks: ```php if ( ! class_exists( 'My_Extension' ) ) : /** * My Extension core class */ class My_Extension { /** * The single instance of the class. */ protected static $_instance = null; /** * Constructor. */ protected function __construct() { // Instantiation logic will go here. } /** * Main Extension Instance. * Ensures only one instance of the extension is loaded or can be loaded. */ public static function instance() { if ( is_null( self::$_instance ) ) { self::$_instance = new self(); } return self::$_instance; } /** * Cloning is forbidden. */ public function __clone() { // Override this PHP function to prevent unwanted copies of your instance. // Implement your own error or use `wc_doing_it_wrong()` } /** * Unserializing instances of this class is forbidden. */ public function __wakeup() { // Override this PHP function to prevent unwanted copies of your instance. // Implement your own error or use `wc_doing_it_wrong()` } } endif; ``` Notice that the example class above is designed to be instantiated by calling the static class method `instance()`, which will either return an existing instance of the class or create one and return it. In order to fully protect against unwanted instantiation, it's also necessary to override the built-in magic methods `__clone()` and `__wakeup()`. You can implement your own error logging here or use something like `_doing_it_wrong()` which handles error logging for you. You can also use WooCommerce's wrapper function `wc_doing_it_wrong()` here. Just be sure your code checks that the function exists first. ## Constructor The example above includes an empty constructor for demonstration. In a real-world WooCommerce extension, however, this constructor should handle a few important tasks: - Check for an active installation of WooCommerce & other sibling dependencies. - Call a setup method that loads other files that your class depends on. - Call an initialization method that gets your class and its dependencies ready to go. If we build upon our example above, it might look something like this: ```php protected function __construct() { $this->includes(); $this->init(); // You might also include post-setup steps such as showing activation notices here. } ``` ## Loading dependencies The includes() function above is where you'll load other class dependencies, typically via an include or require constructs. A common way of managing and loading external dependencies is to use Composer's autoload feature, but you can also load specific files individually. You can read more about how to autoload external dependencies in the Composer documentation. A basic example of a setup method that uses both Composer and internal inclusion is below. ```php public function includes() { $loader = include_once dirname( __FILE__ ) . '/' . 'vendor/autoload.php'; if ( ! $loader ) { throw new Exception( 'vendor/autoload.php missing please run `composer install`' ); } require_once dirname( __FILE__ ) . '/' . 'includes/my-extension-functions.php'; } ``` ## Initialization The `init()` function above is where you should handle any setup for the classes you loaded in the includes() method. This step is where you'll often perform any initial registration with relevant actions or filters. It's also where you can register and enqueue your extension's JavaScripts and stylesheets. Here's an example of what your initialization method might look like: ```php private function init() { // Set up cache management. new My_Extension_Cache(); // Initialize REST API. new My_Extension_REST_API(); // Set up email management. new My_Extension_Email_Manager(); // Register with some-action hook add_action( 'some-action', 'my-extension-function' ); } ``` There are many different ways that your core class' initialization method might look, depending on the way that you choose to architect your extension. The important concept here is that this function serves as a central point for handling any initial registration and setup that your extension requires in order to respond to web requests going forward. ## Delaying initialization The WordPress activation hook we set up above with register_activation_hook() may seem like a great place to instantiate our extension's main class, and in some cases it will work. By virtue of being a plugin for a plugin, however, WooCommerce extensions typically require WooCommerce to be loaded in order to function properly, so it's often best to delay instantiation and initialization until after WordPress has loaded other plugins. To do that, instead of hooking your instantiation to your extension's activation hook, use the plugins_loaded action in WordPress to instantiate your extension's core class and add its singleton to the $GLOBALS array. ```php function my_extension_initialize() { // This is also a great place to check for the existence of the WooCommerce class if ( ! class_exists( 'WooCommerce' ) ) { // You can handle this situation in a variety of ways, // but adding a WordPress admin notice is often a good tactic. return; } $GLOBALS['my_extension'] = My_Extension::instance(); } add_action( 'plugins_loaded', 'my_extension_initialize', 10 ); ``` In the example above, WordPress will wait until after all plugins have been loaded before trying to instantiate your core class. The third argument in add_action() represents the priority of the function, which ultimately determines the order of execution for functions that hook into the plugins_loaded action. Using a value of 10 here ensures that other WooCommerce-related functionality will run before our extension is instantiated. ## Handling execution Once your extension is active and initialized, the possibilities are wide open. This is where the proverbial magic happens in an extension, and it's largely up to you to define. While implementing specific functionality is outside the scope of this guide, there are some best practices to keep in mind as you think about how to build out your extension's functionality. - Keep an event-driven mindset. Merchants and shoppers who use your extension will be interacting with WooCommerce using web requests, so it can be helpful to anchor your extension to some of the critical flows that users follow in WooCommerce. - Keep business logic and presentation logic separate. This could be as simple as maintaining separate classes for handling back-end processing and front-end rendering. - Where possible, break functionality into smaller parts and delegate responsibility to dedicated classes instead of building bloated classes and lengthy functions. You can find detailed documentation of classes and hooks in the WooCommerce Core Code Reference and additional documentation of the REST API endpoints in the WooCommerce REST API Documentation. ## Handling deactivation The WordPress deactivation hook we set up earlier in our main PHP file with register_deactivation_hook() is a great place to aggregate functionality for any cleanup that you need to handle when a merchant deactivates your extension. In addition to any WordPress-related deactivation tasks your extension needs to do, you should also account for WooCommerce-related cleanup, including: - Removing Scheduled Actions - Removing Notes in the Admin Inbox - Removing Admin Tasks ## Uninstallation While it's certainly possible to completely reverse everything your extension has created when a merchant deactivates it, it's not advisable nor practical in most cases. Instead, it's best to reserve that behavior for uninstallation. For handling uninstallation, it's best to follow the guidelines in the WordPress Plugin Handbook. ## Putting it all together Below is an example of what a main plugin file might look like for a very simple extension: ```php /** * Plugin Name: My Great WooCommerce Extension * Plugin URI: https://woocommerce.com/products/woocommerce-extension/ * Description: Your extension's description text. * Version: 1.0.0 * Author: Your Name * Author URI: http://yourdomain.com/ * Developer: Your Name * Developer URI: http://yourdomain.com/ * Text Domain: my-extension * Domain Path: /languages * * Woo: 12345:342928dfsfhsf8429842374wdf4234sfd * * License: GNU General Public License v3.0 * License URI: http://www.gnu.org/licenses/gpl-3.0.html */ defined( 'ABSPATH' ) || exit; /** * Activation and deactivation hooks for WordPress */ function myPrefix_extension_activate() { // Your activation logic goes here. } register_activation_hook( __FILE__, 'myPrefix_extension_activate' ); function myPrefix_extension_deactivate() { // Your deactivation logic goes here. // Don't forget to: // Remove Scheduled Actions // Remove Notes in the Admin Inbox // Remove Admin Tasks } register_deactivation_hook( __FILE__, 'myPrefix_extension_deactivate' ); if ( ! class_exists( 'My_Extension' ) ) : /** * My Extension core class */ class My_Extension { /** * The single instance of the class. */ protected static $_instance = null; /** * Constructor. */ protected function __construct() { $this->includes(); $this->init(); } /** * Main Extension Instance. */ public static function instance() { if ( is_null( self::$_instance ) ) { self::$_instance = new self(); } return self::$_instance; } /** * Cloning is forbidden. */ public function __clone() { // Override this PHP function to prevent unwanted copies of your instance. // Implement your own error or use `wc_doing_it_wrong()` } /** * Unserializing instances of this class is forbidden. */ public function __wakeup() { // Override this PHP function to prevent unwanted copies of your instance. // Implement your own error or use `wc_doing_it_wrong()` } /** * Function for loading dependencies. */ private function includes() { $loader = include_once dirname( __FILE__ ) . '/' . 'vendor/autoload.php'; if ( ! $loader ) { throw new Exception( 'vendor/autoload.php missing please run `composer install`' ); } require_once dirname( __FILE__ ) . '/' . 'includes/my-extension-functions.php'; } /** * Function for getting everything set up and ready to run. */ private function init() { // Examples include: // Set up cache management. // new My_Extension_Cache(); // Initialize REST API. // new My_Extension_REST_API(); // Set up email management. // new My_Extension_Email_Manager(); // Register with some-action hook // add_action('some-action', 'my-extension-function'); } } endif; /** * Function for delaying initialization of the extension until after WooCommerce is loaded. */ function my_extension_initialize() { // This is also a great place to check for the existence of the WooCommerce class if ( ! class_exists( 'WooCommerce' ) ) { // You can handle this situation in a variety of ways, // but adding a WordPress admin notice is often a good tactic. return; } $GLOBALS['my_extension'] = My_Extension::instance(); } ``` --- ## How to add a section to a settings tab *Source: extensions/settings-and-config/adding-a-section-to-a-settings-tab.md* # How to add a section to a settings tab When you're building an extension for WooCommerce that requires settings of some kind, it's important to ask yourself: **Where do they belong?** If your extension just has a couple of simple settings, do you really need to create a new tab specifically for it? Most likely the answer is no. ## When to Create a Section Let's say we had an extension that adds a slider to a single product page. This extension only has a few options: - Auto-insert into single product page (checkbox) - Slider Title (text field) That's only two options, specifically related to **Products**. We could quite easily just append them onto the core WooCommerce Products Settings (**WooCommerce > Settings > Products**), but that wouldn't be very user friendly. Users wouldn't know where to look initially so they'd have to scan all of the Products options and it would be difficult / impossible to link the options directly. Fortunately, as of WooCommerce 2.2.2, there is a new filter in place that allows you add a new **section**, beneath one of the core settings' tabs. ## How to Create a Section We'll go over doing this through individual functions, but you should probably create a Class that stores all of your settings methods. The first thing you need to is add the section, which can be done like this by hooking into the `woocommerce_get_sections_products` filter: ```php /** * Create the section beneath the products tab **/ add_filter( 'woocommerce_get_sections_products', 'wcslider_add_section' ); function wcslider_add_section( $sections ) { $sections['wcslider'] = __( 'WC Slider', 'text-domain' ); return $sections; } ``` _[wc-create-section-beneath-products.php](https://gist.github.com/woogists/2964ec01c8bea50fcce62adf2f5c1232/raw/da5348343cf3664c0bc8b6b132d8105bfcf9ca51/wc-create-section-beneath-products.php)_ Make sure you change the **wcslider** parts to suit your extension's name / text-domain. The important thing about the `woocommerce_get_sections_products` filter, is that the last part **products**, is the tab you'd like to add a section to. So if you want to add a new tab to accounts section, you would hook into the `woocommerce_get_sections_accounts` filter. ## How to Add Settings to a Section Now that you've got the tab, you need to filter the output of `woocommerce_get_sections_products` (or similar). You would add the settings like usual using the [**WooCommerce Settings API**](./settings-api.md), but check for the current section before adding the settings to the tab's settings array. For example, let's add the sample settings we discussed above to the new **wcslider** section we just created: ```php /** * Add settings to the specific section we created before */ add_filter( 'woocommerce_get_settings_products', 'wcslider_all_settings', 10, 2 ); function wcslider_all_settings( $settings, $current_section ) { /** * Check the current section is what we want **/ if ( $current_section == 'wcslider' ) { $settings_slider = array(); // Add Title to the Settings $settings_slider[] = array( 'name' => __( 'WC Slider Settings', 'text-domain' ), 'type' => 'title', 'desc' => __( 'The following options are used to configure WC Slider', 'text-domain' ), 'id' => 'wcslider' ); // Add first checkbox option $settings_slider[] = array( 'name' => __( 'Auto-insert into single product page', 'text-domain' ), 'desc_tip' => __( 'This will automatically insert your slider into the single product page', 'text-domain' ), 'id' => 'wcslider_auto_insert', 'type' => 'checkbox', 'css' => 'min-width:300px;', 'desc' => __( 'Enable Auto-Insert', 'text-domain' ), ); // Add second text field option $settings_slider[] = array( 'name' => __( 'Slider Title', 'text-domain' ), 'desc_tip' => __( 'This will add a title to your slider', 'text-domain' ), 'id' => 'wcslider_title', 'type' => 'text', 'desc' => __( 'Any title you want can be added to your slider with this option!', 'text-domain' ), ); $settings_slider[] = array( 'type' => 'sectionend', 'id' => 'wcslider' ); return $settings_slider; /** * If not, return the standard settings **/ } else { return $settings; } } ``` _[wc-add-settings-section.php](https://gist.github.com/woogists/4038b83900508806c57a193a2534b845#file-wc-add-settings-section-php)_ We're hooking into the same `woocommerce_get_sections_products` filter, but this time doing a check that the `$current_section` matches our earlier defined custom section (wcslider), before adding in our new settings. ## Using the New Settings You would now just use your newly created settings like you would any other WordPress / WooCommerce setting, through the [**get_option**](http://codex.wordpress.org/Function_Reference/get_option) function and the defined ID of the setting. For example, to use the previously created **wcslider_auto_insert** option, simply use the following code: `get_option( 'wcslider_auto_insert' )` ## Conclusion When creating an extension for WooCommerce, think about where your settings belong before you create them. The key to building a useful product is making it easy to use for the end user, so appropriate setting placement is crucially important. For more specific information on adding settings to WooCommerce, check out the [**Settings API documentation**](https://github.com/woocommerce/woocommerce/blob/trunk/docs/extension-development/settings-api.md). --- ## How to add store management links *Source: extensions/settings-and-config/how-to-add-your-own-store-management-links.md* # How to add store management links ## Introduction In the new and improved WooCommerce home screen, there are two points of extensibility for plugin developers that have recently had some attention. The first is the setup task list, allowing you to remind the user of tasks they need to complete and keeping track of their progress for them. The second is the store management links section. Once the user has completed the setup tasks this will display for them. This section consolidates a list of handy navigation links that merchants can use to quickly find features in WooCommerce. Discoverability can be hard for users so this can be a great place to bring attention to the features of your plugin and allow users to easily find their way to the key functionality your plugin provides. Adding your own store management links is a simple process. ## Add your own store management link Before we start, let's outline a couple of restrictions on this feature. Right now these links are designed to keep the user within WooCommerce, so it does not support external links. All the links you add will fall under a special category in the list called "Extensions". There is not currently any support for custom categories. With those things in mind, let's start. ## Step 1 - Enqueue JavaScript Adding a store management link will all be done in JavaScript, so the first step is enqueuing your script that will add the store management link. The most important thing here is ensuring that your script runs before the store management link section is rendered. To ensure that your script runs before ours you'll need to enqueue it with a priority higher than 15. You'll also need to depend on `wp-hooks` to get access to `addFilter`. Example: ```php function enqueue_management_link_script() { wp_enqueue_script( $script_name, $script_url, array( 'wp-hooks' ), 10 ); } add_action( 'admin_enqueue_scripts', 'enqueue_management_link_script' ); ``` ## Step 2 - Install @wordpress/icons To provide an icon of your choice for your store management link, you'll need to install `@wordpress/icons` in your JavaScript project: ```sh npm install @wordpress/icons --save ``` ## Step 3 - Add your filter Your script will need to use `addFilter` to provide your custom link to the store management link section. And you'll need to import your icon of choice from `@wordpress/icons`. Here's an example: ```js import { megaphone } from "@wordpress/icons"; import { addFilter } from "@wordpress/hooks"; addFilter( "woocommerce_admin_homescreen_quicklinks", "my-extension", (quickLinks) => { return [ ...quickLinks, { title: "My link", href: "link/to/something", icon: megaphone, }, ]; } ); ``` Here's a screen shot using our new custom store management link: ![screen shot of custom store management link in wp-admin](https://developer.woocommerce.com/wp-content/uploads/2023/12/yvXeSya.png) --- ## Creating custom settings for WooCommerce extensions *Source: extensions/settings-and-config/implementing-settings.md* # Creating custom settings for WooCommerce extensions If you're customizing WooCommerce or adding your own functionality to it you'll probably need a settings page of some sort. One of the easiest ways to create a settings page is by taking advantage of the [`WC_Integration` class](https://woocommerce.github.io/code-reference/classes/WC-Integration.html 'WC_Integration Class'). Using the Integration class will automatically create a new settings page under **WooCommerce > Settings > Integrations** and it will automatically save, and sanitize your data for you. We've created this tutorial so you can see how to create a new integration. ## Setting up the Integration You'll need at least two files to create an integration so you'll need to create a directory. ### Creating the Main Plugin File Create your main plugin file to [hook](https://developer.wordpress.org/reference/functions/add_action/ 'WordPress add_action()') into the `plugins_loaded` hook and check if the `WC_Integration` [class exists](https://www.php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends 'PHP Class Exists'). If it doesn't then the user most likely doesn't have WooCommerce activated. After you do that you need to register the integration. Load the integration file (we'll get to this file in a minute). Use the `woocommerce_integrations` filter to add a new integration to the [array](http://php.net/manual/en/language.types.array.php 'PHP Array'). ### Creating the Integration Class Now that we have the framework setup let's actually implement this Integration class. There already is a `WC_Integration` class so we want to make a [child class](https://www.php.net/manual/en/language.oop5.inheritance.php). This way it inherits all of the existing methods and data. You'll need to set an id, a description, and a title for your integration. These will show up on the integration page. You'll also need to load the settings by calling: `$this->init_form_fields();` & `$this->init_settings();` You'll also need to save your options by calling the `woocommerce_update_options_integration_{your method id}` hook. Lastly you have to input some settings to save! We've included two dummy fields below but we'll go more into fields in the next section. > Added to a file named `class-wc-integration-demo-integration.php` ```php id = 'integration-demo'; $this->method_title = __( 'Integration Demo', 'woocommerce-integration-demo' ); $this->method_description = __( 'An integration demo to show you how easy it is to extend WooCommerce.', 'woocommerce-integration-demo' ); // Load the settings. $this->init_form_fields(); $this->init_settings(); // Define user set variables. $this->api_key = $this->get_option( 'api_key' ); $this->debug = $this->get_option( 'debug' ); // Actions. add_action( 'woocommerce_update_options_integration_' . $this->id, array( $this, 'process_admin_options' ) ); } /** * Initialize integration settings form fields. */ public function init_form_fields() { $this->form_fields = array( 'api_key' => array( 'title' => __( 'API Key', 'woocommerce-integration-demo' ), 'type' => 'text', 'description' => __( 'Enter with your API Key. You can find this in "User Profile" drop-down (top right corner) > API Keys.', 'woocommerce-integration-demo' ), 'desc_tip' => true, 'default' => '', ), 'debug' => array( 'title' => __( 'Debug Log', 'woocommerce-integration-demo' ), 'type' => 'checkbox', 'label' => __( 'Enable logging', 'woocommerce-integration-demo' ), 'default' => 'no', 'description' => __( 'Log events such as API requests', 'woocommerce-integration-demo' ), ), ); } } endif; ``` > Added to a file named `wc-integration-demo.php` ```php . * */ if ( ! class_exists( 'WC_Integration_Demo' ) ) : /** * Integration demo class. */ class WC_Integration_Demo { /** * Construct the plugin. */ public function __construct() { add_action( 'plugins_loaded', array( $this, 'init' ) ); } /** * Initialize the plugin. */ public function init() { // Checks if WooCommerce is installed. if ( class_exists( 'WC_Integration' ) ) { // Include our integration class. include_once 'class-wc-integration-demo-integration.php'; // Register the integration. add_filter( 'woocommerce_integrations', array( $this, 'add_integration' ) ); } else { // throw an admin error if you like } } /** * Add a new integration to WooCommerce. * * @param array Array of integrations. */ public function add_integration( $integrations ) { $integrations[] = 'WC_Integration_Demo_Integration'; return $integrations; } } endif; $WC_Integration_Demo = new WC_Integration_Demo( __FILE__ ); ``` ## Creating Settings If you took a look through the last section you'll see that we added two dummy settings using the `init_form_fields()` method. ### Types of Settings WooCommerce includes support for 8 types of settings. - text - price - decimal - password - textarea - checkbox - select - multiselect And these settings have attributes which you can use. These affect the way the setting looks and behaves on the settings page. It doesn't affect the setting itself. The attributes will manifest slightly differently depending on the setting type. A placeholder for example doesn't work with checkboxes. To see exactly how they work you should look through the [source code](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/includes/abstracts/abstract-wc-settings-api.php 'WC Settings API on GitHub'). Ex. - title - class - css - placeholder - description - default - desc_tip ### Creating Your Own Settings The built-in settings are great but you may need extra controls to create your settings page. That's why we included some methods to do this for you. First, define a setting by adding it to the `$this->form_fields` array, entering the kind of form control you want under `type`. You can override the default HTML for your form inputs by creating a method with a name of the format `generate_{ type }_html` which outputs HTML markup. To specify how buttons are rendered, you'd add a method called `generate_button_html`. For textareas, you'd add a `generate_textarea_html` method, and so on. (Check out the `generate_settings_html` method of the `WC_Settings_API` class in the WooCommerce source code to see how WooCommerce uses this.) The below example creates a button that goes to WooCommerce.com. ```php /** * Initialize integration settings form fields. * * @return void */ public function init_form_fields() { $this->form_fields = array( // don't forget to put your other settings here 'customize_button' => array( 'title' => __( 'Customize!', 'woocommerce-integration-demo' ), 'type' => 'button', 'custom_attributes' => array( 'onclick' => "location.href='https://woocommerce.com'", ), 'description' => __( 'Customize your settings by going to the integration site directly.', 'woocommerce-integration-demo' ), 'desc_tip' => true, ) ); } /** * Generate Button HTML. * * @access public * @param mixed $key * @param mixed $data * @since 1.0.0 * @return string */ public function generate_button_html( $key, $data ) { $field = $this->plugin_id . $this->id . '_' . $key; $defaults = array( 'class' => 'button-secondary', 'css' => '', 'custom_attributes' => array(), 'desc_tip' => false, 'description' => '', 'title' => '', ); $data = wp_parse_args( $data, $defaults ); ob_start(); ?> get_tooltip_html( $data ); ?> get_description_html( $data ); ?> id, array( $this, 'sanitize_settings' ) ); } /** * Sanitize our settings */ public function sanitize_settings( $settings ) { // We're just going to make the api key all upper case characters since that's how our imaginary API works if ( isset( $settings ) && isset( $settings['api_key'] ) ) { $settings['api_key'] = strtoupper( $settings['api_key'] ); } return $settings; } ``` ### Validation Validation isn't always necessary but it's nice to do. If your API keys are always 10 characters long and someone enters one that's not 10 then you can print out an error message and prevent the user a lot of headache when they assumed they put it in correctly. First set up a `validate_{setting key}_field` method for each field you want to validate. For example, with the `api_key` field you need a `validate_api_key_field()` method. ```php public function validate_api_key_field( $key, $value ) { if ( isset( $value ) && 20 < strlen( $value ) ) { WC_Admin_Settings::add_error( esc_html__( 'Looks like you made a mistake with the API Key field. Make sure it isn't longer than 20 characters', 'woocommerce-integration-demo' ) ); } return $value; } ``` ## A complete example If you've been following along you should have a complete integration example. If you have any problems see our [full integration demo](https://github.com/woogists/woocommerce-integration-demo 'Integration Demo'). --- ## Settings API *Source: extensions/settings-and-config/settings-api.md* # Settings API The WooCommerce Settings API is used by extensions to display, save, and load settings. The best way to make use of the API in your extension is to create a class that extends the `WC_Settings_API` class: ```php class My_Extension_Settings extends WC_Settings_API { // } ``` ## Defining form fields You can define your fields using a method called `init_form_fields` in your class constructor: ```php $this->init_form_fields(); ``` You must have your settings defined before you can load them. Setting definitions go in the `form_fields` array: ```php /** * Initialise gateway settings form fields. */ function init_form_fields() { $this->form_fields = array( 'title' => array( 'title' => __( 'Title', 'your-text-domain' ), 'type' => 'text', 'description' => __( 'This controls the title which the user sees during checkout.', 'your-text-domain' ), 'default' => __( 'PayPal', 'your-text-domain' ) ), 'description' => array( 'title' => __( 'Description', 'your-text-domain' ), 'type' => 'textarea', 'description' => __( 'This controls the description which the user sees during checkout.', 'your-text-domain' ), 'default' => __( "Pay via PayPal; you can pay with your credit card if you don't have a PayPal account", 'your-text-domain' ) ) ); } // End init_form_fields() ``` (Make sure your class initializes the `form_fields` property so that the "Creation of dynamic property" error is not thrown in PHP 8.2+) In the above example we define two settings, Title and Description. Title is a text box, whereas Description is a textarea. Notice how you can define a default value and a description for the setting itself. Setting definitions use the following format: ```php 'setting_name' => array( 'title' => 'Title for your setting shown on the settings page', 'description' => 'Description for your setting shown on the settings page', 'type' => 'text|password|textarea|checkbox|select|multiselect', 'default' => 'Default value for the setting', 'class' => 'Class for the input element', 'css' => 'CSS rules added inline on the input element', 'label' => 'Label', // For checkbox inputs only. 'options' => array( // Array of options for select/multiselect inputs only. 'key' => 'value' ), ) ``` ## Displaying your settings Create a method called `admin_options` containing the following: ```php function admin_options() { ?>

generate_settings_html(); ?>
init_settings(); ``` After that you can load your settings from the settings API. The `init_settings` method above populates the settings variable for you: ```php // Define user set variables $this->title = $this->settings['title']; $this->description = $this->settings['description']; ``` --- ## Managing custom attributes in WooCommerce menus and taxonomy archives *Source: extensions/settings-and-config/using-custom-attributes-in-menus.md* # Managing custom attributes in WooCommerce menus and taxonomy archives Attributes that can be used for the layered nav are a custom taxonomy, which means you can display them in menus, or display products by attributes. This requires some work on your part, and archives must be enabled. ## Register the taxonomy for menus When registering taxonomies for your custom attributes, WooCommerce calls the following hook: ```php $show_in_nav_menus = apply_filters('woocommerce_attribute_show_in_nav_menus', false, $name); ``` So, for example, if your attribute slug was `size` you would do the following to register it for menus: ```php add_filter('woocommerce_attribute_show_in_nav_menus', 'wc_reg_for_menus', 1, 2); function wc_reg_for_menus( $register, $name = '' ) { if ( $name == 'pa_size' ) $register = true; return $register; } ``` Custom attribute slugs are prefixed with `pa_`, so an attribute called `size` would be `pa_size` Now use your attribute in **Appearance > Menus**. You will notice, however, that it has default blog styling when you click on a link to your taxonomy term. ## Create a template You need to theme your attribute to make it display products as you want. To do this: 1. Copy `woocommerce/templates/taxonomy-product_cat.php` into your theme folder 2. Rename the template to reflect your attribute - in our example we'd use `taxonomy-pa_size.php` You should now see this template when viewing taxonomy terms for your custom attribute. --- ## Integrating admin pages into WooCommerce extensions *Source: extensions/settings-and-config/working-with-woocommerce-admin-pages.md* # Integrating admin pages into WooCommerce extensions ## Introduction There are a number of ways to manage admin-area pages for your WooCommerce extension. You can use existing PHP pages or create new React-powered pages. Regardless of the approach you choose, you'll need to register your page with the [`PageController`](https://woocommerce.github.io/code-reference/classes/Automattic-WooCommerce-Admin-PageController.html) in order to display the WooCommerce Admin header and activity panel on your page. ## Connecting a PHP-powered page to WooCommerce Admin To register an existing PHP-powered admin page with the [`PageController`](https://woocommerce.github.io/code-reference/classes/Automattic-WooCommerce-Admin-PageController.html), use the [`wc_admin_connect_page()`](https://woocommerce.github.io/code-reference/namespaces/default.html#function_wc_admin_connect_page) function. For example: ```php wc_admin_connect_page( array( 'id' => 'woocommerce-settings', 'screen_id' => 'woocommerce_page_wc-settings-general', 'title' => array( 'Settings', 'General' ), 'path' => add_query_arg( 'page', 'wc-settings', 'admin.php' ), ) ); ``` The [`wc_admin_connect_page()`](https://woocommerce.github.io/code-reference/namespaces/default.html#function_wc_admin_connect_page) function accepts an array of arguments, two of which are optional: - `id` (**required**) - This identifies the page with the controller. - `parent` (_optional_) - This value denotes the page as a child of a parent (using the parent's ID) and is used for generating breadcrumbs. - `screen_id` (**required**) - This corresponds to [`PageController::get_current_screen_id()`](https://woocommerce.github.io/code-reference/classes/Automattic-WooCommerce-Admin-PageController.html#method_get_current_screen_id). It is used to determine the current page. (see note below) - `title` (**required**) - This corresponds to the page's title and is used to build breadcrumbs. You can supply a string or an array of breadcrumb pieces here. - `path` (_optional_) - This is the page's relative path. Used for linking breadcrumb pieces when this page is a parent. In the example above, you can see how to use an array to construct breadcrumbs for your extension. WooCommerce will attach a link leading to the `path` value to the first piece in the title array. All subsequent pieces are rendered as text and not linked. ### A note about determining the screen ID WooCommerce Admin uses its own version of [`get_current_screen()`](https://developer.wordpress.org/reference/functions/get_current_screen/) to allow for more precise identification of admin pages, which may have various tabs and subsections. The format of this ID may vary depending on the structural elements present on the page. Some formats that the function will generate are: - `{$current_screen->action}-{$current_screen->action}-tab-section` - `{$current_screen->action}-{$current_screen->action}-tab` - `{$current_screen->action}-{$current_screen->action}` if no tab is present - `{$current_screen->action}` if no action or tab is present If your extension adds new pages with tabs or subsections, be sure to use the `wc_admin_pages_with_tabs` and `wc_admin_page_tab_sections` filters to have WooCommerce generate accurate screen IDs for them. You can also use the `wc_admin_current_screen_id` filter to make any changes necessary to the screen ID generation behavior. ## Registering a React-powered page To register a React-powered page, use the [`wc_admin_register_page()`](https://woocommerce.github.io/code-reference/namespaces/default.html#function_wc_admin_register_page) function. It accepts an array of arguments: - `id` (**required**) - This identifies the page with the controller. - `parent` (_optional_) - This denotes the page as a child of `parent` (using the parent's ID) and is used for generating breadcrumbs. - `title` (**required**) - This corresponds to the page's title and is used to build breadcrumbs. You can supply a String or an Array of breadcrumb pieces here. - `path` (**required**) - This is the page's path (relative to `#wc-admin`). It is used for identifying this page and for linking breadcrumb pieces when this page is a parent. - `capability` (_optional_) - User capability needed to access this page. The default value is `manage_options`. - `icon` (_optional_) - Use this to apply a Dashicons helper class or base64-encoded SVG. Include the entire dashicon class name, ie `dashicons-*`. Note that this won't be included in WooCommerce Admin Navigation. - `position` (_optional_) - Menu item position for parent pages. See: [`add_menu_page()`](https://developer.wordpress.org/reference/functions/add_menu_page/). Registering a React-powered page is similar to connecting a PHP page, but with some key differences. Registering pages will automatically create WordPress menu items for them, with the appropriate hierarchy based on the value of `parent`. ### Example: Adding a new WooCommerce Admin page ```php if ( ! function_exists( 'YOUR_PREFIX_add_extension_register_page' ) ) { function YOUR_PREFIX_add_extension_register_page() { if ( ! function_exists( 'wc_admin_register_page' ) ) { return; } wc_admin_register_page( array( 'id' => 'my-example-page', 'title' => __( 'My Example Page', 'YOUR-TEXTDOMAIN' ), 'parent' => 'woocommerce', 'path' => '/example', ) ); } } add_action( 'admin_menu', 'YOUR_PREFIX_add_extension_register_page' ); ``` In the example above, we encapsulated our call to [`wc_admin_register_page()`](https://woocommerce.github.io/code-reference/namespaces/default.html#function_wc_admin_register_page) in a function that we have hooked to the [`admin_menu`](https://developer.wordpress.org/reference/hooks/admin_menu/) action. Once you have registered a page with the controller, you can supply a React component on the client side. ```js import { addFilter } from '@wordpress/hooks'; import { __ } from '@wordpress/i18n'; const MyExamplePage = () =>

My Example Extension

; addFilter( 'woocommerce_admin_pages_list', 'my-namespace', ( pages ) => { pages.push( { container: MyExamplePage, path: '/example', breadcrumbs: [ __( 'My Example Page', 'YOUR-TEXTDOMAIN' ) ], } ); return pages; } ); ``` Above, we're creating a simple [functional React component](https://reactjs.org/docs/components-and-props.html#function-and-class-components) for the sake of demonstration, but a real-world extension would likely have a more complex nesting of components. ## Further reading You can learn more about how page registration works by checking out the [`PageController`](https://woocommerce.github.io/code-reference/classes/Automattic-WooCommerce-Admin-PageController.html) class in the WooCommerce Core Code Reference. You can see real-world examples of the two page registration methods in WooCommerce Core by taking a look at: - [How WooCommerce Admin registers existing core pages](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/includes/react-admin/connect-existing-pages.php) - registering PHP-powered pages - [How WooCommerce registers React-powered Analytics report pages](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Internal/Admin/Analytics.php) - registering React-powered pages --- ## Extension Guidelines *Source: extensions/ux-guidelines-extensions/README.md* # Extension Guidelines This section covers general guidelines, and best practices to follow in order to ensure your product experience aligns with WooCommerce for ease of use, seamless integration, and strong adoption. We strongly recommend you review the current [WooCommerce setup experience](https://woocommerce.com/documentation/plugins/woocommerce/getting-started/) to get familiar with the user experience and taxonomy. We also recommend you review the [WordPress core guidelines](https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/) to ensure your product isn't breaking any rules, and review [this helpful resource](https://woocommerce.com/document/grammar-punctuation-style-guide/) on content style. ## General Use existing WordPress/WooCommerce UI, built in components (text fields, checkboxes, etc) and existing menu structures. Plugins which draw on WordPress' core design aesthetic will benefit from future updates to this design as WordPress continues to evolve. If you need to make an exception for your product, be prepared to provide a valid use case. - [WordPress Components library](https://wordpress.github.io/gutenberg/?path=/story/docs-introduction--page) - [Figma for WordPress](https://make.wordpress.org/design/2018/11/19/figma-for-wordpress/) | ([WordPress Design Library Figma](https://www.figma.com/file/e4tLacmlPuZV47l7901FEs/WordPress-Design-Library)) - [WooCommerce Component Library](https://woocommerce.github.io/woocommerce/) ## Component Library - Storybook > Storybook is an open source tool for developing UI components in isolation for React, React Native and more. It makes building stunning UIs organized and efficient. The WooCommerce repository also includes [Storybook](https://storybook.js.org/) integration that allows testing and developing in a WooCommerce-agnostic context. This is very helpful for developing reusable components and trying generic JavaScript modules without any backend dependency. You can launch Storybook by running `pnpm --filter=@woocommerce/storybook watch:build` locally. It will open in your browser automatically. You can also test Storybook for the current `trunk` branch on GitHub Pages: [https://woocommerce.github.io/woocommerce](https://woocommerce.github.io/woocommerce) --- ## Accessibility *Source: extensions/ux-guidelines-extensions/accessibility.md* # Accessibility Your extensions must meet the [WordPress Accessibility Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/accessibility/) For more information on accessibility, check out the [WordPress accessibility quick start guide](https://make.wordpress.org/accessibility/handbook/best-practices/quick-start-guide/). --- ## Best Practices *Source: extensions/ux-guidelines-extensions/best-practices.md* # Best Practices - **Plugin name should simply state the feature of the plugin and not use an existing core feature or extension in its' title**. The plugin name should appear at all times in the UI as a functional and original name. e.g "Appointments" instead of "VendorXYZ Bookings Plugin for WooCommerce." - **Avoid creating new UI**. Before considering a new UI, review the WordPress interface to see if a component can be repurposed. Follow existing UI navigation patterns so merchants have context on where they are when navigating to a new experience. - **Be considerate of mobile for the merchant (and shopper-facing if applicable) experience**. Stores operate 24/7. Merchants shouldn't be limited to checking their store on a desktop. Extensions need to be built responsively so they work on all device sizes. - **It's all about the merchant**. Don't distract with unrelated content. Keep the product experience front and center to help the user achieve the tasks they purchased your product for. - **Present a review request at the right time**. Presenting users with a request for review is a great way to get feedback on your extension. Think about best placement and timing to show these prompts. - Avoid showing the user a review request upon first launching the extension. Once the user has had a chance to set up, connect, and use the plugin they'll have a better idea of how to rate it. - Try to present the review request at a time that's least disruptive, such as after successful completion of a task or event. - **Don't alter the core interface**. Don't express your brand by changing the shape of containers in the Woo admin. - **Focus on the experience**. After the customer installs your product, the experience should be the primary focus. Keep things simple and guide the user to successful setup. Do not convolute the experience or distract the user with branding, self promotion, large banners, or anything obtrusive. - **Keep copy short and simple**. Limit instructions within the interface to 120-140 characters. Anything longer should be placed in the product documentation. - **Maintain a consistent tone when communicating with a user**. Maintain the same communication style and terminology across an extension, and avoid abbreviations and acronyms. - In extensions: - Use sentences for descriptions, feedback, and headlines. Avoid all-caps text. - Use standard punctuation and avoid excessive exclamation marks. - Use American English. - For more, read our [Grammar, Punctuation, and Capitalization guide](https://woocommerce.com/document/grammar-punctuation-style-guide/). --- ## Colors *Source: extensions/ux-guidelines-extensions/colors.md* # Colors When creating extensions for the WordPress wp-admin, use the core colors, respect the user's WordPress admin color scheme selection, and ensure your designs pass AA level guidelines. When using components with text, such as buttons, cards, or navigation, the background-to-text contrast ratio should be at least 4.5:1 to be [WCAG AA compliant](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html). Be sure to [test your color contrast ratios](https://webaim.org/resources/contrastchecker/) to abide by WCAG standards. - [Accessibility handbook on uses of color and contrast](https://make.wordpress.org/accessibility/handbook/current-projects/use-of-color/) - [Color contrast ratio checker](http://webaim.org/resources/contrastchecker/) - [More resources regarding accessibility and color testing](http://webaim.org/resources/contrastchecker/) For WooCommerce-specific color use, review our [Style Guide](https://woocommerce.com/brand-and-logo-guidelines/). --- ## Navigation *Source: extensions/ux-guidelines-extensions/navigation.md* # Navigation **Menu Structure.** Place your product navigation elements within the existing WooCommerce taxonomy. Examples: - If your extension is extending a component within WooCommerce, it should live directly within that category's section. ![Navigation category](/img/doc_images/Image-1242x764-1.png) - If your plugin adds a settings screen to set up the plugin, settings should be under an appropriate tab on the WooCommerce > Settings screen. Only if necessary, create a top-level settings tab if your plugin has settings that don't fit under existing tabs and creating a sub-tab isn't appropriate. **No iframes, only APIs.** To create a cohesive experience, application data should be loaded via API instead of an iFrame. **One line navigation label.** Keep all navigation labels on one line. Do not introduce a second line in any of the preexisting menu items. **Keep menu structure simple.** Use existing WooCommerce menu structures as much as possible to reduce redundancies. If your plugin must introduce multiple pages or areas, consider grouping them in tabs using existing components to remain consistent with WooCommerce structure. **No top level navigation.** If your product is extending WooCommerce, then there's a 99.9% chance your product navigation, and settings should live within the WooCommerce nav structure-see above menu structure examples. --- ## Notices *Source: extensions/ux-guidelines-extensions/notices.md* # Notices Use notices primarily to provide user feedback in response to an action. Avoid using notices to communicate offers or announcements. Don't apply brand colors, fonts, or illustrations to your notices. If a post-activation notice is required, keep it within the WordPress plugin area-do not display it on the dashboard, or any other parts of the platform. Use the standard WordPress notice format and WooCommerce admin notices API. ## Language Providing timely feedback like success and error messages is essential for ensuring that the user understands whether changes have been made. Use short but meaningful messages that communicate what is happening. Ensure that the message provides instructions on what the user needs to do to continue. Proper punctuation should be used if the message contains multiple sentences. Avoid abbreviations. ## Design The placement of feedback is vital so the user notices it. For example, when validation messages are needed to prompt the user to enter data, get the user's attention by displaying a message close to the inputs where data needs to be revised. ![visualization of four different notice designs next to one another](https://developer.woocommerce.com/wp-content/uploads/2023/12/notices1.png) **Success** message: When the user performs an action that is executed successfully. **Error Message**: When the user performs an action that could not be completed. (This can include validation messages.) When requiring the user to input data, make sure you verify whether each field meets the requirements, such as format, ranges, and if the field is required. Provide validation messages that are adjacent to each field so that the user can act on each in context. Avoid technical jargon. **Warning Message**: When the user performs an action that may have completed successfully, but the user should review it and proceed with caution. **Informational Message**: When it's necessary to provide information before the user executes any action on the screen. Examples can be limitations within a time period or when a global setting limits actions on the current screen. ### Examples ![an example of an informational message as a notice](https://developer.woocommerce.com/wp-content/uploads/2023/12/informational-notice.png) --- ## Onboarding *Source: extensions/ux-guidelines-extensions/onboarding.md* # Onboarding The first experience your users have with your extension is crucial. A user activating your extension for the first time provides an opportunity to onboard new and reorient returning users the right way. Is it clear to the user how to get started? Keep in mind that the more difficult the setup, the more likely a user will abandon the product altogether so keep it simple and direct. **Use primary buttons as calls to action and keep secondary information deprioritized for clarity**. Guide merchants towards successful setup with a clear next step and/or step-by-step process with progress indicator if the extension isn't configured or if setup is not complete. **If necessary, provide a dismissible notification in the plugin area**. Add a notification to communicate next steps if setup or connection is required to successfully enable the plugin. - Use the standard WordPress notice format and WooCommerce admin notices API. - Notices should be dismissible. Users should always have a clear way to close the notice. - Keep the post-activation notice with the WordPress plugin area in context of the plugin listing-do not display it on the dashboard, or any other parts of the platform. - Don't display more than one notice. - Try to keep the notice copy between 125 to 200 characters. If no action is required for setup it's best to rely on other onboarding aids such as the Task List (link to component) and Inbox (link to component) to help users discover features and use your plugin. **Get to the point and keep it instructional**. This is not a time to promote your brand or pitch the product. The user has bought your product and is ready to use it. Keep the information instructional and precise and avoid the use of branded colors, fonts, and illustrations in empty states and other onboarding aids. Help users with context on next steps. **Show helpful empty states**. Rely on the existing plugin UI, if any, to guide users towards successful setup and use of the plugin. Avoid onboarding emails, push notifications, and welcome tours. **Plugins should not redirect on activation from WordPress plugins area**. This can break bulk activation of plugins. Following the [dotorg plugin guideline 11](https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/#11-plugins-should-not-hijack-the-admin-dashboard), the extension shouldn't hijack the dashboard or hide functionality of core or other extensions. **Avoid dead end links and pages**. There should always be a way forward or back. **Error Handling and Messaging**. If users encounter an error during setup, provide a clear and useful notification with clear and easily understood information on what went wrong and how to fix it. --- ## Settings *Source: extensions/ux-guidelines-extensions/settings.md* # Settings **Make extension settings easy to understand.** Only include settings options that are crucial to the overall functionality of the product. **Use smart defaults wherever possible**, rather than asking the merchant to configure. **Group your settings intuitively.** Prioritize frequently-used settings at the top of each section, and place destructive or infrequently-used settings at the bottom. **Group relevant settings together**, either by frequency of use (eg. Quick Settings), or by description (eg. Account Settings). Use section titles and sub-section titles to create clear hierarchy. **Avoid long, tedious lists of many different types of settings.** This overwhelms the user with so many options that the product feels unusable to them. Consider using separate pages or sections for different types of settings, or progressive disclosure that surfaces the most used settings and hides everything else. **Use impersonal titles and text**, eg. "Reports" instead of "My reports." Exception: If referring to the user is necessary for understanding the setting, use the second person ("you") rather than the first person ("I"). **Include helper text** for settings or sections that are not easily understandable. **Use clear and consistent language.** Avoid technical jargon. --- ## Task List and Inbox *Source: extensions/ux-guidelines-extensions/task-list-and-inbox.md* # Task List and Inbox Plugins should choose between implementing a Task or Inbox note based on the following guidelines. Avoid implementing both Task and Inbox note for the same message, which adds clutter and reduces the impact of the message. Use the Task List and Inbox sparingly. Messages should be clear, concise, and maintain a consistent tone. Follow the [Grammar, Punctuation, and Capitalization guide](https://woocommerce.com/document/grammar-punctuation-style-guide/). ## Task List ![an example of a task in the task list](https://developer.woocommerce.com/wp-content/uploads/2023/12/task-list1.png) Anything that **requires** action should go in the task list. - *What appears in the Things to Do Task List:* - Tasks that will enable, connect, or configure an extension. - Tasks that are critical to the business, such as capturing payment or responding to disputes. - *What doesn't appear in the Things to do Task List:* - Any critical update that would impact or prevent the functioning of the store should appear as a top level notice using the standard WordPress component. - Informational notices such as feature announcements or tips for using the plugin should appear in the Inbox as they are not critical and do not require action. - Notifications from user activity should result in regular feedback notices (success, info, error, warning). Examples: ![three tasks in the task list under the heading "Things to do next" with the option to expand at the bottom to "show 3 more tasks" ](https://developer.woocommerce.com/wp-content/uploads/2023/12/task-list-example.png) ## Inbox The Inbox provides informational, useful, and supplemental content to the user, while important notices and setup tasks have their separate and relevant locations. ![an example of an inbox notification](https://developer.woocommerce.com/wp-content/uploads/2023/12/inbox1.png) - *What appears in the Inbox*: - Informational notices such as non-critical reminders. - Requests for plugin reviews and feedback. - Tips for using the plugin and introducing features. - Insights such as inspirational messages or milestones. - *What doesn't appear in the Inbox*: - Notices that require action, extension setup tasks, or regular feedback notices. Examples: ![an example of two inbox notifications listed under the "Inbox" section of the admin](https://developer.woocommerce.com/wp-content/uploads/2023/12/inbox-examples.png) --- ## Testing *Source: extensions/ux-guidelines-extensions/testing.md* # Testing Users must be able to perform all actions of the functionality provided. This can be achieved by running user tests. To run an effective user test, list as many scenarios your extension supports. An example of a scenario: "I am a store owner who wants to create a customizable product that includes letting customers add custom text and select from 5 different color options for the custom text." Give this scenario to testers, and watch them attempt/perform the task with your extension. Different ways to do quick user testing: - Recruit potential customers to test your product. - Use online services that allow you to expose your extension to new users such as UserTesting.com. - Ask enthusiasts in the [WooCommerce Community Slack](https://woocommerce.com/community-slack/) for feedback. --- ## How to add columns to analytics reports and CSV downloads *Source: features/analytics/adding-columns-to-analytics-reports-and-csv-downloads.md* # How to add columns to analytics reports and CSV downloads Adding columns to analytics reports are a really interesting way to add functionality to WooCommerce. New data can be consumed in the table view of the user interface and in your user's favourite spreadsheet or third party application by generating a CSV. These instructions assume that you have a test plugin for WooCommerce installed and activated. You can follow the ["Getting started" instructions](extending-woocommerce-admin-reports.md) to get a test plugin set up. That post also includes instructions to further modify the query that is executed to get the data in an advanced fashion - it isn't required to just add a simple column. In WooCommerce, analytics CSVs are generated in two different ways: in the web browser using data already downloaded, or on the server using a new query. It uses the size of the data set to determine the method - if there is more than one page worth of results it generates the data on the server and emails a link to the user, but if the results fit on one page the data is generated and downloaded straight away in the browser. We'll look at the on-server method for adding a column first, because this is also where the data sent to the browser is generated. This example extends the Downloads analytics report. To get some data in your system for this report, create a downloadable product with a download expiry value, create an order purchasing the product, then download the product several times. In testing I created 26 downloads, which is enough that the report is spread over two pages when showing 25 items per page, and on a single page when showing 50 items per page. This let me test CSVs generated both on the server and in browser. In the PHP for your plugin, add three filter handlers: ```php // This adds the SELECT fragment to the report SQL function add_access_expires_select( $report_columns, $context, $table_name ) { if ( $context !== 'downloads' ) { return $report_columns; } $report_columns['access_expires'] = 'product_permissions.access_expires AS access_expires'; return $report_columns; } add_filter( 'woocommerce_admin_report_columns', 'add_access_expires_select', 10, 3 ); // This adds the column header to the CSV function add_column_header( $export_columns ) { $export_columns['access_expires'] = 'Access expires'; return $export_columns; } add_filter( 'woocommerce_filter_downloads_export_columns', 'add_column_header' ); // This maps the queried item to the export item function map_access_expires( $export_item, $item ) { $export_item['access_expires'] = $item['access_expires']; return $export_item; } add_filter( 'woocommerce_report_downloads_prepare_export_item', 'map_access_expires', 10, 2 ); ``` This adds the access expiry timestamp to the Downloads table/CSV (when the CSV is generated on the server). These three filters together add the new column to the database query, adds the new header to the CSV, and maps the data returned from the database to the CSV. The first filter `woocommerce_admin_report_columns` adds a SQL fragment to the `SELECT` statement generated for the data query. The second filter `woocommerce_filter_downloads_export_columns` adds the column header to the CSV generated on the server. The third filter `woocommerce_report_downloads_prepare_export_item` maps the value in the data returned from the database query `$item` to the export item for the CSV. To finish this off by adding support for columns generated in browser, another filter needs to be added to your plugin's JavaScript: ```js import { addFilter } from "@wordpress/hooks"; function addAccessExpiresToDownloadsReport(reportTableData) { const { endpoint, items } = reportTableData; if ("downloads" !== endpoint) { return reportTableData; } reportTableData.headers = [ ...reportTableData.headers, { label: "Access expires", key: "access_expires", }, ]; reportTableData.rows = reportTableData.rows.map((row, index) => { const item = items.data[index]; const newRow = [ ...row, { display: item.access_expires, value: item.access_expires, }, ]; return newRow; }); return reportTableData; } addFilter( "woocommerce_admin_report_table", "dev-blog-example", addAccessExpiresToDownloadsReport ); ``` This filter first adds the header to the CSV, then maps the data. With the plugin you've created, you should now be able to add data to the analytics table, the CSV generated on the server, and the CSV generated on the browser. --- ## How to extend WooCommerce analytics reports *Source: features/analytics/extending-woocommerce-admin-reports.md* # How to extend WooCommerce analytics reports ## Introduction This document serves as a guide to extending WC-Admin Reports with a basic UI dropdown, added query parameters, and modification of SQL queries and resulting report data. This example will create a currency selector for viewing the Orders Report based on a specific currency. Code from this guide can be viewed in the [woocommerce code repository](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce/client/admin/docs/examples/extensions/sql-modification). ## Getting started We'll be using a local installation of WordPress with WooCommerce and the development version of WC-Admin to take advantage of `create-wc-extension` as a way to easily scaffold a modern WordPress JavaScript environment for plugins. In your local install, clone and start WC-Admin if you haven't already. ```sh cd wp-content/plugins git clone git@github.com:woocommerce/woocommerce.git cd plugins/woocommerce/client/admin npm run build ``` Once that's working, we can setup the extension folder ready for JavaScript development. ```sh npm run create-wc-extension ``` After choosing a name, move into that folder and start webpack to watch and build files. ```sh cd ../ npm install npm start ``` Don't forget to head over to `/wp-admin/plugins.php` and activate your plugin. ## Populating test data Next, set up some orders to have sample data. Using WooCommerce > Settings > Currency, I added three test orders in Mexican Peso, US Dollar, and New Zealand Dollar. After doing so, check out WC-Admin to make sure the orders are showing up by going to `/wp-admin/admin.php?page=wc-admin&period=today&path=%2Fanalytics%2Forders&compare=previous_year`. Note that without any modification currency figures show according to what I have currently in WooCommerce settings, which is New Zealand Dollar in this case. ![screenshot of wp-admin showing processing orders](https://developer.woocommerce.com/wp-content/uploads/2023/12/screen-shot-2020-02-19-at-12.11.34-pm.png?w=851) We can confirm each order's currency by running the following query on the `wp_posts` table and joining `wp_postmeta` to gather currency meta values. Results show an order in NZD, USD, and MXN. This query is similar to the one we'll implement later in the guide to gather and display currency values. ```sql SELECT ID, post_name, post_type, currency_postmeta.meta_value AS currency FROM `wp_posts` JOIN wp_postmeta currency_postmeta ON wp_posts.ID = currency_postmeta.post_id WHERE currency_postmeta.meta_key = '_order_currency' ORDER BY wp_posts.post_date DESC LIMIT 3 ``` ![screenshot of resulting query](https://developer.woocommerce.com/wp-content/uploads/2023/12/screen-shot-2020-02-19-at-12.33.45-pm.png?w=756) ## Add a UI dropdown In order to view reports in differing currencies, a filter or dropdown will be needed. We can add a basic filter to reports by adding a configuration object similar to [this one from the Orders Report](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/client/admin/client/analytics/report/orders/config.js#L50-L62). First, we need to populate the client with data to render the dropdown. The best way to do this is to add data to the `wcSettings` global. This global can be useful for transferring static configuration data from PHP to the client. In the main PHP file, add currency settings to the Data Registry to populate `window.wcSettings.multiCurrency`. ```php function add_currency_settings() { $currencies = array( array( 'label' => __( 'United States Dollar', 'dev-blog-example' ), 'value' => 'USD', ), array( 'label' => __( 'New Zealand Dollar', 'dev-blog-example' ), 'value' => 'NZD', ), array( 'label' => __( 'Mexican Peso', 'dev-blog-example' ), 'value' => 'MXN', ), ); $data_registry = Automattic\WooCommerce\Blocks\Package::container()->get( Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry::class ); $data_registry->add( 'multiCurrency', $currencies ); } add_action( 'init', 'add_currency_settings' ); ``` In the console, you can confirm the data has safely made its way to the client. ![screnshot of console](https://developer.woocommerce.com/wp-content/uploads/2023/12/screen-shot-2020-02-19-at-1.11.50-pm.png?w=476) In `index.js` create the custom currency filter and add it the Orders Report. ```js import { addFilter } from "@wordpress/hooks"; import { __ } from "@wordpress/i18n"; const addCurrencyFilters = (filters) => { return [ { label: __("Currency", "dev-blog-example"), staticParams: [], param: "currency", showFilters: () => true, defaultValue: "USD", filters: [...(wcSettings.multiCurrency || [])], }, ...filters, ]; }; addFilter( "woocommerce_admin_orders_report_filters", "dev-blog-example", addCurrencyFilters ); ``` If we check out the Orders Report, we can see our new dropdown. Play around with it and you'll notice the currency query parameter gets added to the url. If you check out the Network tab, you'll also see this value included in requests for data used to populate the report. For example, see the requests to orders stats endpoint, `/wp-json/wc-analytics/reports/orders/stats`. Next we'll use that query parameter to adjust report results. ![screenshot showing UI dropdown in wp-admin](https://developer.woocommerce.com/wp-content/uploads/2023/12/screen-shot-2020-02-19-at-1.16.44-pm.png?w=512) ## Handle currency parameters on the server Now that our dropdown adds a `currency` query parameter to requests for data, the first thing we'll need to do is add the parameter as a query argument to the Orders Data Store and Orders Stats Data Store. Those data stores use query arguments for caching purposes, so by adding our parameter we can be sure a new database query will be performed when the parameter changes. Add the query argument in your main PHP file. ```php function apply_currency_arg( $args ) { $currency = 'USD'; if ( isset( $_GET['currency'] ) ) { $currency = sanitize_text_field( wp_unslash( $_GET['currency'] ) ); } $args['currency'] = $currency; return $args; } add_filter( 'woocommerce_analytics_orders_query_args', 'apply_currency_arg' ); add_filter( 'woocommerce_analytics_orders_stats_query_args', 'apply_currency_arg' ); ``` Now that we're sure a new database query is performed on mutations of the `currency` query parameter, we can start adding SQL statements to the queries that gather data. Lets start by adding a JOIN for the orders table, orders stats, and orders chart. ```php function add_join_subquery( $clauses ) { global $wpdb; $clauses[] = "JOIN {$wpdb->postmeta} currency_postmeta ON {$wpdb->prefix}wc_order_stats.order_id = currency_postmeta.post_id"; return $clauses; } add_filter( 'woocommerce_analytics_clauses_join_orders_subquery', 'add_join_subquery' ); add_filter( 'woocommerce_analytics_clauses_join_orders_stats_total', 'add_join_subquery' ); add_filter( 'woocommerce_analytics_clauses_join_orders_stats_interval', 'add_join_subquery' ); ``` Next, add a WHERE clause ```php function add_where_subquery( $clauses ) { $currency = 'USD'; if ( isset( $_GET['currency'] ) ) { $currency = sanitize_text_field( wp_unslash( $_GET['currency'] ) ); } $clauses[] = "AND currency_postmeta.meta_key = '_order_currency' AND currency_postmeta.meta_value = '{$currency}'"; return $clauses; } add_filter( 'woocommerce_analytics_clauses_where_orders_subquery', 'add_where_subquery' ); add_filter( 'woocommerce_analytics_clauses_where_orders_stats_total', 'add_where_subquery' ); add_filter( 'woocommerce_analytics_clauses_where_orders_stats_interval', 'add_where_subquery' ); ``` And finally, a SELECT clause. ```php function add_select_subquery( $clauses ) { $clauses[] = ', currency_postmeta.meta_value AS currency'; return $clauses; } add_filter( 'woocommerce_analytics_clauses_select_orders_subquery', 'add_select_subquery' ); add_filter( 'woocommerce_analytics_clauses_select_orders_stats_total', 'add_select_subquery' ); add_filter( 'woocommerce_analytics_clauses_select_orders_stats_interval', 'add_select_subquery' ); ``` Lets head back to the Orders Report and see if it works. You can manipulate the dropdown and see the relevant order reflected in the table. ![screenshot of WooCommerce Orders tab in wp-admin showing the relevant order reflected in the table.](https://developer.woocommerce.com/wp-content/uploads/2023/12/screen-shot-2020-02-19-at-1.38.54-pm.png?w=585) ## Finishing touches The orders table could use some customisation to reflect the selected currency. We can add a column to display the currency in `index.js`. The `reportTableData` argument is an object of headers, rows, and items, which are arrays of data. We'll need to add a new header and append the currency to each row's data array. ```js const addTableColumn = (reportTableData) => { if ("orders" !== reportTableData.endpoint) { return reportTableData; } const newHeaders = [ { label: "Currency", key: "currency", }, ...reportTableData.headers, ]; const newRows = reportTableData.rows.map((row, index) => { const item = reportTableData.items.data[index]; const newRow = [ { display: item.currency, value: item.currency, }, ...row, ]; return newRow; }); reportTableData.headers = newHeaders; reportTableData.rows = newRows; return reportTableData; }; addFilter("woocommerce_admin_report_table", "dev-blog-example", addTableColumn); ``` ![screenshot of customized table](https://developer.woocommerce.com/wp-content/uploads/2023/12/screen-shot-2020-02-19-at-4.02.15-pm.png?w=861) While adding a column is certainly helpful, currency figures in the table and chart only reflect the store currency. ![screenshot of report](https://developer.woocommerce.com/wp-content/uploads/2023/12/screen-shot-2020-02-19-at-4.03.42-pm.png?w=865) In order to change a Report's currency and number formatting, we can make use of the `woocommerce_admin_report_currency` JS hook. You can see the store's default sent to the client in `wcSettings.currency`, but we'll need to change these depending on the currency being viewed and designated by the query parameter `?currency=NZD`. ![screenshot of currency settings](https://developer.woocommerce.com/wp-content/uploads/2023/12/screen-shot-2020-04-03-at-11.18.42-am.png?w=238) First, lets create some configs in index.js. ```js const currencies = { MXN: { code: "MXN", symbol: "$MXN", // For the sake of the example. symbolPosition: "left", thousandSeparator: ",", decimalSeparator: ".", precision: 2, }, NZD: { code: "NZD", symbol: "$NZ", symbolPosition: "left", thousandSeparator: ",", decimalSeparator: ".", precision: 2, }, }; ``` Finally, add our function to the hook which applies a config based on the currency query parameter. ```js const updateReportCurrencies = (config, { currency }) => { if (currency && currencies[currency]) { return currencies[currency]; } return config; }; addFilter( "woocommerce_admin_report_currency", "dev-blog-example", updateReportCurrencies ); ``` 🎉 We can now view our Orders Report and see the currency reflected in monetary values throughout the report. ![Screenshot of customized order report](https://developer.woocommerce.com/wp-content/uploads/2023/12/screen-shot-2020-04-03-at-11.29.05-am.png?w=912) ## Conclusion In this guide, we added a UI element to manipulate query parameters sent to the server and used those values to modify SQL statements which gather report data. In doing so, we established a way to highly customise WC-Admin reports. Hopefully this example illustrates how the platform can be tailored by extensions to bring a powerful experience to users. --- ## Email HTML - Best Practices *Source: features/email/email-html-best-practices.md* # Email HTML - Best Practices ## Overview Email design and development require a different approach than traditional web development. This guide outlines best practices for ensuring your HTML emails render correctly across all major email clients. ### Key principles - **Width**: Keep your email width between 600-640px - This ensures your email displays properly across all devices without horizontal scrolling on smaller screens. - **File size**: Keep total email size under 100KB - Large emails may be clipped by Gmail and other providers, and take longer to load on mobile devices. - **Tables**: Use tables for layout (despite being deprecated for the web) - Email clients have inconsistent CSS support but reliable table rendering. - **Testing**: Test across multiple email clients and devices - Email clients render HTML differently, and testing helps identify and fix rendering issues. ## HTML ### Use older, simpler HTML standards - Use HTML 4.01 or XHTML 1.0 - Many email clients use outdated rendering engines and don't support modern HTML5 features. - Avoid HTML5 elements in the main structure - Elements like `
`, `
`, and `