Commit cce4601fdff for woocommerce
commit cce4601fdff219a3564b8924bbdb023855a5a54e
Author: Ryan Ray <ryan.ray@automattic.com>
Date: Fri Nov 6 05:22:37 2026 -0500
docs: Add order fulfillments developer documentation (#63908)
* docs: Add order fulfillments developer documentation
- Add comprehensive developer guide for order fulfillments feature
- Cover architecture overview, data model, and PHP usage
- Include REST API v3 and v4 reference with examples
- Document all available hooks and filters
- Add frontend integration guide for WordPress data store
- Include best practices for developers
- Add custom shipping provider extension example
* fix: apply CodeRabbit auto-fixes
Fixed 3 file(s) based on 3 unresolved review comments.
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
* Update docs/features/fulfillments/rest-api.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update docs/features/fulfillments/README.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update docs/features/fulfillments/README.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update docs/features/fulfillments/README.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update docs/features/fulfillments/rest-api.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update docs/features/fulfillments/rest-api.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update docs/features/fulfillments/rest-api.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* docs: apply review feedback - fix meta keys, provider example, is_fulfilled, date format
Agent-Logs-Url: https://github.com/woocommerce/woocommerce/sessions/666fdc53-bddb-4e93-aaa9-4cdfbda9d56c
Co-authored-by: ryanr14 <867748+ryanr14@users.noreply.github.com>
* docs: fix MD047 missing trailing newline in hooks.md
Agent-Logs-Url: https://github.com/woocommerce/woocommerce/sessions/84222bc6-4d63-4afe-a14b-426635bc884d
Co-authored-by: ryanr14 <867748+ryanr14@users.noreply.github.com>
* docs: update fulfillments docs based on tpaksu review feedback
Agent-Logs-Url: https://github.com/woocommerce/woocommerce/sessions/ea9ebfa9-8af2-43e1-bead-bcea9512be6f
Co-authored-by: ryanr14 <867748+ryanr14@users.noreply.github.com>
* docs: fix MD036 and MD051 markdownlint violations in README.md
Agent-Logs-Url: https://github.com/woocommerce/woocommerce/sessions/b7c869f2-bd87-4211-88c9-310c751af172
Co-authored-by: ryanr14 <867748+ryanr14@users.noreply.github.com>
* docs: rerun fulfillments docs CI
* Fix fulfillment creation and deletion documentation
* Remove superseded fulfillment guides
* Remove duplicate fulfillment REST API reference
* Document the provider filters and primary key missing from the fulfillments docs
* Document the provider name and shipping option fulfillment meta keys
* Correct the store action signature and fill the remaining fulfillments doc gaps
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: ryanr14 <867748+ryanr14@users.noreply.github.com>
Co-authored-by: Taha Paksu <3295+tpaksu@users.noreply.github.com>
diff --git a/docs/features/fulfillments/README.md b/docs/features/fulfillments/README.md
new file mode 100644
index 00000000000..b4c0a984624
--- /dev/null
+++ b/docs/features/fulfillments/README.md
@@ -0,0 +1,411 @@
+---
+post_title: Order fulfillments
+sidebar_label: Order fulfillments
+category_slug: order-fulfillments
+---
+
+# Order fulfillments
+
+Order fulfillments add a first-class fulfillment record to WooCommerce orders. A single order can have multiple fulfillment records, each with its own status, item allocations, tracking information, and customer notification flow.
+
+This feature is intended for developers who need to create, update, read, or extend fulfillment data from custom code, integrations, or admin tooling. WooCommerce exposes order fulfillments through a dedicated data store, REST API controllers, custom database tables, and WordPress hooks.
+
+## Architecture overview
+
+The fulfillment system is built with several key components working together to provide a complete fulfillment workflow.
+
+### Backend components
+
+- **FulfillmentsManager**: Core manager class handling lifecycle hooks and business logic for fulfillment operations.
+- **Fulfillment**: The main data object representing a fulfillment with methods for accessing and modifying fulfillment state.
+- **FulfillmentsDataStore**: Data persistence layer that handles reading and writing fulfillment data to the custom database tables.
+- **FulfillmentUtils**: Static helpers for the provider registry, the status vocabularies, provider display labels, and tracking number validation.
+- **FulfillmentsSettings**: Handles WooCommerce settings integration including auto-fulfill options and the shipping providers admin page.
+- **OrderFulfillmentsRestController**: REST API endpoints for v3 order-scoped routes, allowing client access to fulfillments under the `/wp-json/wc/v3/orders/{order_id}/fulfillments` namespace. See the [order fulfillments REST API reference](/docs/apis/rest-api/v3/order-fulfillments) for endpoints, request bodies, and error responses.
+
+### Frontend components
+
+- **React-based UI**: Modern interface built with WordPress components used by the WooCommerce admin and order details pages.
+- **WP Data Store**: State management for fulfillment data using the WordPress data API, available under the `order/fulfillments` namespace.
+
+## Enable the feature
+
+The fulfillments data store, database tables, and REST routes are only registered when the `fulfillments` feature is enabled.
+
+For local development, the simplest way to enable it is with WP-CLI:
+
+```bash
+wp option update woocommerce_feature_fulfillments_enabled yes
+```
+
+When the feature initializes, WooCommerce registers the `order-fulfillment` data store, creates the fulfillment tables if they do not already exist, and exposes the REST API endpoints used by the WooCommerce admin and customer-facing order views.
+
+## Data model
+
+Order fulfillments are stored in two custom tables:
+
+- `wp_wc_order_fulfillments`
+- `wp_wc_order_fulfillment_meta`
+
+Each fulfillment row stores the relationship back to the owning entity together with the current fulfillment state.
+
+| Field | Description |
+| --- | --- |
+| `fulfillment_id` | Auto-incrementing primary key. Returned by `$fulfillment->get_id()` and used as the foreign key in the metadata table. |
+| `entity_type` | The supported entity class. Core currently registers the order controller for `WC_Order`. |
+| `entity_id` | The owning order ID. |
+| `status` | The fulfillment status slug, for example `unfulfilled` or `fulfilled`. |
+| `is_fulfilled` | Boolean state used to distinguish draft or pending records from fulfilled records. |
+| `date_updated` | Last update timestamp. |
+| `date_deleted` | Soft-delete timestamp. Deleted fulfillments stay in the table and are filtered out by the controllers. |
+
+The associated metadata table stores the fulfillment payload that developers usually care about most:
+
+| Meta key | Description |
+| --- | --- |
+| `_items` | Array of line item allocations, each with an `item_id` and `qty`. |
+| `_tracking_number` | Carrier tracking number. |
+| `_shipment_provider` | Provider slug used to build tracking links and UI labels. |
+| `_provider_name` | Merchant-entered provider name. Used as the display label when the provider is the custom `other` option or is missing from the registry. |
+| `_tracking_url` | Explicit tracking URL when a provider template is not enough. |
+| `_shipping_option` | How the tracking number was entered in the fulfillments modal, either `tracking-number` for an automatic lookup or `manual-entry`. |
+| `_date_fulfilled` | Timestamp set when a fulfillment transitions to a fulfilled state. |
+| `_is_locked` | Internal flag used to prevent edits in specific workflows. |
+| `_lock_message` | Optional message shown when a fulfillment is locked. |
+
+The metadata table itself has these columns:
+
+| Field | Description |
+| --- | --- |
+| `meta_id` | Auto-incrementing primary key. |
+| `fulfillment_id` | The fulfillment the value belongs to. |
+| `meta_key` | Metadata key. |
+| `meta_value` | Metadata value, stored as `longtext`. |
+| `date_updated` | Last update timestamp. |
+| `date_deleted` | Soft-delete timestamp. Adding, updating, or deleting metadata on a soft-deleted fulfillment throws an exception. |
+
+The `Automattic\WooCommerce\Admin\Features\Fulfillments\Fulfillment` object wraps these values with helper methods such as `get_items()`, `set_items()`, `get_tracking_number()`, and `set_tracking_url()`.
+
+### Statuses
+
+Fulfillment records and orders use separate status vocabularies.
+
+| Scope | Default statuses | Filter |
+| --- | --- | --- |
+| Fulfillment record | `unfulfilled`, `fulfilled` | `woocommerce_fulfillment_fulfillment_statuses` |
+| Order | `unfulfilled`, `partially_fulfilled`, `fulfilled` | `woocommerce_fulfillment_order_fulfillment_statuses` |
+
+Each entry is keyed by the status slug and holds a `label`, `background_color`, and `text_color`. Fulfillment record statuses also carry `is_fulfilled`, which is what fills the `is_fulfilled` column.
+
+## PHP class reference
+
+### Fulfillment
+
+The main data object representing a fulfillment. The `Automattic\WooCommerce\Admin\Features\Fulfillments\Fulfillment` class extends `WC_Data` and wraps fulfillment record values with helper methods.
+
+#### Core methods
+
+- `get_id()`: Get fulfillment ID
+- `set_id( $id )`: Set fulfillment ID
+- `get_entity_type()`: Get associated entity type (typically `WC_Order`)
+- `set_entity_type( $entity_type )`: Set associated entity type
+- `get_entity_id()`: Get associated entity ID
+- `set_entity_id( $entity_id )`: Set associated entity ID
+
+#### Status management
+
+- `get_status()`: Get fulfillment status
+- `set_status( $status )`: Set fulfillment status
+- `get_is_fulfilled()`: Check if fulfillment is completed
+- **Note**: `set_is_fulfilled()` is private and internal to the class; the fulfilled state follows the status you pass to `set_status()`.
+
+#### Items management
+
+- `get_items()`: Get fulfilled items array
+- `set_items( $items )`: Set fulfillment items
+
+The items array contains objects with at least `item_id` (the order line item ID, not the product ID) and `qty` (quantity fulfilled).
+
+#### Date management
+
+- `get_date_updated()`: Get last updated timestamp
+- `set_date_updated( $date )`: Set last updated timestamp
+- `get_date_fulfilled()`: Get fulfillment completion timestamp
+- `set_date_fulfilled( $date )`: Set fulfillment completion timestamp
+- `get_date_deleted()`: Get deletion timestamp (for soft deletes)
+- `set_date_deleted( $date )`: Set deletion timestamp
+
+#### Lock management
+
+- `is_locked()`: Check if fulfillment is locked
+- `get_lock_message()`: Get lock message
+- `set_locked( $locked, $message )`: Set lock status and optional message
+
+**What locking means**: A locked fulfillment cannot be edited by merchants through the WooCommerce admin UI. Extensions use locking to communicate that a fulfillment is being managed externally and should not be modified manually. Common use cases include:
+
+- Marking fulfillments created or updated automatically by a 3rd-party shipping integration as read-only.
+- Preventing further edits once a fulfillment has been handed off to a carrier and confirmed.
+- Enforcing immutability for fulfillments in specific custom statuses that your extension controls.
+
+When `set_locked( true, 'Managed by MyShipping plugin' )` is called, the optional message is shown in the admin UI to explain why the fulfillment cannot be edited.
+
+#### Data access
+
+- `get_order()`: Get the `WC_Order` object associated with this fulfillment (works when entity type is an order)
+- `get_raw_data()`: Get all data including metadata as a raw array
+- `get_raw_meta_data()`: Get raw metadata array
+
+#### Inherited methods from WC_Data
+
+- `save()`: Create or update the fulfillment in the database
+- `delete( $force_delete = false )`: Soft-delete the fulfillment; the fulfillment data store ignores `$force_delete`
+- `get_data()`: Get all data as an associative array
+- `get_changes()`: Get data that has been changed since the last save
+- `apply_changes()`: Apply changes and mark the object as clean
+
+#### Tracking and shipping convenience methods
+
+New in 10.7.0, the `Fulfillment` class provides convenience methods for the most common tracking-related metadata keys:
+
+- `get_tracking_number()`: Get the carrier tracking number (`_tracking_number` meta)
+- `set_tracking_number( $tracking_number )`: Set the carrier tracking number
+- `get_shipment_provider()`: Get the shipment provider slug (`_shipment_provider` meta)
+- `set_shipment_provider( $shipment_provider )`: Set the shipment provider slug
+- `get_tracking_url()`: Get the explicit tracking URL (`_tracking_url` meta)
+- `set_tracking_url( $tracking_url )`: Set the explicit tracking URL
+- `get_item_count()`: Get the total quantity of items across all item allocations
+
+These methods are equivalent to calling the corresponding `get_meta()` / `update_meta_data()` methods directly, but provide type safety and a cleaner API.
+
+#### Metadata management
+
+The Fulfillment class inherits standard WC_Data metadata methods:
+
+- `get_meta_data()`: Get all metadata objects
+- `get_meta( $key, $single = true, $context = 'view' )`: Get metadata value by key
+- `meta_exists( $key )`: Check if metadata key exists
+- `add_meta_data( $key, $value, $unique = false )`: Add new metadata
+- `update_meta_data( $key, $value, $meta_id = 0 )`: Update existing metadata
+- `delete_meta_data( $key )`: Delete metadata by key
+- `delete_meta_data_by_mid( $meta_id )`: Delete metadata by ID
+- `set_meta_data( $data )`: Set all metadata from array
+- `save_meta_data()`: Save metadata changes to database
+
+**Private vs public metadata**: Metadata keys prefixed with an underscore (`_`) are considered private/internal and are not displayed in the admin UI. Keys without an underscore prefix are treated as public metadata and are shown to the merchant in the fulfillment section below the tracking number field. The label for public keys can be customized using the `woocommerce_fulfillment_meta_key_translations` and `woocommerce_fulfillment_translate_meta_key` filters.
+
+#### Usage examples
+
+**Creating a new fulfillment:**
+
+```php
+use Automattic\WooCommerce\Admin\Features\Fulfillments\Fulfillment;
+
+$fulfillment = new Fulfillment();
+$fulfillment->set_entity_type( WC_Order::class );
+$fulfillment->set_entity_id( '123' );
+$fulfillment->set_status( 'fulfilled' );
+
+// Add items to fulfillment (item_id is the order line item ID, not the product ID)
+$items = [
+ [ 'item_id' => 456, 'qty' => 2 ],
+ [ 'item_id' => 789, 'qty' => 1 ]
+];
+$fulfillment->set_items( $items );
+
+// Set tracking information using convenience methods (available since 10.7.0)
+$fulfillment->set_tracking_number( '1Z999AA1234567890' );
+$fulfillment->set_shipment_provider( 'ups' );
+
+// Add public metadata (shown in the admin UI below the tracking number)
+$fulfillment->add_meta_data( 'custom_public_note', 'Handle with care' );
+
+// Save the fulfillment
+$fulfillment->save();
+```
+
+**Working with existing fulfillments:**
+
+```php
+$existing = new Fulfillment( 456 );
+
+// Check what data exists
+$all_data = $existing->get_data(); // Get all data as array
+$changes = $existing->get_changes(); // Get unsaved changes
+
+// Update tracking using convenience methods (available since 10.7.0)
+$existing->set_tracking_number( '1Z999BB1234567890' );
+$existing->set_shipment_provider( 'fedex' );
+$existing->save();
+
+// Or update arbitrary metadata directly
+$existing->delete_meta_data( 'custom_notes' ); // Remove metadata
+$existing->save();
+
+// Check if specific metadata exists
+if ( $existing->meta_exists( '_shipment_provider' ) ) {
+ $provider = $existing->get_meta( '_shipment_provider' );
+}
+
+// Lock/unlock fulfillment
+$existing->set_locked( true, 'Processing by external system' );
+if ( $existing->is_locked() ) {
+ echo $existing->get_lock_message();
+}
+
+// Get associated order
+$order = $existing->get_order();
+if ( $order instanceof WC_Order ) {
+ echo "Order #" . $order->get_id();
+}
+
+// Soft-delete a fulfillment
+$existing->delete();
+```
+
+Deletion sets `date_deleted` and retains the fulfillment record and its metadata. The fulfillment data store does not support permanent deletion through `delete( true )`.
+
+**Creating a fulfillment programmatically:**
+
+```php
+function create_fulfillment_for_order( $order_id, $items, $tracking_number = '' ) {
+ $fulfillment = new Fulfillment();
+ $fulfillment->set_entity_type( WC_Order::class );
+ $fulfillment->set_entity_id( (string) $order_id );
+ $fulfillment->set_status( 'fulfilled' );
+
+ // Add items
+ $fulfillment->set_items( $items );
+
+ // Add tracking if provided (using convenience method available since 10.7.0)
+ if ( ! empty( $tracking_number ) ) {
+ $fulfillment->set_tracking_number( $tracking_number );
+ }
+
+ $fulfillment->save();
+ return $fulfillment;
+}
+```
+
+### Custom shipping provider
+
+To extend fulfillment functionality, you can register custom shipping providers in two ways.
+
+#### Option 1: Via the admin UI
+
+Navigate to **WooCommerce → Settings → Shipping → Shipping providers** to add a provider name, icon, and tracking URL template through the admin interface. Providers added this way appear immediately in the fulfillment tracking provider selector. They are stored as terms in the `wc_fulfillment_shipping_provider` taxonomy, with the icon and tracking URL template in the `icon` and `tracking_url_template` term meta.
+
+> **Note**: Providers added through the UI do not support automatic tracking number parsing. If you need to detect a specific tracking number format and auto-select the correct provider, you must register the provider in code (Option 2) and implement `try_parse_tracking_number()`.
+
+#### Option 2: Via code (supports tracking number parsing)
+
+```php
+use Automattic\WooCommerce\Admin\Features\Fulfillments\Providers\AbstractShippingProvider;
+
+class My_Custom_Shipping_Provider extends AbstractShippingProvider {
+ public function get_key(): string {
+ return 'my-custom-provider';
+ }
+
+ public function get_name(): string {
+ return 'My Custom Provider';
+ }
+
+ public function get_icon(): string {
+ return 'https://example.com/my-carrier-icon.svg';
+ }
+
+ public function get_tracking_url( string $tracking_number ): string {
+ return 'https://mycarrier.com/track/' . rawurlencode( $tracking_number );
+ }
+
+ public function get_shipping_from_countries(): array {
+ return [ 'US', 'CA' ];
+ }
+
+ public function get_shipping_to_countries(): array {
+ return [ 'US', 'CA' ];
+ }
+
+ public function try_parse_tracking_number( string $tracking_number, string $shipping_from, string $shipping_to ): ?array {
+ if ( ! $this->can_ship_from_to( $shipping_from, $shipping_to ) ) {
+ return null;
+ }
+
+ if ( 1 === preg_match( '/^MC[0-9]{10}$/', $tracking_number ) ) {
+ return [
+ 'url' => $this->get_tracking_url( $tracking_number ),
+ 'ambiguity_score' => 100,
+ ];
+ }
+ return null;
+ }
+}
+
+// Register your custom shipping provider.
+add_filter( 'woocommerce_fulfillment_shipping_providers', function( $providers ) {
+ $providers[] = My_Custom_Shipping_Provider::class;
+ return $providers;
+} );
+```
+
+`AbstractShippingProvider` also provides `can_ship_from()`, `can_ship_to()`, and `can_ship_from_to()`, which the built-in providers check before parsing so that a tracking number only matches on routes the carrier serves. They read the two country lists above, which default to empty arrays, so `can_ship_from_to()` returns `false` until you override them.
+
+WooCommerce fills this filter itself, adding the built-in providers at priority 10 and the providers added through the admin UI at priority 20. Both callbacks merge into the array they receive, so a callback at the default priority sees the built-in list and can append to it.
+
+## Frontend integration
+
+The WooCommerce admin uses a WordPress data store for state management of fulfillment data. You can access and interact with fulfillments from React components using the standard WordPress data API.
+
+### Data store
+
+The fulfillments data store is available under the `order/fulfillments` namespace:
+
+```javascript
+import { useSelect, useDispatch } from '@wordpress/data';
+
+function MyComponent({ orderId }) {
+ const { fulfillments, isLoading } = useSelect( ( select ) => ({
+ fulfillments: select( 'order/fulfillments' ).readFulfillments( orderId ),
+ isLoading: select( 'order/fulfillments' ).isLoading( orderId ),
+ }));
+
+ const { saveFulfillment } = useDispatch( 'order/fulfillments' );
+
+ // Component logic here
+}
+```
+
+### Available actions
+
+- `saveFulfillment( orderId, fulfillment, notifyCustomer )`: Create or save a fulfillment
+- `updateFulfillment( orderId, fulfillment, notifyCustomer, customerNote )`: Update an existing fulfillment. `customerNote` is sent as `customer_note` and used in the update notification email.
+- `deleteFulfillment( orderId, fulfillmentId, notifyCustomer )`: Delete a fulfillment
+
+### Available selectors
+
+- `getOrder( orderId )`: Get order details
+- `getRefunds( orderId )`: Get the refunds recorded for the order
+- `readFulfillments( orderId )`: Get all fulfillments for an order
+- `readFulfillment( orderId, fulfillmentId )`: Get a specific fulfillment
+- `isLoading( orderId )`: Check if data is currently loading
+- `getError( orderId )`: Get any error that occurred during API calls
+
+## Best practices for developers
+
+When working with the fulfillments feature, follow these guidelines to ensure robust, secure, and performant implementations:
+
+1. **Error handling**: Always wrap API calls in try-catch blocks to gracefully handle failures.
+2. **Data validation**: Validate fulfillment data before saving, especially item allocations and tracking information.
+3. **Hook priority**: Use appropriate hook priorities when registering callbacks to ensure proper execution order and allow other extensions to hook at the same priority.
+4. **Performance**: Use batch operations for bulk fulfillment updates rather than looping individual operations.
+5. **Security**: Always validate user permissions before allowing fulfillment operations. Use the REST API permission checks as a reference for capability requirements.
+
+## Hooks
+
+Use the [hooks reference](./hooks.md) for lifecycle filters, notification actions, provider filters, and email template hooks.
+
+## Shipping providers and tracking lookups
+
+WooCommerce ships with a provider registry that powers the admin UI and tracking link generation. Developers can extend that registry with filters (see [Custom shipping provider](#custom-shipping-provider) above) or through the **WooCommerce → Settings → Shipping → Shipping providers** admin page.
+
+The feature also exposes a tracking lookup route that runs the `woocommerce_fulfillment_parse_tracking_number` filter with the store base country and the order shipping country. That makes it possible to normalize tracking numbers or infer the correct provider without modifying the REST controller.
diff --git a/docs/features/fulfillments/_category_.json b/docs/features/fulfillments/_category_.json
new file mode 100644
index 00000000000..839e097b32e
--- /dev/null
+++ b/docs/features/fulfillments/_category_.json
@@ -0,0 +1,10 @@
+{
+ "position": 6,
+ "label": "Order fulfillments",
+ "collapsible": true,
+ "collapsed": true,
+ "link": {
+ "type": "doc",
+ "id": "README"
+ }
+}
\ No newline at end of file
diff --git a/docs/features/fulfillments/hooks.md b/docs/features/fulfillments/hooks.md
new file mode 100644
index 00000000000..c95c97773b7
--- /dev/null
+++ b/docs/features/fulfillments/hooks.md
@@ -0,0 +1,151 @@
+---
+post_title: Order fulfillments hooks
+sidebar_label: Hooks
+---
+
+# Order fulfillments hooks
+
+WooCommerce exposes lifecycle hooks, status filters, provider filters, notification actions, and email template hooks for order fulfillments. These hooks let extensions validate fulfillment data, synchronize external systems, customize tracking behavior, and adjust merchant or customer-facing output.
+
+## Data lifecycle filters
+
+These filters run before WooCommerce persists fulfillment changes. Each filter receives the `Fulfillment` object and must return it (optionally modified). To **cancel the operation** — for example, to enforce a validation rule — throw a `FulfillmentException` inside the callback. The exception message is surfaced to the caller as an error response.
+
+| Hook | Purpose |
+| --- | --- |
+| `woocommerce_fulfillment_before_create` | Modify a `Fulfillment` object before it is inserted into the database. Throw a `FulfillmentException` to block creation. |
+| `woocommerce_fulfillment_before_fulfill` | Modify a `Fulfillment` object immediately before it transitions into a fulfilled state during create or update operations. |
+| `woocommerce_fulfillment_before_update` | Modify a `Fulfillment` object before changes are written to the database. Throw a `FulfillmentException` to block the update. |
+| `woocommerce_fulfillment_before_delete` | Modify a `Fulfillment` object before WooCommerce soft-deletes it. Throw a `FulfillmentException` to block deletion. |
+
+```php
+use Automattic\WooCommerce\Admin\Features\Fulfillments\FulfillmentException;
+
+// Require a tracking number before a fulfillment can be created in fulfilled status.
+add_filter( 'woocommerce_fulfillment_before_create', function( $fulfillment ) {
+ if ( $fulfillment->get_is_fulfilled() && ! $fulfillment->get_tracking_number() ) {
+ throw new FulfillmentException(
+ __( 'A tracking number is required to create a fulfilled fulfillment.', 'my-plugin' )
+ );
+ }
+
+ return $fulfillment;
+} );
+```
+
+## Data lifecycle actions
+
+These actions fire after WooCommerce saves or deletes the fulfillment.
+
+| Hook | Parameters | Purpose |
+| --- | --- | --- |
+| `woocommerce_fulfillment_after_create` | `Fulfillment $fulfillment` | React after a fulfillment is created. |
+| `woocommerce_fulfillment_after_fulfill` | `Fulfillment $fulfillment` | React when a fulfillment becomes fulfilled. |
+| `woocommerce_fulfillment_after_update` | `Fulfillment $fulfillment`, `array $changes`, `string $previous_status` | React after an update and inspect the changed core fields or metadata. |
+| `woocommerce_fulfillment_after_delete` | `Fulfillment $fulfillment` | React after a fulfillment is soft-deleted. |
+
+```php
+add_action( 'woocommerce_fulfillment_after_update', function( $fulfillment, $changes, $previous_status ) {
+ $meta_changes = $changes['meta_data'] ?? array();
+
+ if ( array_key_exists( '_tracking_number', $meta_changes ) ) {
+ // Push the new tracking number to an external system.
+ }
+
+ if ( $previous_status !== $fulfillment->get_status() ) {
+ // React to a status transition.
+ }
+}, 10, 3 );
+```
+
+## Notification actions
+
+These actions back the built-in customer fulfillment emails and any additional notification channel an extension wants to add.
+
+| Hook | Parameters | Purpose |
+| --- | --- | --- |
+| `woocommerce_fulfillment_created_notification` | `int $order_id`, `Fulfillment $fulfillment`, `WC_Order $order` | Notify the customer that a fulfilled fulfillment was created. |
+| `woocommerce_fulfillment_updated_notification` | `int $order_id`, `Fulfillment $fulfillment`, `WC_Order $order` | Notify the customer that a fulfilled fulfillment was updated. |
+| `woocommerce_fulfillment_deleted_notification` | `int $order_id`, `Fulfillment $fulfillment`, `WC_Order $order` | Notify the customer that a fulfilled fulfillment was deleted. |
+
+```php
+add_action( 'woocommerce_fulfillment_created_notification', function( $order_id, $fulfillment, $order ) {
+ $phone = $order->get_billing_phone();
+
+ if ( $phone && $fulfillment->get_tracking_number() ) {
+ // Send an SMS in parallel with the built-in email.
+ }
+}, 10, 3 );
+```
+
+## Status, provider, and tracking filters
+
+These filters change how WooCommerce calculates fulfillment state and resolves shipping providers.
+
+| Hook | Purpose |
+| --- | --- |
+| `woocommerce_fulfillment_order_fulfillment_statuses` | Register or modify order-level fulfillment statuses. |
+| `woocommerce_fulfillment_fulfillment_statuses` | Register or modify fulfillment record statuses. |
+| `woocommerce_fulfillment_calculate_order_fulfillment_status` | Override how WooCommerce derives an order's fulfillment state from its fulfillment records. |
+| `woocommerce_fulfillments_auto_fulfill_products` | Return an array of product or variation IDs to fulfill automatically. Receives the IDs array and the `WC_Order` object. |
+| `woocommerce_fulfillment_order_fulfillment_status_text` | Customize the display label for an order-level fulfillment state. |
+| `woocommerce_fulfillment_shipping_providers` | Add or modify shipping providers available to the feature. |
+| `woocommerce_fulfillment_shipment_provider_label` | Customize the provider label used in fulfillment emails, order notes, and the orders list column, after WooCommerce resolves it from the provider list or from fulfillment metadata. Receives the label and the `Fulfillment` object. A value that cannot be rendered as text is ignored. |
+| `woocommerce_rest_prepare_fulfillments_providers` | Add or modify the provider list returned by the fulfillments providers REST endpoint. Receives the providers array and the `WP_REST_Request`. Entries missing `label`, `icon`, `value`, or `url` are dropped. |
+| `woocommerce_fulfillment_parse_tracking_number` | Parse or normalize tracking numbers based on origin and destination countries. |
+| `woocommerce_fulfillment_meta_key_translations` | Customize the translation map for fulfillment metadata labels. |
+| `woocommerce_fulfillment_translate_meta_key` | Customize the label for one metadata key before it is rendered in emails or other UI. |
+
+```php
+add_filter( 'woocommerce_fulfillment_shipping_providers', function( $providers ) {
+ // Option 1: Register a provider class name
+ $providers['my-carrier'] = 'My_Carrier_Provider';
+
+ // Option 2: Register a provider instance
+ $providers['my-carrier-alt'] = new My_Carrier_Provider();
+
+ return $providers;
+} );
+```
+
+## Order note filters
+
+WooCommerce writes order notes when fulfillments are created, updated, deleted, or when fulfillment-related status changes occur. These filters let extensions rewrite those note messages.
+
+| Hook | Purpose |
+| --- | --- |
+| `woocommerce_fulfillment_created_order_note` | Customize the order note written after a fulfillment is created. |
+| `woocommerce_fulfillment_updated_order_note` | Customize the order note written after a fulfillment is updated. |
+| `woocommerce_fulfillment_deleted_order_note` | Customize the order note written after a fulfillment is deleted. |
+| `woocommerce_fulfillment_order_status_changed_order_note` | Customize the note written when the order-level fulfillment status changes. |
+| `woocommerce_fulfillment_status_changed_order_note` | Customize the note written when an individual fulfillment status changes. |
+
+## Email template hooks
+
+The fulfillment email templates expose the standard extension points below.
+
+| Hook | Parameters | Purpose |
+| --- | --- | --- |
+| `woocommerce_email_fulfillment_details` | `WC_Order $order`, `Fulfillment $fulfillment`, `bool $sent_to_admin`, `bool $plain_text`, `WC_Email $email` | Render the fulfillment details block in the email. |
+| `woocommerce_email_fulfillment_meta` | `WC_Order $order`, `Fulfillment $fulfillment`, `bool $sent_to_admin`, `bool $plain_text`, `WC_Email $email` | Render additional metadata inside the email. |
+| `woocommerce_email_before_fulfillment_table` | `WC_Order $order`, `Fulfillment $fulfillment`, `bool $sent_to_admin`, `bool $plain_text`, `WC_Email $email` | Inject content before the fulfillment items table. |
+| `woocommerce_email_after_fulfillment_table` | `WC_Order $order`, `Fulfillment $fulfillment`, `bool $sent_to_admin`, `bool $plain_text`, `WC_Email $email` | Inject content after the fulfillment items table. |
+| `woocommerce_email_fulfillment_items_args` | Template arguments array | Customize how fulfillment line items are rendered in the email templates. |
+| `woocommerce_get_email_fulfillment_items_table` | `string $html`, `WC_Order $order`, `Fulfillment $fulfillment` | Filter and return the rendered fulfillment items table HTML. |
+
+## Privacy filters
+
+When an order is anonymized, through the WordPress personal-data eraser or the automatic retention cleanup, WooCommerce clears the personal data stored on its fulfillments. This also runs on a store where the fulfillments feature has since been disabled, as long as its tables were created while it was on, since any tracking data they hold is still stored.
+
+| Hook | Parameters | Purpose |
+| --- | --- | --- |
+| `woocommerce_privacy_remove_fulfillment_personal_data_meta` | `string[] $meta_keys`, `WC_Order $order` | Add or remove the fulfillment meta keys cleared during erasure. Defaults to `_tracking_number` and `_tracking_url`. |
+
+```php
+add_filter( 'woocommerce_privacy_remove_fulfillment_personal_data_meta', function( $meta_keys, $order ) {
+ // Redact a shipping label URL stored by this extension.
+ $meta_keys[] = '_shipping_label_url';
+
+ return $meta_keys;
+}, 10, 2 );
+```
diff --git a/plugins/woocommerce/client/admin/docs/features/fulfillments-hooks.md b/plugins/woocommerce/client/admin/docs/features/fulfillments-hooks.md
deleted file mode 100644
index 054db2053ca..00000000000
--- a/plugins/woocommerce/client/admin/docs/features/fulfillments-hooks.md
+++ /dev/null
@@ -1,804 +0,0 @@
-# WooCommerce Order Fulfillments - Complete Hooks Reference
-
-This document provides a comprehensive reference for all action hooks and filter hooks available in the WooCommerce Order Fulfillments system.
-
-## Action Hooks
-
-Action hooks allow you to execute custom code at specific points in the fulfillment lifecycle without modifying the return value.
-
-### Data Lifecycle Actions
-
-#### `woocommerce_fulfillment_after_create`
-
-Fired after a fulfillment is successfully created in the database.
-
-**File:** `src/Admin/Features/Fulfillments/DataStore/FulfillmentsDataStore.php:122`
-
-**Parameters:**
-
-- `$data` (Fulfillment) - The created fulfillment object
-
-**Purpose:** Allows plugins to perform actions after a fulfillment is created, such as updating order fulfillment status, syncing to external systems, or logging.
-
-```php
-add_action( 'woocommerce_fulfillment_after_create', 'log_fulfillment_creation' );
-
-function log_fulfillment_creation( $fulfillment ) {
- error_log( 'Fulfillment created: ' . $fulfillment->get_id() );
-}
-```
-
-#### `woocommerce_fulfillment_after_fulfill`
-
-Fired after a fulfillment is marked as fulfilled (both during create and update operations).
-
-**File:** `src/Admin/Features/Fulfillments/DataStore/FulfillmentsDataStore.php:131` and `273`
-
-**Parameters:**
-
-- `$data` (Fulfillment) - The fulfilled fulfillment object
-
-**Purpose:** Triggered when a fulfillment transitions to fulfilled status, useful for inventory updates or external system notifications.
-
-```php
-add_action( 'woocommerce_fulfillment_after_fulfill', 'update_inventory_system' );
-
-function update_inventory_system( $fulfillment ) {
- $items = $fulfillment->get_meta( '_items', true );
- // Update external inventory system
-}
-```
-
-#### `woocommerce_fulfillment_after_update`
-
-Fired after a fulfillment is successfully updated in the database.
-
-**File:** `src/Admin/Features/Fulfillments/DataStore/FulfillmentsDataStore.php`
-
-**Parameters:**
-
-- `$data` (Fulfillment) - The updated fulfillment object
-- `$changes` (array) - The changes that were applied, as returned by `Fulfillment::get_changes()` before save. Core data properties (e.g. `status`, `is_fulfilled`) sit at the top level; meta-based changes (e.g. `_tracking_number`, `_shipment_provider`, `_items`) are nested under the `meta_data` key.
-- `$previous_status` (string) - The fulfillment status before the update (e.g. `'unfulfilled'`)
-
-**Purpose:** Allows plugins to perform actions after a fulfillment is updated. All core data and metadata changes are included in `$changes`.
-
-```php
-add_action( 'woocommerce_fulfillment_after_update', 'sync_fulfillment_changes', 10, 3 );
-
-function sync_fulfillment_changes( $fulfillment, $changes, $previous_status ) {
- // Check if tracking info changed
- $meta_changes = $changes['meta_data'] ?? array();
- if ( array_key_exists( '_tracking_number', $meta_changes ) ) {
- // Sync tracking to external service
- }
-
- // Check if status changed
- if ( array_key_exists( 'status', $changes ) ) {
- // Status changed from $previous_status to $changes['status']
- }
-}
-```
-
-#### `woocommerce_fulfillment_after_delete`
-
-Fired after a fulfillment is soft-deleted from the database.
-
-**File:** `src/Admin/Features/Fulfillments/DataStore/FulfillmentsDataStore.php:331`
-
-**Parameters:**
-
-- `$data` (Fulfillment) - The deleted fulfillment object
-
-**Purpose:** Allows plugins to perform cleanup actions after a fulfillment is deleted.
-
-```php
-add_action( 'woocommerce_fulfillment_after_delete', 'cleanup_fulfillment_data' );
-
-function cleanup_fulfillment_data( $fulfillment ) {
- // Clean up related data or notify external systems
-}
-```
-
-### Customer Notification Actions
-
-#### `woocommerce_fulfillment_created_notification`
-
-Fired when customer notification should be sent for a new fulfilled fulfillment. This should only be triggered for fulfilled fulfillments. Draft fulfillments shouldn’t send a notification when created.
-
-**File:** `src/Admin/Features/Fulfillments/OrderFulfillmentsRestController.php:268` and `370`
-
-**Parameters:**
-
-- `$order_id` (int) - The order ID
-- `$fulfillment` (Fulfillment) - The fulfillment object
-- `$order` (WC_Order) - The order object
-
-**Purpose:** Triggers customer notification emails when a fulfillment is created as fulfilled.
-
-```php
-// To trigger the default email notification sent when a fulfillment is created
-do_action( 'woocommerce_fulfillment_created_notification', $order_id, $fulfillment, $order );
-
-// To add another notification type alongside the email notification
-add_action( 'woocommerce_fulfillment_created_notification', 'send_sms_notification', 10, 3 );
-
-function send_sms_notification( $order_id, $fulfillment, $order ) {
- $phone = $order->get_billing_phone();
- $tracking = $fulfillment->get_tracking_number();
-
- if ( $phone && $tracking ) {
- // Send SMS notification
- }
-}
-```
-
-#### `woocommerce_fulfillment_updated_notification`
-
-Fired when customer notification should be sent for an updated fulfillment. This should only be triggered for fulfilled fulfillments. Draft fulfillments shouldn’t send a notification when updated.
-
-**File:** `src/Admin/Features/Fulfillments/OrderFulfillmentsRestController.php:377`
-
-**Parameters:**
-
-- `$order_id` (int) - The order ID
-- `$fulfillment` (Fulfillment) - The fulfillment object
-- `$order` (WC_Order) - The order object
-
-**Purpose:** Triggers customer notification emails when a fulfillment is updated.
-
-```php
-// To trigger the default email notification sent when a fulfillment is updated
-do_action( 'woocommerce_fulfillment_updated_notification', $order_id, $fulfillment, $order );
-
-// To add another notification type alongside the email notification
-add_action( 'woocommerce_fulfillment_updated_notification', 'notify_fulfillment_update', 10, 3 );
-
-function notify_fulfillment_update( $order_id, $fulfillment, $order ) {
- // Send update notification
-}
-```
-
-#### `woocommerce_fulfillment_deleted_notification`
-
-Fired when customer notification should be sent for a deleted fulfillment. This should only be triggered for fulfilled fulfillments. Draft fulfillments shouldn’t send a notification when deleted.
-
-**File:** `src/Admin/Features/Fulfillments/OrderFulfillmentsRestController.php:437`
-
-**Parameters:**
-
-- `$order_id` (int) - The order ID
-- `$fulfillment` (Fulfillment) - The fulfillment object
-- `$order` (WC_Order) - The order object
-
-**Purpose:** Triggers customer notification emails when a fulfillment is deleted.
-
-```php
-// To trigger the default email notification sent when a fulfillment is cancelled
-do_action( 'woocommerce_fulfillment_deleted_notification', $order_id, $fulfillment, $order );
-
-// To add another notification type alongside the email notification
-add_action( 'woocommerce_fulfillment_deleted_notification', 'notify_fulfillment_cancellation', 10, 3 );
-
-function notify_fulfillment_cancellation( $order_id, $fulfillment, $order ) {
- // Send cancellation notification
-}
-```
-
-### Email Template Actions
-
-#### `woocommerce_email_fulfillment_details`
-
-Called in email templates to display fulfillment details.
-
-**Files:** Various email templates (`customer-fulfillment-created.php`, `customer-fulfillment-updated.php`, `customer-fulfillment-deleted.php`)
-
-**Parameters:**
-
-- `$order` (WC_Order) - The order object
-- `$fulfillment` (Fulfillment) - The fulfillment object
-- `$sent_to_admin` (bool) - Whether email is sent to admin
-- `$plain_text` (bool) - Whether email is plain text
-- `$email` (WC_Email) - The email object
-
-**Purpose:** Allows customization of fulfillment details display in emails.
-
-```php
-add_action( 'woocommerce_email_fulfillment_details', 'add_custom_fulfillment_info', 10, 5 );
-
-function add_custom_fulfillment_info( $order, $fulfillment, $sent_to_admin, $plain_text, $email ) {
- if ( ! $sent_to_admin ) {
- echo '<p>Thank you for your order!</p>';
- }
-}
-```
-
-#### `woocommerce_email_fulfillment_meta`
-
-Called in email templates to display fulfillment metadata.
-
-**Files:** Various email templates
-
-**Parameters:**
-
-- `$order` (WC_Order) - The order object
-- `$fulfillment` (Fulfillment) - The fulfillment object
-- `$sent_to_admin` (bool) - Whether email is sent to admin
-- `$plain_text` (bool) - Whether email is plain text
-- `$email` (WC_Email) - The email object
-
-**Purpose:** Allows customization of fulfillment metadata display in emails.
-
-```php
-add_action( 'woocommerce_email_fulfillment_meta', 'display_custom_meta', 10, 5 );
-
-function display_custom_meta( $order, $fulfillment, $sent_to_admin, $plain_text, $email ) {
- $warehouse = $fulfillment->get_meta( '_warehouse_location', true );
- if ( $warehouse ) {
- echo '<p>Shipped from: ' . esc_html( $warehouse ) . '</p>';
- }
-}
-```
-
-#### `woocommerce_email_before_fulfillment_table`
-
-Called before the fulfillment items table in emails.
-
-**File:** `templates/emails/email-fulfillment-details.php:58`
-
-**Parameters:**
-
-- `$order` (WC_Order) - The order object
-- `$fulfillment` (Fulfillment) - The fulfillment object
-- `$sent_to_admin` (bool) - Whether email is sent to admin
-- `$plain_text` (bool) - Whether email is plain text
-- `$email` (WC_Email) - The email object
-
-**Purpose:** Allows adding content before the fulfillment table.
-
-```php
-add_action( 'woocommerce_email_before_fulfillment_table', 'add_fulfillment_header', 10, 5 );
-
-function add_fulfillment_header( $order, $fulfillment, $sent_to_admin, $plain_text, $email ) {
- echo '<h3>Your Items Have Been Shipped</h3>';
-}
-```
-
-#### `woocommerce_email_after_fulfillment_table`
-
-Called after the fulfillment items table in emails.
-
-**File:** `templates/emails/email-fulfillment-details.php:110`
-
-**Parameters:**
-
-- `$order` (WC_Order) - The order object
-- `$fulfillment` (Fulfillment) - The fulfillment object
-- `$sent_to_admin` (bool) - Whether email is sent to admin
-- `$plain_text` (bool) - Whether email is plain text
-- `$email` (WC_Email) - The email object
-
-**Purpose:** Allows adding content after the fulfillment table.
-
-```php
-add_action( 'woocommerce_email_after_fulfillment_table', 'add_tracking_info', 10, 5 );
-
-function add_tracking_info( $order, $fulfillment, $sent_to_admin, $plain_text, $email ) {
- $tracking = $fulfillment->get_tracking_number();
- if ( $tracking ) {
- echo '<p>Track your package: ' . esc_html( $tracking ) . '</p>';
- }
-}
-```
-
-## Filter Hooks
-
-Filter hooks allow you to modify data as it passes through the fulfillment system by returning a modified value.
-
-### Email Template Filters
-
-#### `woocommerce_email_fulfillment_items_args`
-
-Filters arguments passed to the fulfillment items template.
-
-**File:** `includes/wc-template-functions.php:3702`
-
-**Parameters:**
-
-- `$args` (array) - Array with order, fulfillment, items, and display settings
-
-**Return Value:** Modified array of arguments for fulfillment items display
-
-**Purpose:** Allows customization of how fulfillment items are displayed in emails.
-
-```php
-add_filter( 'woocommerce_email_fulfillment_items_args', 'customize_fulfillment_display' );
-
-function customize_fulfillment_display( $args ) {
- $args['show_sku'] = true;
- $args['show_image'] = false;
- return $args;
-}
-```
-
-#### `woocommerce_get_email_fulfillment_items_table`
-
-Filters the complete HTML output of the fulfillment items table.
-
-**File:** `includes/wc-template-functions.php:3727`
-
-**Parameters:**
-
-- `$html` (string) - The HTML table content
-- `$order` (WC_Order) - The order object
-- `$fulfillment` (Fulfillment) - The fulfillment object
-
-**Return Value:** Modified HTML string for the fulfillment items table
-
-**Purpose:** Allows complete customization of the fulfillment items table HTML.
-
-```php
-add_filter( 'woocommerce_get_email_fulfillment_items_table', 'custom_fulfillment_table', 10, 3 );
-
-function custom_fulfillment_table( $html, $order, $fulfillment ) {
- // Return custom HTML table
- return '<div class="custom-fulfillment-table">' . $html . '</div>';
-}
-```
-
-### Data Validation Filters
-
-#### `woocommerce_fulfillment_before_create`
-
-Allows modification of fulfillment data before it's created in the database. If you want to cancel the creation, you can throw a `FulfillmentException` exception, which will cancel the process , and show the error message on the user’s form.
-
-**File:** `src/Admin/Features/Fulfillments/DataStore/FulfillmentsDataStore.php:59`
-
-**Parameters:**
-
-- `$data` (Fulfillment) - The fulfillment object to be created
-
-**Return Value:** Modified Fulfillment object
-
-**Purpose:** Validate or modify fulfillment data before creation.
-
-```php
-add_filter( 'woocommerce_fulfillment_before_create', 'validate_fulfillment_data' );
-
-function validate_fulfillment_data( $fulfillment ) {
- // Add validation or set default values
- if ( ! $fulfillment->get_meta( '_warehouse_location', true ) ) {
- $fulfillment->update_meta_data( '_warehouse_location', 'Main Warehouse' );
- }
-
- // Or, prevent the update action if some checks of yours fail
- if ( ! $fulfillment->get_meta( '_required_meta' ) ) {
- throw new FulfillmentException( __( 'The fulfillment is missing the required item. Please add it to the fulfillment and try again.', 'woocommerce' ) );
- }
-
- return $fulfillment;
-}
-```
-
-#### `woocommerce_fulfillment_before_update`
-
-Allows modification of fulfillment data before it's updated in the database.
-
-**File:** `src/Admin/Features/Fulfillments/DataStore/FulfillmentsDataStore.php:197`
-
-**Parameters:**
-
-- `$data` (Fulfillment) - The fulfillment object to be updated
-
-**Return Value:** Modified Fulfillment object
-
-**Purpose:** Validate or modify fulfillment data before update. If you want to cancel the update, you can throw a `FulfillmentException` exception, which will cancel the process, and show the error message on the user’s form.
-
-```php
-add_filter( 'woocommerce_fulfillment_before_update', 'log_fulfillment_changes' );
-
-function log_fulfillment_changes( $fulfillment ) {
- // Log changes or perform validation
- $logged = custom_error_log( 'Updating fulfillment: ' . $fulfillment->get_id() );
- if ( $logged ) {
- return $fulfillment;
- }
-
- // Or, prevent the update action if some checks of yours fail
- throw new FulfillmentException( __( 'The fulfillment is missing the required item. Please add it to the fulfillment and try again.', 'woocommerce' ) );
-}
-```
-
-#### `woocommerce_fulfillment_before_fulfill`
-
-Allows modification of fulfillment data before it's marked as fulfilled. If you want to cancel the fulfillment, you can throw a `FulfillmentException` exception, which will cancel the process, and show the error message on the user’s form.
-
-**File:** `src/Admin/Features/Fulfillments/DataStore/FulfillmentsDataStore.php:72` and `213`
-
-**Parameters:**
-
-- `$data` (Fulfillment) - The fulfillment object to be fulfilled
-
-**Return Value:** Modified Fulfillment object
-
-**Purpose:** Validate or modify fulfillment data before marking as fulfilled.
-
-```php
-add_filter( 'woocommerce_fulfillment_before_fulfill', 'set_fulfillment_timestamp' );
-
-function set_fulfillment_timestamp( $fulfillment ) {
- if ( ! $fulfillment->get_meta( '_fulfilled_timestamp', true )) {
- $fulfillment->update_meta_data( '_fulfilled_timestamp', time() );
- return $fulfillment;
- }
-
- // Or, prevent the fulfill action if some checks of yours fail
- throw new FulfillmentException( __( 'The fulfillment already has a fulfilled timestamp. Please check.', 'woocommerce' ) );
-}
-```
-
-#### `woocommerce_fulfillment_before_delete`
-
-Allows modification of fulfillment data before it's deleted. If you want to cancel the deletion, you can throw a `FulfillmentException` exception, which will cancel the process, and show the error message on the user’s form.
-
-**File:** `src/Admin/Features/Fulfillments/DataStore/FulfillmentsDataStore.php:298`
-
-**Parameters:**
-
-- `$data` (Fulfillment) - The fulfillment object to be deleted
-
-**Return Value:** Modified Fulfillment object
-
-**Purpose:** Perform validation or logging before deletion.
-
-```php
-add_filter( 'woocommerce_fulfillment_before_delete', 'log_fulfillment_deletion' );
-
-function log_fulfillment_deletion( $fulfillment ) {
-
- $logged = custom_error_log( 'Deleting fulfillment: ' . $fulfillment->get_id() );
- if ( $logged ) {
- return $fulfillment;
- }
-
- // Or, prevent the delete action if some checks of yours fail
- throw new FulfillmentException( __( 'The fulfillment can\'t be deleted because it is being processed by a 3rd party plugin.', 'woocommerce' ) );
-}
-```
-
-### Status and Display Filters
-
-#### `woocommerce_fulfillment_calculate_order_fulfillment_status`
-
-Allows customization of how order fulfillment status is calculated.
-
-**File:** `src/Admin/Features/Fulfillments/FulfillmentUtils.php:174`
-
-**Parameters:**
-
-- `$status` (string) - The calculated fulfillment status
-- `$order` (WC_Order) - The order object
-- `$fulfillments` (array) - Array of fulfillments for the order
-
-**Return Value:** Modified fulfillment status string
-
-**Purpose:** Customize the logic for determining order fulfillment status.
-
-```php
-add_filter( 'woocommerce_fulfillment_calculate_order_fulfillment_status', 'custom_fulfillment_status', 10, 3 );
-
-function custom_fulfillment_status( $status, $order, $fulfillments ) {
- // Custom logic for status calculation
- $delivered_fulfillments = array_filter( $fulfillments, function( $fulfillment ) {
- return $fulfillment->get_status() === 'delivered';
- });
- if ( count( $fulfillments ) === count( $delivered_fulfillments ) ) {
- return 'delivered';
- }
- return $status;
-}
-```
-
-#### `woocommerce_fulfillment_order_fulfillment_status_text`
-
-Allows customization of fulfillment status text display on the customer order details page. (“It has been **Fulfilled**” part on the below image)
-
-
-
-**File:** `src/Admin/Features/Fulfillments/FulfillmentUtils.php:268`
-
-**Parameters:**
-
-- `$fulfillment_status_text` (string) - The status text to display
-- `$fulfillment_status` (string) - The fulfillment status
-- `$order` (WC_Order) - The order object
-
-**Return Value:** Modified status text string
-
-**Purpose:** Customize how fulfillment status is displayed to users.
-
-```php
-add_filter( 'woocommerce_fulfillment_order_fulfillment_status_text', 'custom_status_text', 10, 3 );
-
-function custom_status_text( $text, $status, $order ) {
- if ( $status === 'delivered' ) {
- return __( 'All items have been <strong>Delivered</strong>.', 'woocommerce' );
- }
- return $text;
-}
-```
-
-#### `woocommerce_fulfillment_order_fulfillment_statuses`
-
-Allows adding/modifying available order fulfillment statuses.
-
-**File:** `src/Admin/Features/Fulfillments/FulfillmentUtils.php:325`
-
-**Parameters:**
-
-- `$order_fulfillment_statuses` (array) - Array of available order fulfillment statuses
-
-**Return Value:** Modified array of order fulfillment statuses
-
-**Purpose:** Add custom order fulfillment statuses.
-
-```php
-add_filter( 'woocommerce_fulfillment_order_fulfillment_statuses', 'add_custom_order_fulfillment_statuses' );
-
-function add_custom_order_fulfillment_statuses( $statuses ) {
- $statuses['bulk_fulfilled'] = __( 'Bulk Fulfilled', 'textdomain' );
- $statuses['expedited'] = __( 'Expedited Fulfillment', 'textdomain' );
- return $statuses;
-}
-```
-
-#### `woocommerce_fulfillment_fulfillment_statuses`
-
-Allows adding/modifying available fulfillment statuses.
-
-**File:** `src/Admin/Features/Fulfillments/FulfillmentUtils.php:350`
-
-**Parameters:**
-
-- `$fulfillment_statuses` (array) - Array of available fulfillment statuses
-
-**Return Value:** Modified array of fulfillment statuses
-
-**Purpose:** Add custom fulfillment statuses.
-
-```php
-add_filter( 'woocommerce_fulfillment_fulfillment_statuses', 'add_custom_fulfillment_statuses' );
-
-function add_custom_fulfillment_statuses( $statuses ) {
- $statuses['in_transit'] = __( 'In Transit', 'textdomain' );
- $statuses['delivered'] = __( 'Delivered', 'textdomain' );
- return $statuses;
-}
-```
-
-### Metadata and Translation Filters
-
-#### `woocommerce_fulfillment_meta_key_translations`
-
-Allows translation of fulfillment meta keys for display purposes.
-
-**File:** `src/Admin/Features/Fulfillments/FulfillmentsManager.php:75`
-
-**Parameters:**
-
-- `$meta_key_translations` (array) - Array of meta key translations
-
-**Return Value:** Modified array of meta key translations
-
-**Purpose:** Provide translations for fulfillment meta keys.
-
-```php
-add_filter( 'woocommerce_fulfillment_meta_key_translations', 'add_meta_translations' );
-
-function add_meta_translations( $translations ) {
- $translations['_warehouse_location'] = __( 'Warehouse', 'textdomain' );
- $translations['_fulfillment_notes'] = __( 'Notes', 'textdomain' );
- return $translations;
-}
-```
-
-#### `woocommerce_fulfillment_translate_meta_key`
-
-Translates individual fulfillment meta keys for user-friendly display.
-
-**File:** `src/Admin/Features/Fulfillments/FulfillmentsManager.php:29`
-
-**Parameters:**
-
-- `$meta_key` (string) - The meta key to translate
-
-**Return Value:** Translated meta key string
-
-**Purpose:** Provide user-friendly labels for meta keys.
-
-```php
-// This uses translations added via `woocommerce_fulfillment_meta_key_translations` filter.
-
-// Will return 'Warehouse'.
-$translated_meta_key = apply_filters( 'woocommerce_fulfillment_translate_meta_key', '_warehouse_location' );
-```
-
-### Business Logic Filters
-
-#### `woocommerce_fulfillments_auto_fulfill_products`
-
-Allows specifying which products should be automatically fulfilled.
-
-**File:** `src/Admin/Features/Fulfillments/FulfillmentsSettings.php:121`
-
-**Parameters:**
-
-- `$auto_fulfill_product_ids` (array) - Array of product IDs to auto-fulfill
-- `$order` (WC_Order) - The order object
-
-**Return Value:** Array of product IDs to auto-fulfill
-
-**Purpose:** Determine which products should be automatically fulfilled based on custom logic.
-
-```php
-add_filter( 'woocommerce_fulfillments_auto_fulfill_products', 'custom_auto_fulfill_logic', 10, 2 );
-
-function custom_auto_fulfill_logic( $product_ids, $order ) {
- // Auto-fulfill products in a specific category
- $gift_cards = get_term_by( 'slug', 'gift-cards', 'product_cat' );
- $products = wc_get_products(
- array(
- 'category' => $gift_cards ? $gift_cards->slug : '',
- )
- );
-
- $product_ids = array_map( function( $product ) {
- return $product->get_id();
- }, $products );
-
- return $product_ids;
-}
-```
-
-### Shipping Provider Filters
-
-#### `woocommerce_fulfillment_shipping_providers`
-
-Allows adding/modifying available shipping providers.
-
-**File:** `src/Admin/Features/Fulfillments/FulfillmentUtils.php:374`
-
-**Parameters:**
-
-- `$shipping_providers` (array) - Array of shipping provider class names
-
-**Return Value:** Modified array of shipping provider class map.
-
-**Purpose:** Register new shipping providers, or overwrite existing ones for tracking number parsing purposes.
-
-```php
-add_filter( 'woocommerce_fulfillment_shipping_providers', 'add_custom_providers' );
-
-function add_custom_providers( $providers ) {
- $providers['custom_provider'] = My_Custom_Shipping_Provider::class;
- return $providers;
-}
-```
-
-**Note:** `My_Custom_Shipping_Provider` class needs to extend `Automattic\WooCommerce\Admin\Features\Fulfillments\Providers\AbstractShippingProvider` class to be able to run tracking number parsing.
-
-#### `woocommerce_fulfillment_shipment_provider_label`
-
-Filters the shipment provider label shown to customers and merchants (fulfillment emails, order notes, and the orders list column).
-
-**File:** `src/Admin/Features/Fulfillments/FulfillmentUtils.php`
-
-**Parameters:**
-
-- `$label` (string) - The resolved provider label (display name for known providers, the merchant-entered name for custom "other" providers, or the slug as a last resort)
-- `$fulfillment` (Fulfillment) - The fulfillment object
-
-**Return Value:** The provider label to display.
-
-**Purpose:** Adjust how a provider's name is displayed without overriding templates.
-
-```php
-add_filter( 'woocommerce_fulfillment_shipment_provider_label', 'customize_provider_label', 10, 2 );
-
-function customize_provider_label( $label, $fulfillment ) {
- return 'usps' === $fulfillment->get_shipment_provider() ? 'USPS (Priority)' : $label;
-}
-```
-
-### Privacy Filters
-
-#### `woocommerce_privacy_remove_fulfillment_personal_data_meta`
-
-Filters the fulfillment meta keys cleared when an order is anonymized through the WordPress personal-data eraser or the automatic retention cleanup. It also runs on a store where the fulfillments feature has since been disabled, as long as its tables were created while it was on, since any tracking data they hold is still stored.
-
-**File:** `src/Admin/Features/Fulfillments/FulfillmentsPrivacy.php`
-
-**Parameters:**
-
-- `$meta_keys` (string[]) - The meta keys to clear. Defaults to `_tracking_number` and `_tracking_url`
-- `$order` (WC_Order) - The order being anonymized
-
-**Return Value:** The list of meta keys to clear.
-
-**Purpose:** Add extension-stored personal data (for example a shipping label URL or an internal shipment ID) to the keys redacted during erasure.
-
-```php
-add_filter( 'woocommerce_privacy_remove_fulfillment_personal_data_meta', 'redact_custom_fulfillment_meta', 10, 2 );
-
-function redact_custom_fulfillment_meta( $meta_keys, $order ) {
- $meta_keys[] = '_shipping_label_url';
- return $meta_keys;
-}
-```
-
-## Best Practices
-
-### Hook Priority and Execution Order
-
-When using multiple hooks, consider the execution order:
-
-```php
-// High priority (executes early)
-add_action( 'woocommerce_fulfillment_after_create', 'critical_function', 5 );
-
-// Normal priority (default: 10)
-add_action( 'woocommerce_fulfillment_after_create', 'normal_function' );
-
-// Low priority (executes late)
-add_action( 'woocommerce_fulfillment_after_create', 'cleanup_function', 20 );
-```
-
-### Error Handling
-
-Always include proper error handling in your hook callbacks:
-
-```php
-add_action( 'woocommerce_fulfillment_after_create', 'sync_to_external_system' );
-
-function sync_to_external_system( $fulfillment ) {
- try {
- // API call to external system
- $response = wp_remote_post( 'https://api.example.com/fulfillments', array(
- 'body' => wp_json_encode( array(
- 'fulfillment_id' => $fulfillment->get_id(),
- 'items' => $fulfillment->get_meta( '_items', true )
- ) ),
- 'headers' => array( 'Content-Type' => 'application/json' )
- ) );
-
- if ( is_wp_error( $response ) ) {
- error_log( 'Fulfillment sync failed: ' . $response->get_error_message() );
- }
- } catch ( Exception $e ) {
- error_log( 'Fulfillment sync error: ' . $e->getMessage() );
- }
-}
-```
-
-### Performance Considerations
-
-For hooks that may be called frequently, consider performance:
-
-```php
-add_filter( 'woocommerce_fulfillment_before_create', 'optimize_fulfillment_data' );
-
-function optimize_fulfillment_data( $fulfillment ) {
- // Cache expensive operations
- static $warehouse_cache = array();
-
- $order_id = $fulfillment->get_entity_id();
- if ( ! isset( $warehouse_cache[ $order_id ] ) ) {
- $warehouse_cache[ $order_id ] = determine_warehouse( $order_id );
- }
-
- $fulfillment->update_meta_data( '_warehouse_location', $warehouse_cache[ $order_id ] );
- return $fulfillment;
-}
-```
-
-This comprehensive reference provides all the hooks available in the WooCommerce Order Fulfillments system, enabling developers to fully customize and extend the fulfillment functionality.
diff --git a/plugins/woocommerce/client/admin/docs/features/fulfillments-rest-api.md b/plugins/woocommerce/client/admin/docs/features/fulfillments-rest-api.md
deleted file mode 100644
index 3f53c4162c2..00000000000
--- a/plugins/woocommerce/client/admin/docs/features/fulfillments-rest-api.md
+++ /dev/null
@@ -1,687 +0,0 @@
-# WooCommerce Order Fulfillments REST API
-
-This document describes the REST API endpoints for managing order fulfillments in WooCommerce.
-
-## Base URL
-
-All endpoints use the WooCommerce REST API v3 namespace:
-
-```http
-/wp-json/wc/v3/orders/{order_id}/fulfillments
-```
-
-## Authentication
-
-All endpoints require authentication with `manage_woocommerce` capability for full access, or order ownership for read-only access.
-
-## Endpoints
-
-### 1. Get Order Fulfillments
-
-Retrieve all fulfillments for a specific order.
-
-**Endpoint:** `GET /wp-json/wc/v3/orders/{order_id}/fulfillments`
-
-#### Parameters
-
-| Parameter | Type | Required | Description |
-| ---------- | ------- | -------- | ------------------------------- |
-| `order_id` | integer | Yes | Unique identifier for the order |
-
-#### Example Request
-
-```http
-GET /wp-json/wc/v3/orders/123/fulfillments
-Authorization: Basic <base64_encoded_credentials>
-```
-
-#### Example Response
-
-```json
-[
- {
- "id": 1,
- "entity_type": "WC_Order",
- "entity_id": "123",
- "status": "fulfilled",
- "is_fulfilled": true,
- "date_updated": "2024-01-15T10:30:00",
- "date_deleted": null,
- "meta_data": [
- {
- "id": 1,
- "key": "_items",
- "value": [
- {
- "item_id": 456,
- "qty": 2
- },
- {
- "item_id": 789,
- "qty": 1
- }
- ]
- },
- {
- "id": 2,
- "key": "_tracking_number",
- "value": "1Z999AA1234567890"
- },
- {
- "id": 3,
- "key": "_shipment_provider",
- "value": "ups"
- }
- ]
- }
-]
-```
-
----
-
-### 2. Create Order Fulfillment
-
-Create a new fulfillment for a specific order.
-
-**Endpoint:** `POST /wp-json/wc/v3/orders/{order_id}/fulfillments`
-
-#### Parameters
-
-| Parameter | Type | Required | Description |
-| ----------------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `order_id` | integer | Yes | Unique identifier for the order |
-| `status` | string | No | Status of the fulfillment (`fulfilled` or `unfulfilled`, or any other custom status that has been added, doesn't indicate that the fulfillment is fulfilled, but that's what's going to be shown on the screen.) |
-| `is_fulfilled` | boolean | No | Whether the fulfillment is fulfilled |
-| `meta_data` | array | Yes | Array of metadata objects |
-| `notify_customer` | boolean | No | Whether to notify customer (query parameter) |
-
-#### Example Request
-
-```http
-POST /wp-json/wc/v3/orders/123/fulfillments?notify_customer=true
-Content-Type: application/json
-Authorization: Basic <base64_encoded_credentials>
-
-{
- "status": "fulfilled",
- "is_fulfilled": true,
- "meta_data": [
- {
- "key": "_items",
- "value": [
- {
- "item_id": 456,
- "qty": 2
- },
- {
- "item_id": 789,
- "qty": 1
- }
- ]
- },
- {
- "key": "_tracking_number",
- "value": "1Z999AA1234567890"
- },
- {
- "key": "_shipment_provider",
- "value": "ups"
- },
- {
- "key": "_tracking_url",
- "value": "https://www.ups.com/track?tracknum=1Z999AA1234567890"
- }
- ]
-}
-```
-
-#### Example Response
-
-```json
-{
- "id": 2,
- "entity_type": "WC_Order",
- "entity_id": "123",
- "status": "fulfilled",
- "is_fulfilled": true,
- "date_updated": "2024-01-15T11:45:00",
- "date_deleted": null,
- "meta_data": [
- {
- "id": 3,
- "key": "_items",
- "value": [
- {
- "item_id": 456,
- "qty": 2
- },
- {
- "item_id": 789,
- "qty": 1
- }
- ]
- },
- {
- "id": 4,
- "key": "_tracking_number",
- "value": "1Z999AA1234567890"
- },
- {
- "id": 5,
- "key": "_shipment_provider",
- "value": "ups"
- },
- {
- "id": 6,
- "key": "_tracking_url",
- "value": "https://www.ups.com/track?tracknum=1Z999AA1234567890"
- }
- ]
-}
-```
-
----
-
-### 3. Get Specific Fulfillment
-
-Retrieve a specific fulfillment by ID.
-
-**Endpoint:** `GET /wp-json/wc/v3/orders/{order_id}/fulfillments/{fulfillment_id}`
-
-#### Parameters
-
-| Parameter | Type | Required | Description |
-| ---------------- | ------- | -------- | ------------------------------------- |
-| `order_id` | integer | Yes | Unique identifier for the order |
-| `fulfillment_id` | integer | Yes | Unique identifier for the fulfillment |
-
-#### Example Request
-
-```http
-GET /wp-json/wc/v3/orders/123/fulfillments/2
-Authorization: Basic <base64_encoded_credentials>
-```
-
-#### Example Response
-
-```json
-{
- "id": 2,
- "entity_type": "WC_Order",
- "entity_id": "123",
- "status": "fulfilled",
- "is_fulfilled": true,
- "date_updated": "2024-01-15T11:45:00",
- "date_deleted": null,
- "meta_data": [
- {
- "id": 3,
- "key": "_tracking_number",
- "value": "1Z999AA1234567890"
- },
- {
- "id": 4,
- "key": "_shipment_provider",
- "value": "ups"
- }
- ]
-}
-```
-
----
-
-### 4. Update Fulfillment
-
-Update an existing fulfillment.
-
-**Endpoint:** `PUT /wp-json/wc/v3/orders/{order_id}/fulfillments/{fulfillment_id}`
-
-#### Parameters
-
-| Parameter | Type | Required | Description |
-| ----------------- | ------- | -------- | -------------------------------------------------------- |
-| `order_id` | integer | Yes | Unique identifier for the order |
-| `fulfillment_id` | integer | Yes | Unique identifier for the fulfillment |
-| `status` | string | No | Status of the fulfillment (`fulfilled` or `unfulfilled`) |
-| `is_fulfilled` | boolean | No | Whether the fulfillment is fulfilled |
-| `meta_data` | array | No | Array of metadata objects |
-| `notify_customer` | boolean | No | Whether to notify customer (query parameter) |
-
-#### Example Request
-
-```http
-PUT /wp-json/wc/v3/orders/123/fulfillments/2?notify_customer=true
-Content-Type: application/json
-Authorization: Basic <base64_encoded_credentials>
-
-{
- "id": 2,
- "status": "fulfilled",
- "is_fulfilled": true,
- "meta_data": [
- {
- "key": "_items",
- "value": [
- {
- "item_id": 456,
- "qty": 2
- },
- {
- "item_id": 789,
- "qty": 1
- }
- ]
- },
- {
- "key": "_tracking_number",
- "value": "1Z999AA1234567890"
- },
- {
- "key": "_shipment_provider",
- "value": "ups"
- },
- {
- "key": "_delivery_date",
- "value": "2024-01-16"
- }
- ]
-}
-```
-
-#### Example Response
-
-```json
-{
- "id": 2,
- "entity_type": "WC_Order",
- "entity_id": "123",
- "status": "fulfilled",
- "is_fulfilled": true,
- "date_updated": "2024-01-16T09:30:00",
- "date_deleted": null,
- "meta_data": [
- {
- "id": 3,
- "key": "_items",
- "value": [
- {
- "item_id": 456,
- "qty": 2
- },
- {
- "item_id": 789,
- "qty": 1
- }
- ]
- },
- {
- "id": 4,
- "key": "_tracking_number",
- "value": "1Z999AA1234567890"
- },
- {
- "id": 5,
- "key": "_shipment_provider",
- "value": "ups"
- },
- {
- "id": 6,
- "key": "_delivery_date",
- "value": "2024-01-16"
- }
- ]
-}
-```
-
----
-
-### 5. Delete Fulfillment
-
-Delete a specific fulfillment.
-
-**Endpoint:** `DELETE /wp-json/wc/v3/orders/{order_id}/fulfillments/{fulfillment_id}`
-
-#### Parameters
-
-| Parameter | Type | Required | Description |
-| ----------------- | ------- | -------- | ------------------------------------------- |
-| `order_id` | integer | Yes | Unique identifier for the order |
-| `fulfillment_id` | integer | Yes | Unique identifier for the fulfillment |
-| `notify_customer` | boolean | No | Whether to notify customer (default: false) |
-
-#### Example Request
-
-```http
-DELETE /wp-json/wc/v3/orders/123/fulfillments/2?notify_customer=true
-Authorization: Basic <base64_encoded_credentials>
-```
-
-#### Example Response
-
-```json
-{
- "message": "Fulfillment deleted successfully."
-}
-```
-
----
-
-### 6. Get Fulfillment Metadata
-
-Retrieve metadata for a specific fulfillment.
-
-**Endpoint:** `GET /wp-json/wc/v3/orders/{order_id}/fulfillments/{fulfillment_id}/metadata`
-
-#### Parameters
-
-| Parameter | Type | Required | Description |
-| ---------------- | ------- | -------- | ------------------------------------- |
-| `order_id` | integer | Yes | Unique identifier for the order |
-| `fulfillment_id` | integer | Yes | Unique identifier for the fulfillment |
-
-#### Example Request
-
-```http
-GET /wp-json/wc/v3/orders/123/fulfillments/2/metadata
-Authorization: Basic <base64_encoded_credentials>
-```
-
-#### Example Response
-
-```json
-[
- {
- "id": 3,
- "key": "_items",
- "value": [
- {
- "item_id": 456,
- "qty": 2
- },
- {
- "item_id": 789,
- "qty": 1
- }
- ]
- },
- {
- "id": 4,
- "key": "_tracking_number",
- "value": "1Z999AA1234567890"
- },
- {
- "id": 5,
- "key": "_shipment_provider",
- "value": "ups"
- },
- {
- "id": 6,
- "key": "_tracking_url",
- "value": "https://www.ups.com/track?tracknum=1Z999AA1234567890"
- }
-]
-```
-
----
-
-### 7. Update Fulfillment Metadata
-
-Update metadata for a specific fulfillment.
-
-**Endpoint:** `PUT /wp-json/wc/v3/orders/{order_id}/fulfillments/{fulfillment_id}/metadata`
-
-#### Parameters
-
-| Parameter | Type | Required | Description |
-| ---------------- | ------- | -------- | ------------------------------------- |
-| `order_id` | integer | Yes | Unique identifier for the order |
-| `fulfillment_id` | integer | Yes | Unique identifier for the fulfillment |
-| `meta_data` | array | Yes | Array of metadata objects to update |
-
-#### Example Request
-
-```http
-PUT /wp-json/wc/v3/orders/123/fulfillments/2/metadata
-Content-Type: application/json
-Authorization: Basic <base64_encoded_credentials>
-
-[
- {
- "key": "_items",
- "value": [
- {
- "item_id": 456,
- "qty": 2
- },
- {
- "item_id": 789,
- "qty": 1
- }
- ]
- },
- {
- "key": "_tracking_number",
- "value": "1Z999AA9876543210"
- },
- {
- "key": "_shipment_provider",
- "value": "fedex"
- },
- {
- "key": "_estimated_delivery",
- "value": "2024-01-18"
- }
-]
-```
-
-#### Example Response
-
-```json
-[
- {
- "id": 3,
- "key": "_items",
- "value": [
- {
- "item_id": 456,
- "qty": 2
- },
- {
- "item_id": 789,
- "qty": 1
- }
- ]
- },
- {
- "id": 4,
- "key": "_tracking_number",
- "value": "1Z999AA9876543210"
- },
- {
- "id": 5,
- "key": "_shipment_provider",
- "value": "fedex"
- },
- {
- "id": 7,
- "key": "_estimated_delivery",
- "value": "2024-01-18"
- }
-]
-```
-
----
-
-### 8. Delete Fulfillment Metadata
-
-Delete specific metadata from a fulfillment.
-
-**Endpoint:** `DELETE /wp-json/wc/v3/orders/{order_id}/fulfillments/{fulfillment_id}/metadata`
-
-#### Parameters
-
-| Parameter | Type | Required | Description |
-| ---------------- | ------- | -------- | ------------------------------------- |
-| `order_id` | integer | Yes | Unique identifier for the order |
-| `fulfillment_id` | integer | Yes | Unique identifier for the fulfillment |
-| `meta_key` | string | Yes | The metadata key to delete |
-
-#### Example Request
-
-```http
-DELETE /wp-json/wc/v3/orders/123/fulfillments/2/metadata
-Authorization: Basic <base64_encoded_credentials>
-{
- "meta_key": "_estimated_delivery"
-}
-```
-
-#### Example Response
-
-Empty response with 204 HTTP Header if successful, otherwise an error message is delivered.
-
----
-
-### 9. Lookup Tracking Number Details
-
-Get tracking information for a tracking number.
-
-**Endpoint:** `GET /wp-json/wc/v3/orders/{order_id}/fulfillments/lookup`
-
-#### Parameters
-
-| Parameter | Type | Required | Description |
-| ----------------- | ------- | -------- | ------------------------------- |
-| `order_id` | integer | Yes | Unique identifier for the order |
-| `tracking_number` | string | Yes | The tracking number to lookup |
-
-#### Example Request
-
-```http
-GET /wp-json/wc/v3/orders/123/fulfillments/lookup?tracking_number=1Z999AA1234567890
-Authorization: Basic <base64_encoded_credentials>
-```
-
-#### Example Response (Multiple Possibilities)
-
-```json
-{
- "tracking_number": "1234567890123456",
- "shipping_provider": "fedex",
- "tracking_url": "https://www.fedex.com/fedextrack/?tracknum=1234567890123456",
- "possibilities": {
- "fedex": {
- "url": "https://www.fedex.com/fedextrack/?tracknum=1234567890123456",
- "ambiguity_score": 95
- },
- "ups": {
- "url": "https://www.ups.com/track?tracknum=1234567890123456",
- "ambiguity_score": 85
- },
- "dhl": {
- "url": "https://www.dhl.com/track?tracknum=1234567890123456",
- "ambiguity_score": 75
- }
- }
-}
-```
-
-#### Example Response (Single Match)
-
-```json
-{
- "tracking_number": "1Z999AA1234567890",
- "shipping_provider": "ups",
- "tracking_url": "https://www.ups.com/track?tracknum=1Z999AA1234567890"
-}
-```
-
----
-
-## Error Responses
-
-All endpoints may return error responses in the following format:
-
-```json
-{
- "code": "error_code",
- "message": "Error message description",
- "data": {
- "status": 400
- }
-}
-```
-
-### Common Error Codes
-
-- `woocommerce_rest_order_invalid_id` - Invalid order ID
-- `woocommerce_rest_tracking_number_missing` - Tracking number is required
-- `woocommerce_rest_order_id_missing` - Order ID is required
-- Authentication errors for insufficient permissions
-
-### HTTP Status Codes
-
-- `200` - Success
-- `201` - Created
-- `204` - No Content (for DELETE metadata)
-- `400` - Bad Request
-- `401` - Unauthorized
-- `404` - Not Found
-
-## Metadata Structure
-
-Metadata objects have the following structure:
-
-| Field | Type | Description |
-| ------- | ------- | ------------------------------------------------------ |
-| `id` | integer | Unique identifier for the metadata (0 for new entries) |
-| `key` | string | The metadata key |
-| `value` | mixed | The metadata value (string, number, array, or object) |
-
-**Note:** The metadata keys prefixed with underscore (`_`) are private and for internal use only. You can add as many as your application needs. The unprefixed ones will be shown to the merchant on the fulfillment metadata box, and to the customer on the fulfillment emails.
-
-### Required Metadata
-
-- `_items` - **REQUIRED** - Array of objects representing items being fulfilled
-
-### Optional Private Metadata Keys
-
-- `_tracking_number` - Shipment tracking number
-- `_shipment_provider` - Shipment provider key (ups, fedex, dhl, etc.)
-- `_tracking_url` - URL to track the shipment
-- `_is_locked` - Whether the fulfillment is locked for merchant modification
-- `_lock_message` - What to show as the lock message for a locked fulfillment
-
-**Note:** The tracking number, shipping provider, and tracking URL metadata keys have typed convenience methods on the `Fulfillment` object: `get_tracking_number()` / `set_tracking_number()`, `get_shipment_provider()` / `set_shipment_provider()`, and `get_tracking_url()` / `set_tracking_url()`. These are recommended over direct `get_meta()` / `update_meta_data()` calls for better type safety and IDE autocompletion.
-
-### Items Structure
-
-The `_items` metadata must be an array of objects with the following structure:
-
-```json
-[
- {
- "item_id": 456,
- "qty": 2
- },
- {
- "item_id": 789,
- "qty": 1
- }
-]
-```
-
-| Field | Type | Description |
-| --------- | ------- | ------------------------------------ |
-| `item_id` | integer | The order line item ID |
-| `qty` | integer | Quantity of the item being fulfilled |
-
-**Note:** The `_items` value is stored as a native array object in the metadata.
-
-## Notifications
-
-When `notify_customer` is set to `true`, WooCommerce will trigger appropriate action hooks:
-
-- `woocommerce_fulfillment_created_notification` - When a fulfillment is created or marked fulfilled
-- `woocommerce_fulfillment_updated_notification` - When a fulfilled fulfillment is updated
-- `woocommerce_fulfillment_deleted_notification` - When a fulfilled fulfillment is deleted