Commit 56d02966bf8 for woocommerce

commit 56d02966bf81ab9758d487f735ac07f43bdc4a3e
Author: Néstor Soriano <konamiman@konamiman.com>
Date:   Fri Apr 24 16:32:39 2026 +0200

    Make the GraphQL engine externally usable (#64390)

diff --git a/plugins/woocommerce/changelog/pr-64390 b/plugins/woocommerce/changelog/pr-64390
new file mode 100644
index 00000000000..6df4254b5c7
--- /dev/null
+++ b/plugins/woocommerce/changelog/pr-64390
@@ -0,0 +1,4 @@
+Significance: minor
+Type: update
+
+Make the GraphQL engine externally usable
diff --git a/plugins/woocommerce/composer.json b/plugins/woocommerce/composer.json
index 079e2e31729..6545c534e26 100644
--- a/plugins/woocommerce/composer.json
+++ b/plugins/woocommerce/composer.json
@@ -109,7 +109,8 @@
 			"src/StoreApi/deprecated.php",
 			"src/StoreApi/functions.php",
 			"src/Blocks/Domain/Services/functions.php",
-			"src/Deprecated.php"
+			"src/Deprecated.php",
+			"src/Internal/Api/Schema/aliases.php"
 		]
 	},
 	"autoload-dev": {
diff --git a/plugins/woocommerce/src/Api/Container.php b/plugins/woocommerce/src/Api/Container.php
new file mode 100644
index 00000000000..fe4a2ae2193
--- /dev/null
+++ b/plugins/woocommerce/src/Api/Container.php
@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Automattic\WooCommerce\Api;
+
+/**
+ * Resolver container for code-API command classes.
+ *
+ * Autogenerated GraphQL query and mutation resolvers look up the corresponding
+ * code-API command through {@see self::get()}. WooCommerce core delegates every
+ * lookup to the main WooCommerce DI container.
+ *
+ * Sibling plugins that reuse the GraphQL infrastructure and want their command
+ * classes instantiated through a container of their own can ship their own
+ * Container class at `<plugin-api-namespace>\Container` with the same public
+ * signature: ApiBuilder detects it during generation and routes the generated
+ * resolvers through it. When no such class is present, resolvers fall back to
+ * `new $command_class()`.
+ */
+final class Container {
+	/**
+	 * Resolve a command class to an instance.
+	 *
+	 * @param string $class_name Fully-qualified name of a code-API command class.
+	 * @return object An instance of $class_name.
+	 */
+	public static function get( string $class_name ): object {
+		return wc_get_container()->get( $class_name );
+	}
+}
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLController.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLController.php
new file mode 100644
index 00000000000..c04a4295e19
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLController.php
@@ -0,0 +1,20 @@
+<?php
+declare(strict_types=1);
+
+// THIS FILE IS AUTO-GENERATED. DO NOT EDIT MANUALLY.
+
+namespace Automattic\WooCommerce\Internal\Api\Autogenerated;
+
+use Automattic\WooCommerce\Internal\Api\Schema\Schema;
+
+class GraphQLController extends \Automattic\WooCommerce\Internal\Api\GraphQLController {
+	protected function build_schema(): Schema {
+		return new Schema(
+			array(
+				'query'    => RootQueryType::get(),
+				'mutation' => RootMutationType::get(),
+				'types'    => TypeRegistry::get_interface_implementors(),
+			)
+		);
+	}
+}
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/CreateCoupon.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/CreateCoupon.php
index 273e579a2a4..d3fbad4c49a 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/CreateCoupon.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/CreateCoupon.php
@@ -10,8 +10,8 @@ use Automattic\WooCommerce\Internal\Api\QueryInfoExtractor;
 use Automattic\WooCommerce\Internal\Api\Utils;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output\Coupon as CouponType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Input\CreateCoupon as CreateCouponInput;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ResolveInfo;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ResolveInfo;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class CreateCoupon {
 	public static function get_field_definition(): array {
@@ -31,7 +31,7 @@ class CreateCoupon {
 	public static function resolve( mixed $root, array $args, mixed $context, ResolveInfo $info ): mixed {
 		Utils::check_current_user_can( 'manage_woocommerce' );

-		$command = wc_get_container()->get( CreateCouponCommand::class );
+		$command = \Automattic\WooCommerce\Api\Container::get( CreateCouponCommand::class );

 		$execute_args = array();
 		if ( array_key_exists( 'input', $args ) ) {
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/CreateProduct.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/CreateProduct.php
index 8bac27defe5..5a3344ffd27 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/CreateProduct.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/CreateProduct.php
@@ -10,8 +10,8 @@ use Automattic\WooCommerce\Internal\Api\QueryInfoExtractor;
 use Automattic\WooCommerce\Internal\Api\Utils;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Interfaces\Product as ProductInterface;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Input\CreateProduct as CreateProductInput;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ResolveInfo;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ResolveInfo;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class CreateProduct {
 	public static function get_field_definition(): array {
@@ -31,7 +31,7 @@ class CreateProduct {
 	public static function resolve( mixed $root, array $args, mixed $context, ResolveInfo $info ): mixed {
 		Utils::check_current_user_can( 'edit_products' );

-		$command = wc_get_container()->get( CreateProductCommand::class );
+		$command = \Automattic\WooCommerce\Api\Container::get( CreateProductCommand::class );

 		$execute_args = array();
 		if ( array_key_exists( 'input', $args ) ) {
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/DeleteCoupon.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/DeleteCoupon.php
index 9676979c287..05e242e2f79 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/DeleteCoupon.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/DeleteCoupon.php
@@ -9,8 +9,8 @@ use Automattic\WooCommerce\Api\Mutations\Coupons\DeleteCoupon as DeleteCouponCom
 use Automattic\WooCommerce\Internal\Api\QueryInfoExtractor;
 use Automattic\WooCommerce\Internal\Api\Utils;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output\DeleteCouponResult as DeleteCouponResultType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ResolveInfo;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ResolveInfo;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class DeleteCoupon {
 	public static function get_field_definition(): array {
@@ -35,7 +35,7 @@ class DeleteCoupon {
 	public static function resolve( mixed $root, array $args, mixed $context, ResolveInfo $info ): mixed {
 		Utils::check_current_user_can( 'manage_woocommerce' );

-		$command = wc_get_container()->get( DeleteCouponCommand::class );
+		$command = \Automattic\WooCommerce\Api\Container::get( DeleteCouponCommand::class );

 		$execute_args = array();
 		if ( array_key_exists( 'id', $args ) ) {
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/DeleteProduct.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/DeleteProduct.php
index 2ed4910cefc..7790f9bf36e 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/DeleteProduct.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/DeleteProduct.php
@@ -8,14 +8,14 @@ namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLMutations;
 use Automattic\WooCommerce\Api\Mutations\Products\DeleteProduct as DeleteProductCommand;
 use Automattic\WooCommerce\Internal\Api\QueryInfoExtractor;
 use Automattic\WooCommerce\Internal\Api\Utils;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ResolveInfo;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ResolveInfo;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class DeleteProduct {
 	public static function get_field_definition(): array {
 		return array(
 			'type'        => Type::nonNull(
-				new \Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType(
+				new \Automattic\WooCommerce\Internal\Api\Schema\ObjectType(
 					array(
 						'name'   => 'DeleteProductResult',
 						'fields' => array(
@@ -43,7 +43,7 @@ class DeleteProduct {
 	public static function resolve( mixed $root, array $args, mixed $context, ResolveInfo $info ): mixed {
 		Utils::check_current_user_can( 'manage_woocommerce' );

-		$command = wc_get_container()->get( DeleteProductCommand::class );
+		$command = \Automattic\WooCommerce\Api\Container::get( DeleteProductCommand::class );

 		$execute_args = array();
 		if ( array_key_exists( 'id', $args ) ) {
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/UpdateCoupon.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/UpdateCoupon.php
index ca89b31d21d..86c284a2a36 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/UpdateCoupon.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/UpdateCoupon.php
@@ -10,8 +10,8 @@ use Automattic\WooCommerce\Internal\Api\QueryInfoExtractor;
 use Automattic\WooCommerce\Internal\Api\Utils;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output\Coupon as CouponType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Input\UpdateCoupon as UpdateCouponInput;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ResolveInfo;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ResolveInfo;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class UpdateCoupon {
 	public static function get_field_definition(): array {
@@ -31,7 +31,7 @@ class UpdateCoupon {
 	public static function resolve( mixed $root, array $args, mixed $context, ResolveInfo $info ): mixed {
 		Utils::check_current_user_can( 'manage_woocommerce' );

-		$command = wc_get_container()->get( UpdateCouponCommand::class );
+		$command = \Automattic\WooCommerce\Api\Container::get( UpdateCouponCommand::class );

 		$execute_args = array();
 		if ( array_key_exists( 'input', $args ) ) {
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/UpdateProduct.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/UpdateProduct.php
index 25b65f851a6..c3fb86c5952 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/UpdateProduct.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLMutations/UpdateProduct.php
@@ -10,8 +10,8 @@ use Automattic\WooCommerce\Internal\Api\QueryInfoExtractor;
 use Automattic\WooCommerce\Internal\Api\Utils;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Interfaces\Product as ProductInterface;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Input\UpdateProduct as UpdateProductInput;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ResolveInfo;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ResolveInfo;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class UpdateProduct {
 	public static function get_field_definition(): array {
@@ -31,7 +31,7 @@ class UpdateProduct {
 	public static function resolve( mixed $root, array $args, mixed $context, ResolveInfo $info ): mixed {
 		Utils::check_current_user_can( 'manage_woocommerce' );

-		$command = wc_get_container()->get( UpdateProductCommand::class );
+		$command = \Automattic\WooCommerce\Api\Container::get( UpdateProductCommand::class );

 		$execute_args = array();
 		if ( array_key_exists( 'input', $args ) ) {
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLQueries/GetCoupon.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLQueries/GetCoupon.php
index 589fec2ba50..fe649064cfc 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLQueries/GetCoupon.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLQueries/GetCoupon.php
@@ -9,8 +9,8 @@ use Automattic\WooCommerce\Api\Queries\Coupons\GetCoupon as GetCouponCommand;
 use Automattic\WooCommerce\Internal\Api\QueryInfoExtractor;
 use Automattic\WooCommerce\Internal\Api\Utils;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output\Coupon as CouponType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ResolveInfo;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ResolveInfo;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class GetCoupon {
 	public static function get_field_definition(): array {
@@ -36,7 +36,7 @@ class GetCoupon {
 	public static function resolve( mixed $root, array $args, mixed $context, ResolveInfo $info ): mixed {
 		Utils::check_current_user_can( 'read_private_shop_coupons' );

-		$command = wc_get_container()->get( GetCouponCommand::class );
+		$command = \Automattic\WooCommerce\Api\Container::get( GetCouponCommand::class );

 		$execute_args = array();
 		if ( array_key_exists( 'id', $args ) ) {
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLQueries/GetProduct.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLQueries/GetProduct.php
index f9c641c2cd1..f69702986f4 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLQueries/GetProduct.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLQueries/GetProduct.php
@@ -9,8 +9,8 @@ use Automattic\WooCommerce\Api\Queries\Products\GetProduct as GetProductCommand;
 use Automattic\WooCommerce\Internal\Api\QueryInfoExtractor;
 use Automattic\WooCommerce\Internal\Api\Utils;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Interfaces\Product as ProductInterface;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ResolveInfo;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ResolveInfo;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class GetProduct {
 	public static function get_field_definition(): array {
@@ -28,7 +28,7 @@ class GetProduct {
 	}

 	public static function resolve( mixed $root, array $args, mixed $context, ResolveInfo $info ): mixed {
-		$command = wc_get_container()->get( GetProductCommand::class );
+		$command = \Automattic\WooCommerce\Api\Container::get( GetProductCommand::class );

 		$execute_args = array();
 		if ( array_key_exists( 'id', $args ) ) {
@@ -43,7 +43,7 @@ class GetProduct {
 				'_preauthorized' => current_user_can( 'read_product' ),
 			)
 		) ) {
-			throw new \Automattic\WooCommerce\Vendor\GraphQL\Error\Error(
+			throw new \Automattic\WooCommerce\Internal\Api\Schema\Error(
 				'You do not have permission to perform this action.',
 				extensions: array( 'code' => 'UNAUTHORIZED' )
 			);
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLQueries/ListCoupons.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLQueries/ListCoupons.php
index 690735d0717..a1a6563fc3d 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLQueries/ListCoupons.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLQueries/ListCoupons.php
@@ -10,8 +10,8 @@ use Automattic\WooCommerce\Internal\Api\QueryInfoExtractor;
 use Automattic\WooCommerce\Internal\Api\Utils;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Pagination\CouponConnection as CouponConnectionType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums\CouponStatus as CouponStatusType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ResolveInfo;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ResolveInfo;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class ListCoupons {
 	public static function get_field_definition(): array {
@@ -53,7 +53,7 @@ class ListCoupons {
 	public static function resolve( mixed $root, array $args, mixed $context, ResolveInfo $info ): mixed {
 		Utils::check_current_user_can( 'read_private_shop_coupons' );

-		$command = wc_get_container()->get( ListCouponsCommand::class );
+		$command = \Automattic\WooCommerce\Api\Container::get( ListCouponsCommand::class );

 		$execute_args               = array();
 		$execute_args['pagination'] = Utils::create_pagination_params( $args );
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLQueries/ListProducts.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLQueries/ListProducts.php
index 19ef206a7d4..84a57d886fa 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLQueries/ListProducts.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLQueries/ListProducts.php
@@ -12,8 +12,8 @@ use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Pagination\Pr
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums\ProductStatus as ProductStatusType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums\StockStatus as StockStatusType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums\ProductType as ProductTypeType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ResolveInfo;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ResolveInfo;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class ListProducts {
 	public static function get_field_definition(): array {
@@ -71,7 +71,7 @@ class ListProducts {
 		Utils::check_current_user_can( 'manage_woocommerce' );
 		Utils::check_current_user_can( 'edit_products' );

-		$command = wc_get_container()->get( ListProductsCommand::class );
+		$command = \Automattic\WooCommerce\Api\Container::get( ListProductsCommand::class );

 		$execute_args               = array();
 		$execute_args['pagination'] = Utils::create_pagination_params( $args );
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Enums/CouponStatus.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Enums/CouponStatus.php
index 70d2a043b62..37f67279a03 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Enums/CouponStatus.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Enums/CouponStatus.php
@@ -6,7 +6,7 @@ declare(strict_types=1);
 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums;

 use Automattic\WooCommerce\Api\Enums\Coupons\CouponStatus as CouponStatusEnum;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\EnumType;
+use Automattic\WooCommerce\Internal\Api\Schema\EnumType;

 class CouponStatus {
 	private static ?EnumType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Enums/DiscountType.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Enums/DiscountType.php
index 8c65fc559ad..be3d856ffb6 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Enums/DiscountType.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Enums/DiscountType.php
@@ -6,7 +6,7 @@ declare(strict_types=1);
 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums;

 use Automattic\WooCommerce\Api\Enums\Coupons\DiscountType as DiscountTypeEnum;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\EnumType;
+use Automattic\WooCommerce\Internal\Api\Schema\EnumType;

 class DiscountType {
 	private static ?EnumType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Enums/ProductStatus.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Enums/ProductStatus.php
index 006dcb95f4d..c11fe73bee5 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Enums/ProductStatus.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Enums/ProductStatus.php
@@ -6,7 +6,7 @@ declare(strict_types=1);
 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums;

 use Automattic\WooCommerce\Api\Enums\Products\ProductStatus as ProductStatusEnum;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\EnumType;
+use Automattic\WooCommerce\Internal\Api\Schema\EnumType;

 class ProductStatus {
 	private static ?EnumType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Enums/ProductType.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Enums/ProductType.php
index d335706cacf..b35a5f3941b 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Enums/ProductType.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Enums/ProductType.php
@@ -6,7 +6,7 @@ declare(strict_types=1);
 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums;

 use Automattic\WooCommerce\Api\Enums\Products\ProductType as ProductTypeEnum;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\EnumType;
+use Automattic\WooCommerce\Internal\Api\Schema\EnumType;

 class ProductType {
 	private static ?EnumType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Enums/StockStatus.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Enums/StockStatus.php
index d48329f324f..afb03e84284 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Enums/StockStatus.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Enums/StockStatus.php
@@ -6,7 +6,7 @@ declare(strict_types=1);
 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums;

 use Automattic\WooCommerce\Api\Enums\Products\StockStatus as StockStatusEnum;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\EnumType;
+use Automattic\WooCommerce\Internal\Api\Schema\EnumType;

 class StockStatus {
 	private static ?EnumType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/CreateCoupon.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/CreateCoupon.php
index f20309ba37a..7fbe29a3953 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/CreateCoupon.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/CreateCoupon.php
@@ -7,8 +7,8 @@ namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Input;

 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums\DiscountType as DiscountTypeType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums\CouponStatus as CouponStatusType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\InputObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\InputObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class CreateCoupon {
 	private static ?InputObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/CreateProduct.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/CreateProduct.php
index 60df57d398c..68c5c02d659 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/CreateProduct.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/CreateProduct.php
@@ -8,8 +8,8 @@ namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Input;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums\ProductStatus as ProductStatusType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums\ProductType as ProductTypeType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Input\Dimensions as DimensionsInput;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\InputObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\InputObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class CreateProduct {
 	private static ?InputObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/Dimensions.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/Dimensions.php
index 0c701c4df05..cf1de0d94f1 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/Dimensions.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/Dimensions.php
@@ -5,8 +5,8 @@ declare(strict_types=1);

 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Input;

-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\InputObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\InputObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class Dimensions {
 	private static ?InputObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/ProductFilter.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/ProductFilter.php
index 639ea1d246c..f67ece29a59 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/ProductFilter.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/ProductFilter.php
@@ -7,8 +7,8 @@ namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Input;

 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums\ProductStatus as ProductStatusType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums\StockStatus as StockStatusType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\InputObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\InputObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class ProductFilter {
 	private static ?InputObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/UpdateCoupon.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/UpdateCoupon.php
index 64306d7e93f..bf2fa5e16f4 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/UpdateCoupon.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/UpdateCoupon.php
@@ -7,8 +7,8 @@ namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Input;

 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums\DiscountType as DiscountTypeType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums\CouponStatus as CouponStatusType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\InputObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\InputObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class UpdateCoupon {
 	private static ?InputObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/UpdateProduct.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/UpdateProduct.php
index 76eec48eec2..b07a937ed97 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/UpdateProduct.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Input/UpdateProduct.php
@@ -8,8 +8,8 @@ namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Input;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums\ProductStatus as ProductStatusType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums\ProductType as ProductTypeType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Input\Dimensions as DimensionsInput;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\InputObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\InputObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class UpdateProduct {
 	private static ?InputObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Interfaces/ObjectWithId.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Interfaces/ObjectWithId.php
index 617ce3fdf62..127fc871431 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Interfaces/ObjectWithId.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Interfaces/ObjectWithId.php
@@ -6,8 +6,8 @@ declare(strict_types=1);
 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Interfaces;

 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output\Coupon as CouponType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\InterfaceType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\InterfaceType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class ObjectWithId {
 	private static ?InterfaceType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Interfaces/Product.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Interfaces/Product.php
index 0b2ab53aa4d..483dbd6d633 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Interfaces/Product.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Interfaces/Product.php
@@ -17,8 +17,8 @@ use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output\Produc
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output\ExternalProduct as ExternalProductType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output\VariableProduct as VariableProductType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output\SimpleProduct as SimpleProductType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\InterfaceType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\InterfaceType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class Product {
 	private static ?InterfaceType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/Coupon.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/Coupon.php
index 8ec2751eab3..73b23c597a7 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/Coupon.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/Coupon.php
@@ -9,8 +9,8 @@ use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums\Discoun
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Enums\CouponStatus as CouponStatusType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Scalars\DateTime as DateTimeType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Interfaces\ObjectWithId as ObjectWithIdInterface;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class Coupon {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/DeleteCouponResult.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/DeleteCouponResult.php
index 0d026955221..694dd79554f 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/DeleteCouponResult.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/DeleteCouponResult.php
@@ -5,8 +5,8 @@ declare(strict_types=1);

 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output;

-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class DeleteCouponResult {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ExternalProduct.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ExternalProduct.php
index fd69028cb98..8b13d74d03d 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ExternalProduct.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ExternalProduct.php
@@ -14,8 +14,8 @@ use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output\Produc
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Pagination\ProductReviewConnection as ProductReviewConnectionType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Scalars\DateTime as DateTimeType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Interfaces\Product as ProductInterface;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class ExternalProduct {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ProductAttribute.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ProductAttribute.php
index b656ff6a06c..13bd9982f2e 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ProductAttribute.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ProductAttribute.php
@@ -5,8 +5,8 @@ declare(strict_types=1);

 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output;

-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class ProductAttribute {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ProductDimensions.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ProductDimensions.php
index 8d5d5ee9b40..882c0f53d33 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ProductDimensions.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ProductDimensions.php
@@ -5,8 +5,8 @@ declare(strict_types=1);

 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output;

-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class ProductDimensions {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ProductImage.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ProductImage.php
index 053fb21c574..deb803e1fd7 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ProductImage.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ProductImage.php
@@ -5,8 +5,8 @@ declare(strict_types=1);

 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output;

-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class ProductImage {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ProductReview.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ProductReview.php
index 112d7698438..f08e6333ca8 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ProductReview.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ProductReview.php
@@ -6,8 +6,8 @@ declare(strict_types=1);
 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output;

 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Scalars\DateTime as DateTimeType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class ProductReview {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ProductVariation.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ProductVariation.php
index cc2b3ff80a3..ab5b0fc94fb 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ProductVariation.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/ProductVariation.php
@@ -15,8 +15,8 @@ use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output\Produc
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Pagination\ProductReviewConnection as ProductReviewConnectionType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Scalars\DateTime as DateTimeType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Interfaces\Product as ProductInterface;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class ProductVariation {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/SelectedAttribute.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/SelectedAttribute.php
index 6ede3d2c5d2..a2aef1d04f6 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/SelectedAttribute.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/SelectedAttribute.php
@@ -5,8 +5,8 @@ declare(strict_types=1);

 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output;

-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class SelectedAttribute {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/SimpleProduct.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/SimpleProduct.php
index a18e5f09016..e663e44c065 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/SimpleProduct.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/SimpleProduct.php
@@ -14,8 +14,8 @@ use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output\Produc
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Pagination\ProductReviewConnection as ProductReviewConnectionType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Scalars\DateTime as DateTimeType;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Interfaces\Product as ProductInterface;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class SimpleProduct {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/VariableProduct.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/VariableProduct.php
index 87b9014f44f..25d362fb225 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/VariableProduct.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Output/VariableProduct.php
@@ -17,8 +17,8 @@ use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Scalars\DateT
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Interfaces\Product as ProductInterface;
 use Automattic\WooCommerce\Api\Pagination\Connection;
 use Automattic\WooCommerce\Internal\Api\Utils;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class VariableProduct {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/CouponConnection.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/CouponConnection.php
index 2db08798e1c..7d568e75e38 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/CouponConnection.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/CouponConnection.php
@@ -7,8 +7,8 @@ declare(strict_types=1);
 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Pagination;

 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output\Coupon as CouponType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class CouponConnection {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/CouponEdge.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/CouponEdge.php
index a9c685ada9b..0972ae84fd6 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/CouponEdge.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/CouponEdge.php
@@ -7,8 +7,8 @@ declare(strict_types=1);
 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Pagination;

 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output\Coupon as CouponType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class CouponEdge {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/PageInfo.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/PageInfo.php
index 7827521503c..20d1e8b594c 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/PageInfo.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/PageInfo.php
@@ -5,8 +5,8 @@ declare(strict_types=1);

 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Pagination;

-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class PageInfo {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductConnection.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductConnection.php
index 52f72836c85..e286e500ffc 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductConnection.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductConnection.php
@@ -7,8 +7,8 @@ declare(strict_types=1);
 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Pagination;

 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Interfaces\Product as ProductType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class ProductConnection {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductEdge.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductEdge.php
index 25509671c2d..c4d4240fd3a 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductEdge.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductEdge.php
@@ -7,8 +7,8 @@ declare(strict_types=1);
 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Pagination;

 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Interfaces\Product as ProductType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class ProductEdge {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductReviewConnection.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductReviewConnection.php
index 343576bdcb6..e9c58f9db01 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductReviewConnection.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductReviewConnection.php
@@ -7,8 +7,8 @@ declare(strict_types=1);
 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Pagination;

 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output\ProductReview as ProductReviewType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class ProductReviewConnection {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductReviewEdge.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductReviewEdge.php
index b2cae95a1ba..db026985df7 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductReviewEdge.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductReviewEdge.php
@@ -7,8 +7,8 @@ declare(strict_types=1);
 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Pagination;

 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output\ProductReview as ProductReviewType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class ProductReviewEdge {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductVariationConnection.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductVariationConnection.php
index 6df67642e82..4ecae409c5d 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductVariationConnection.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductVariationConnection.php
@@ -7,8 +7,8 @@ declare(strict_types=1);
 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Pagination;

 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output\ProductVariation as ProductVariationType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class ProductVariationConnection {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductVariationEdge.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductVariationEdge.php
index 267caeec866..ab412e9c52a 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductVariationEdge.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Pagination/ProductVariationEdge.php
@@ -7,8 +7,8 @@ declare(strict_types=1);
 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Pagination;

 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Output\ProductVariation as ProductVariationType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class ProductVariationEdge {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Scalars/DateTime.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Scalars/DateTime.php
index 419f8fe054a..a606c9d60d0 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Scalars/DateTime.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/GraphQLTypes/Scalars/DateTime.php
@@ -6,7 +6,7 @@ declare(strict_types=1);
 namespace Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLTypes\Scalars;

 use Automattic\WooCommerce\Api\Scalars\DateTime as DateTimeScalar;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\CustomScalarType;
+use Automattic\WooCommerce\Internal\Api\Schema\CustomScalarType;

 class DateTime {
 	private static ?CustomScalarType $instance = null;
@@ -22,18 +22,18 @@ class DateTime {
 						try {
 							return DateTimeScalar::parse( $value );
 						} catch ( \InvalidArgumentException $e ) {
-							throw new \Automattic\WooCommerce\Vendor\GraphQL\Error\Error( $e->getMessage() );
+							throw new \Automattic\WooCommerce\Internal\Api\Schema\Error( $e->getMessage() );
 						}
 					},
 					'parseLiteral' => function ( $value_node, ?array $variables = null ) {
-						if ( $value_node instanceof \Automattic\WooCommerce\Vendor\GraphQL\Language\AST\StringValueNode ) {
+						if ( $value_node instanceof \Automattic\WooCommerce\Internal\Api\Schema\AST\StringValueNode ) {
 							try {
 								return DateTimeScalar::parse( $value_node->value );
 							} catch ( \InvalidArgumentException $e ) {
-								throw new \Automattic\WooCommerce\Vendor\GraphQL\Error\Error( $e->getMessage() );
+								throw new \Automattic\WooCommerce\Internal\Api\Schema\Error( $e->getMessage() );
 							}
 						}
-						throw new \Automattic\WooCommerce\Vendor\GraphQL\Error\Error(
+						throw new \Automattic\WooCommerce\Internal\Api\Schema\Error(
 							'DateTime must be a string, got: ' . $value_node->kind
 						);
 					},
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/RootMutationType.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/RootMutationType.php
index d632029ff23..14d7b282a8a 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/RootMutationType.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/RootMutationType.php
@@ -11,7 +11,7 @@ use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLMutations\DeletePro
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLMutations\DeleteCoupon;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLMutations\CreateCoupon;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLMutations\UpdateCoupon;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;

 class RootMutationType {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/RootQueryType.php b/plugins/woocommerce/src/Internal/Api/Autogenerated/RootQueryType.php
index 3e0872a7d87..92f8cecd7d6 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/RootQueryType.php
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/RootQueryType.php
@@ -9,7 +9,7 @@ use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLQueries\ListProduct
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLQueries\GetProduct;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLQueries\GetCoupon;
 use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLQueries\ListCoupons;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;

 class RootQueryType {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/Autogenerated/api_generation_date.txt b/plugins/woocommerce/src/Internal/Api/Autogenerated/api_generation_date.txt
index ec38f7ba76e..774b4294dea 100644
--- a/plugins/woocommerce/src/Internal/Api/Autogenerated/api_generation_date.txt
+++ b/plugins/woocommerce/src/Internal/Api/Autogenerated/api_generation_date.txt
@@ -1 +1 @@
-2026-04-22T09:37:36+00:00
\ No newline at end of file
+2026-04-24T07:48:05+00:00
\ No newline at end of file
diff --git a/plugins/woocommerce/src/Internal/Api/DesignTime/Scripts/ApiBuilder.php b/plugins/woocommerce/src/Internal/Api/DesignTime/Scripts/ApiBuilder.php
index bf148564fc1..7e6b529c747 100644
--- a/plugins/woocommerce/src/Internal/Api/DesignTime/Scripts/ApiBuilder.php
+++ b/plugins/woocommerce/src/Internal/Api/DesignTime/Scripts/ApiBuilder.php
@@ -21,13 +21,129 @@ use Automattic\WooCommerce\Api\Attributes\Unroll;

 /**
  * Scans the public API classes and generates the GraphQL schema and resolver code.
+ *
+ * The builder is parameterised so the same infrastructure can be reused by
+ * sibling WooCommerce plugins that ship their own code-API classes. Each
+ * caller supplies the input directory/namespace to scan and the output
+ * directory/namespace to generate into. When invoked without arguments the
+ * constructor defaults reproduce WooCommerce core's own configuration, so
+ * core's build pipeline is unaffected.
  */
 class ApiBuilder {
-	private const API_DIR                 = __DIR__ . '/../../../../Api';
-	private const AUTOGENERATED_DIR       = __DIR__ . '/../../Autogenerated';
-	private const TEMPLATES_DIR           = __DIR__ . '/../Templates';
-	private const API_NAMESPACE           = 'Automattic\\WooCommerce\\Api';
-	private const AUTOGENERATED_NAMESPACE = 'Automattic\\WooCommerce\\Internal\\Api\\Autogenerated';
+	private const TEMPLATES_DIR = __DIR__ . '/../Templates';
+
+	private string $api_dir;
+	private string $autogenerated_dir;
+	private string $api_namespace;
+	private string $autogenerated_namespace;
+	private ?string $composer_working_dir;
+	private string $phpcbf_path;
+
+	/**
+	 * @param ?string $api_dir                 Absolute path to the directory containing the code-API classes to scan. Null = WooCommerce core's `src/Api`.
+	 * @param ?string $autogenerated_dir       Absolute path to the directory where generated code will be written. The directory is wiped on each build. Null = WooCommerce core's `src/Internal/Api/Autogenerated`.
+	 * @param ?string $api_namespace           PSR-4 namespace that maps to $api_dir. Null = `Automattic\WooCommerce\Api`.
+	 * @param ?string $autogenerated_namespace PSR-4 namespace that maps to $autogenerated_dir. Null = `Automattic\WooCommerce\Internal\Api\Autogenerated`.
+	 * @param ?string $composer_working_dir    Directory passed to `composer dump-autoload` after generation. Null = skip autoload regeneration (the caller is expected to run it themselves).
+	 * @param ?string $phpcbf_path             Absolute path to a phpcbf executable used to format generated files. Null = use WooCommerce core's vendored phpcbf. To skip linting entirely, pass $skip_linter to build().
+	 */
+	public function __construct(
+		?string $api_dir = null,
+		?string $autogenerated_dir = null,
+		?string $api_namespace = null,
+		?string $autogenerated_namespace = null,
+		?string $composer_working_dir = null,
+		?string $phpcbf_path = null
+	) {
+		// The four path/namespace arguments are all-or-nothing. Allowing any
+		// subset to default back to WooCommerce core's own values would turn
+		// a plugin-side partial configuration into an accidental `wipe` of
+		// core's Autogenerated tree (or into a plugin shipping files under
+		// core's PSR-4 prefix). If any one is provided, all four must be.
+		$provided = array_filter(
+			array( $api_dir, $autogenerated_dir, $api_namespace, $autogenerated_namespace ),
+			static fn( $v ) => null !== $v
+		);
+		if ( count( $provided ) > 0 && count( $provided ) < 4 ) {
+			throw new \InvalidArgumentException(
+				'ApiBuilder: $api_dir, $autogenerated_dir, $api_namespace and $autogenerated_namespace must be provided together (all four or none).'
+			);
+		}
+
+		$this->api_dir                 = $api_dir ?? realpath( __DIR__ . '/../../../../Api' );
+		$this->autogenerated_dir       = $autogenerated_dir ?? realpath( __DIR__ . '/../../Autogenerated' );
+		$this->api_namespace           = $api_namespace ?? 'Automattic\\WooCommerce\\Api';
+		$this->autogenerated_namespace = $autogenerated_namespace ?? 'Automattic\\WooCommerce\\Internal\\Api\\Autogenerated';
+		$this->phpcbf_path             = $phpcbf_path ?? realpath( __DIR__ . '/../../../../..' ) . '/vendor/bin/phpcbf';
+
+		// Default composer working dir to WooCommerce core's plugin dir when no
+		// override is given, so core's existing invocation keeps working. When
+		// an external caller passes --api-dir they presumably drive composer
+		// themselves, so we leave it null and skip the autoload regeneration.
+		if ( null === $api_dir && null === $composer_working_dir ) {
+			$composer_working_dir = realpath( __DIR__ . '/../../../../..' );
+		}
+		$this->composer_working_dir = $composer_working_dir;
+	}
+
+	/**
+	 * Configure an ApiBuilder by convention for a sibling WooCommerce plugin.
+	 *
+	 * The plugin is expected to keep its code-API classes under
+	 * `$plugin_root/src/Api` and emit generated code into
+	 * `$plugin_root/src/Internal/Api/Autogenerated`, with PSR-4 namespaces
+	 * derived from `$namespace_prefix`. Composer dump-autoload runs in
+	 * `$plugin_root`; phpcbf defaults to WooCommerce core's vendored copy
+	 * (callers that want a different style pass --phpcbf-path to the CLI
+	 * instead of using this factory).
+	 *
+	 * @param string $plugin_root      Absolute path to the plugin repository root.
+	 * @param string $namespace_prefix Top-level PSR-4 namespace the plugin publishes under (e.g. 'Automattic\\WooCommerceSimpleMath').
+	 */
+	public static function for_plugin( string $plugin_root, string $namespace_prefix ): self {
+		$namespace_prefix = trim( $namespace_prefix, '\\' );
+		return new self(
+			$plugin_root . '/src/Api',
+			$plugin_root . '/src/Internal/Api/Autogenerated',
+			$namespace_prefix . '\\Api',
+			$namespace_prefix . '\\Internal\\Api\\Autogenerated',
+			$plugin_root
+		);
+	}
+
+	/**
+	 * Turn-key entry point for a sibling plugin's `bin/build-api.php` script.
+	 *
+	 * Given the plugin root and namespace prefix, requires the plugin's own
+	 * composer autoloader, parses `--no-linter` out of `$argv`, configures an
+	 * ApiBuilder via {@see self::for_plugin()}, and runs the build. Writes a
+	 * clear error to STDERR and exits non-zero on the two common failure
+	 * modes (missing plugin autoloader, builder call fails).
+	 *
+	 * Keeps the plugin-side script tiny: after locating WooCommerce and
+	 * requiring WooCommerce's own autoloader, the plugin only needs to call
+	 * this method with its own root path and namespace prefix.
+	 *
+	 * @param string $plugin_root      Absolute path to the plugin repository root.
+	 * @param string $namespace_prefix Top-level PSR-4 namespace the plugin publishes under.
+	 */
+	public static function run_for_plugin( string $plugin_root, string $namespace_prefix ): void {
+		if ( ! is_file( $plugin_root . '/vendor/autoload.php' ) ) {
+			fwrite( STDERR, "Plugin autoloader not found at {$plugin_root}/vendor/autoload.php. Run `composer install` in the plugin root first.\n" );
+			exit( 1 );
+		}
+		require_once $plugin_root . '/vendor/autoload.php';
+
+		$argv        = $GLOBALS['argv'] ?? array();
+		$skip_linter = in_array( '--no-linter', $argv, true );
+
+		try {
+			self::for_plugin( $plugin_root, $namespace_prefix )->build( $skip_linter );
+		} catch ( \Throwable $e ) {
+			fwrite( STDERR, "API build failed: {$e->getMessage()}\n" );
+			exit( 1 );
+		}
+	}

 	/** @var array<string, array{class: \ReflectionClass|\ReflectionEnum, kind: string, ignored: bool}> */
 	private array $classes = array();
@@ -47,6 +163,9 @@ class ApiBuilder {
 	/** @var array<string, string[]> Map of interface trait FQCN => list of output type FQCNs that use it */
 	private array $interface_implementors = array();

+	/** @var ?string Optional FQCN of a user-provided `<api_namespace>\Container` class with a public static `get(string): object` method; null when absent. */
+	private ?string $container_fqcn = null;
+
 	// Counters for summary.
 	private int $query_count      = 0;
 	private int $mutation_count   = 0;
@@ -57,8 +176,9 @@ class ApiBuilder {
 	private int $interface_count  = 0;

 	public function build( bool $skip_linter = false ): void {
-		echo "Scanning src/Api/ for code API classes...\n";
+		echo "Scanning {$this->api_dir} for code API classes...\n";

+		$this->detect_container();
 		$this->discover();
 		$this->validate();

@@ -75,16 +195,16 @@ class ApiBuilder {
 		$this->generate();
 		if ( ! $skip_linter ) {
 			echo "Applying linter to generated files...\n";
-			$this->format_with_phpcbf( self::AUTOGENERATED_DIR );
+			$this->format_with_phpcbf( $this->autogenerated_dir );
 		}
 		$this->write_timestamp();

-		// Regenerate autoloader.
-		$wc_dir = realpath( __DIR__ . '/../../../../..' );
-		echo "Regenerating autoloader...\n";
-		exec( 'composer dump-autoload --working-dir=' . escapeshellarg( $wc_dir ) . ' 2>&1', $output, $code );
-		if ( $code !== 0 ) {
-			echo 'Warning: composer dump-autoload failed: ' . implode( "\n", $output ) . "\n";
+		if ( null !== $this->composer_working_dir ) {
+			echo "Regenerating autoloader...\n";
+			exec( 'composer dump-autoload --working-dir=' . escapeshellarg( $this->composer_working_dir ) . ' 2>&1', $output, $code );
+			if ( $code !== 0 ) {
+				echo 'Warning: composer dump-autoload failed: ' . implode( "\n", $output ) . "\n";
+			}
 		}

 		// Print summary.
@@ -106,13 +226,65 @@ class ApiBuilder {
 		}
 	}

+	// ========================================================================
+	// Container detection
+	// ========================================================================
+
+	/**
+	 * Detect an optional user-provided resolver container.
+	 *
+	 * If a class `<api_namespace>\Container` exists and exposes a `public
+	 * static function get(string): object` method, autogenerated resolvers
+	 * route command lookups through it. Otherwise they fall back to
+	 * `new $command_class()`.
+	 *
+	 * WooCommerce core ships such a container at
+	 * `Automattic\WooCommerce\Api\Container` delegating to `wc_get_container()`.
+	 * Sibling plugins can ship their own with the same signature.
+	 */
+	private function detect_container(): void {
+		$candidate = $this->api_namespace . '\\Container';
+		if ( ! class_exists( $candidate ) ) {
+			return;
+		}
+		if ( ! method_exists( $candidate, 'get' ) ) {
+			$this->warnings[] = "Container class {$candidate} has no get() method; ignoring.";
+			return;
+		}
+		$method = new \ReflectionMethod( $candidate, 'get' );
+		if ( ! $method->isStatic() || ! $method->isPublic() ) {
+			$this->warnings[] = "Container class {$candidate}::get() must be public static; ignoring.";
+			return;
+		}
+
+		// Validate signature `get(string): object`. A wrong signature would pass
+		// the checks above but fail at request time inside the generated
+		// resolvers with a type error far removed from its cause.
+		$params                       = $method->getParameters();
+		$return_type                  = $method->getReturnType();
+		$param_type                   = isset( $params[0] ) ? $params[0]->getType() : null;
+		$single_required_string_param = 1 === count( $params )
+			&& ! $params[0]->isVariadic()
+			&& ! $params[0]->isOptional()
+			&& $param_type instanceof \ReflectionNamedType
+			&& 'string' === $param_type->getName();
+		$object_return                = $return_type instanceof \ReflectionNamedType && 'object' === $return_type->getName();
+		if ( ! $single_required_string_param || ! $object_return ) {
+			$this->warnings[] = "Container class {$candidate}::get() must have signature `get(string): object`; ignoring.";
+			return;
+		}
+
+		$this->container_fqcn = $candidate;
+		echo "  Using resolver container: {$candidate}.\n";
+	}
+
 	// ========================================================================
 	// Discovery
 	// ========================================================================

 	private function discover(): void {
 		$iterator = new \RecursiveIteratorIterator(
-			new \RecursiveDirectoryIterator( self::API_DIR, \FilesystemIterator::SKIP_DOTS )
+			new \RecursiveDirectoryIterator( $this->api_dir, \FilesystemIterator::SKIP_DOTS )
 		);

 		foreach ( $iterator as $file ) {
@@ -176,14 +348,14 @@ class ApiBuilder {
 	}

 	private function file_to_fqcn( string $filepath ): ?string {
-		$rel = str_replace( realpath( self::API_DIR ) . '/', '', realpath( $filepath ) );
+		$rel = str_replace( realpath( $this->api_dir ) . '/', '', realpath( $filepath ) );
 		$rel = str_replace( '.php', '', $rel );
 		$rel = str_replace( '/', '\\', $rel );
-		return self::API_NAMESPACE . '\\' . $rel;
+		return $this->api_namespace . '\\' . $rel;
 	}

 	private function classify_by_namespace( string $fqcn ): ?string {
-		$relative = substr( $fqcn, strlen( self::API_NAMESPACE ) + 1 );
+		$relative = substr( $fqcn, strlen( $this->api_namespace ) + 1 );
 		$parts    = explode( '\\', $relative );
 		$top_dir  = $parts[0];

@@ -197,7 +369,7 @@ class ApiBuilder {
 			'Scalars'    => 'scalar',
 			'Pagination' => 'pagination',
 			'Attributes' => 'attribute',
-			default      => $fqcn === self::API_NAMESPACE . '\\ApiException' ? 'exception' : null,
+			default      => $fqcn === $this->api_namespace . '\\ApiException' ? 'exception' : null,
 		};
 	}

@@ -443,22 +615,22 @@ class ApiBuilder {
 	// ========================================================================

 	private function wipe_autogenerated(): void {
-		if ( is_dir( self::AUTOGENERATED_DIR ) ) {
-			$this->rmdir_recursive( self::AUTOGENERATED_DIR );
+		if ( is_dir( $this->autogenerated_dir ) ) {
+			$this->rmdir_recursive( $this->autogenerated_dir );
 		}
 	}

 	private function create_directory_structure(): void {
 		$dirs = array(
-			self::AUTOGENERATED_DIR,
-			self::AUTOGENERATED_DIR . '/GraphQLTypes/Output',
-			self::AUTOGENERATED_DIR . '/GraphQLTypes/Input',
-			self::AUTOGENERATED_DIR . '/GraphQLTypes/Enums',
-			self::AUTOGENERATED_DIR . '/GraphQLTypes/Interfaces',
-			self::AUTOGENERATED_DIR . '/GraphQLTypes/Scalars',
-			self::AUTOGENERATED_DIR . '/GraphQLTypes/Pagination',
-			self::AUTOGENERATED_DIR . '/GraphQLQueries',
-			self::AUTOGENERATED_DIR . '/GraphQLMutations',
+			$this->autogenerated_dir,
+			$this->autogenerated_dir . '/GraphQLTypes/Output',
+			$this->autogenerated_dir . '/GraphQLTypes/Input',
+			$this->autogenerated_dir . '/GraphQLTypes/Enums',
+			$this->autogenerated_dir . '/GraphQLTypes/Interfaces',
+			$this->autogenerated_dir . '/GraphQLTypes/Scalars',
+			$this->autogenerated_dir . '/GraphQLTypes/Pagination',
+			$this->autogenerated_dir . '/GraphQLQueries',
+			$this->autogenerated_dir . '/GraphQLMutations',
 		);

 		foreach ( $dirs as $dir ) {
@@ -536,6 +708,7 @@ class ApiBuilder {
 		$this->generate_root_query_type( $queries );
 		$this->generate_root_mutation_type( $mutations );
 		$this->generate_type_registry();
+		$this->generate_graphql_controller();
 	}

 	private function discover_connections( array $queries, array $mutations ): void {
@@ -582,7 +755,7 @@ class ApiBuilder {
 		$code = $this->render_template(
 			'EnumTypeTemplate.php',
 			array(
-				'namespace'    => self::AUTOGENERATED_NAMESPACE . '\\GraphQLTypes\\Enums',
+				'namespace'    => $this->autogenerated_namespace . '\\GraphQLTypes\\Enums',
 				'class_name'   => $ref->getShortName(),
 				'graphql_name' => $graphql_name,
 				'description'  => $description,
@@ -592,7 +765,7 @@ class ApiBuilder {
 			)
 		);

-		$path = self::AUTOGENERATED_DIR . '/GraphQLTypes/Enums/' . $ref->getShortName() . '.php';
+		$path = $this->autogenerated_dir . '/GraphQLTypes/Enums/' . $ref->getShortName() . '.php';
 		file_put_contents( $path, $code );
 		++$this->enum_count;
 	}
@@ -607,7 +780,7 @@ class ApiBuilder {
 		$code = $this->render_template(
 			'ScalarTypeTemplate.php',
 			array(
-				'namespace'    => self::AUTOGENERATED_NAMESPACE . '\\GraphQLTypes\\Scalars',
+				'namespace'    => $this->autogenerated_namespace . '\\GraphQLTypes\\Scalars',
 				'class_name'   => $ref->getShortName(),
 				'graphql_name' => $graphql_name,
 				'description'  => $description,
@@ -616,7 +789,7 @@ class ApiBuilder {
 			)
 		);

-		$path = self::AUTOGENERATED_DIR . '/GraphQLTypes/Scalars/' . $ref->getShortName() . '.php';
+		$path = $this->autogenerated_dir . '/GraphQLTypes/Scalars/' . $ref->getShortName() . '.php';
 		file_put_contents( $path, $code );
 		++$this->scalar_count;
 	}
@@ -645,7 +818,7 @@ class ApiBuilder {
 		foreach ( $this->interface_implementors[ $fqcn ] ?? array() as $impl_fqcn ) {
 			$impl_short  = ( new \ReflectionClass( $impl_fqcn ) )->getShortName();
 			$alias       = $impl_short . 'Type';
-			$use_stmts[] = self::AUTOGENERATED_NAMESPACE . "\\GraphQLTypes\\Output\\{$impl_short} as {$alias}";
+			$use_stmts[] = $this->autogenerated_namespace . "\\GraphQLTypes\\Output\\{$impl_short} as {$alias}";
 			$type_map[]  = array(
 				'fqcn'  => $impl_fqcn,
 				'alias' => $alias,
@@ -655,7 +828,7 @@ class ApiBuilder {
 		$code = $this->render_template(
 			'InterfaceTypeTemplate.php',
 			array(
-				'namespace'      => self::AUTOGENERATED_NAMESPACE . '\\GraphQLTypes\\Interfaces',
+				'namespace'      => $this->autogenerated_namespace . '\\GraphQLTypes\\Interfaces',
 				'class_name'     => $ref->getShortName(),
 				'graphql_name'   => $graphql_name,
 				'description'    => $description,
@@ -665,7 +838,7 @@ class ApiBuilder {
 			)
 		);

-		$path = self::AUTOGENERATED_DIR . '/GraphQLTypes/Interfaces/' . $ref->getShortName() . '.php';
+		$path = $this->autogenerated_dir . '/GraphQLTypes/Interfaces/' . $ref->getShortName() . '.php';
 		file_put_contents( $path, $code );
 		++$this->interface_count;
 	}
@@ -697,7 +870,7 @@ class ApiBuilder {
 			if ( $trait_info !== null && $trait_info['kind'] === 'interface' ) {
 				$iface_short  = $trait->getShortName();
 				$alias        = $iface_short . 'Interface';
-				$use_stmts[]  = self::AUTOGENERATED_NAMESPACE . "\\GraphQLTypes\\Interfaces\\{$iface_short} as {$alias}";
+				$use_stmts[]  = $this->autogenerated_namespace . "\\GraphQLTypes\\Interfaces\\{$iface_short} as {$alias}";
 				$interfaces[] = array( 'alias' => $alias );
 			}
 		}
@@ -705,7 +878,7 @@ class ApiBuilder {
 		$code = $this->render_template(
 			'ObjectTypeTemplate.php',
 			array(
-				'namespace'      => self::AUTOGENERATED_NAMESPACE . '\\GraphQLTypes\\Output',
+				'namespace'      => $this->autogenerated_namespace . '\\GraphQLTypes\\Output',
 				'class_name'     => $ref->getShortName(),
 				'graphql_name'   => $graphql_name,
 				'description'    => $description,
@@ -715,7 +888,7 @@ class ApiBuilder {
 			)
 		);

-		$path = self::AUTOGENERATED_DIR . '/GraphQLTypes/Output/' . $ref->getShortName() . '.php';
+		$path = $this->autogenerated_dir . '/GraphQLTypes/Output/' . $ref->getShortName() . '.php';
 		file_put_contents( $path, $code );
 		++$this->type_count;
 	}
@@ -749,7 +922,7 @@ class ApiBuilder {
 		$code = $this->render_template(
 			'InputObjectTypeTemplate.php',
 			array(
-				'namespace'      => self::AUTOGENERATED_NAMESPACE . '\\GraphQLTypes\\Input',
+				'namespace'      => $this->autogenerated_namespace . '\\GraphQLTypes\\Input',
 				'class_name'     => $gen_class_name,
 				'graphql_name'   => $graphql_name,
 				'description'    => $description,
@@ -758,7 +931,7 @@ class ApiBuilder {
 			)
 		);

-		$path = self::AUTOGENERATED_DIR . '/GraphQLTypes/Input/' . $gen_class_name . '.php';
+		$path = $this->autogenerated_dir . '/GraphQLTypes/Input/' . $gen_class_name . '.php';
 		file_put_contents( $path, $code );
 		++$this->input_type_count;
 	}
@@ -900,7 +1073,7 @@ class ApiBuilder {
 		}

 		$dir_name  = $kind === 'query' ? 'GraphQLQueries' : 'GraphQLMutations';
-		$namespace = self::AUTOGENERATED_NAMESPACE . '\\' . $dir_name;
+		$namespace = $this->autogenerated_namespace . '\\' . $dir_name;

 		$code = $this->render_template(
 			'QueryResolverTemplate.php',
@@ -911,6 +1084,7 @@ class ApiBuilder {
 				'description'           => $description,
 				'command_fqcn'          => $fqcn,
 				'command_alias'         => $command_alias,
+				'container_fqcn'        => $this->container_fqcn,
 				'return_type_expr'      => $return_type_expr,
 				'use_statements'        => array_unique( $use_stmts ),
 				'args'                  => $args,
@@ -927,7 +1101,7 @@ class ApiBuilder {
 			)
 		);

-		$path = self::AUTOGENERATED_DIR . "/{$dir_name}/" . $ref->getShortName() . '.php';
+		$path = $this->autogenerated_dir . "/{$dir_name}/" . $ref->getShortName() . '.php';
 		file_put_contents( $path, $code );

 		if ( $kind === 'query' ) {
@@ -1162,12 +1336,12 @@ class ApiBuilder {
 		$node_ref  = $this->classes[ $node_type_fqcn ]['class'] ?? new \ReflectionClass( $node_type_fqcn );
 		$node_name = $node_ref->getShortName();

-		$namespace             = self::AUTOGENERATED_NAMESPACE . '\\GraphQLTypes\\Pagination';
+		$namespace             = $this->autogenerated_namespace . '\\GraphQLTypes\\Pagination';
 		$node_type_class       = $node_name;
 		$node_info             = $this->get_class_info( $node_type_fqcn );
 		$node_type_namespace   = ( null !== $node_info && 'interface' === $node_info['kind'] )
-			? self::AUTOGENERATED_NAMESPACE . '\\GraphQLTypes\\Interfaces'
-			: self::AUTOGENERATED_NAMESPACE . '\\GraphQLTypes\\Output';
+			? $this->autogenerated_namespace . '\\GraphQLTypes\\Interfaces'
+			: $this->autogenerated_namespace . '\\GraphQLTypes\\Output';
 		$node_type_alias       = $node_name . 'Type';
 		$connection_class_name = $node_name . 'Connection';
 		$edge_class_name       = $node_name . 'Edge';
@@ -1175,10 +1349,10 @@ class ApiBuilder {
 		$connection_code = $this->generate_connection_code( $namespace, $node_type_class, $node_type_namespace, $node_type_alias, $connection_class_name, $edge_class_name );
 		$edge_code       = $this->generate_edge_code( $namespace, $node_type_class, $node_type_namespace, $node_type_alias, $edge_class_name );

-		$connection_path = self::AUTOGENERATED_DIR . '/GraphQLTypes/Pagination/' . $connection_class_name . '.php';
+		$connection_path = $this->autogenerated_dir . '/GraphQLTypes/Pagination/' . $connection_class_name . '.php';
 		file_put_contents( $connection_path, $connection_code );

-		$edge_path = self::AUTOGENERATED_DIR . '/GraphQLTypes/Pagination/' . $edge_class_name . '.php';
+		$edge_path = $this->autogenerated_dir . '/GraphQLTypes/Pagination/' . $edge_class_name . '.php';
 		file_put_contents( $edge_path, $edge_code );
 	}

@@ -1188,8 +1362,8 @@ class ApiBuilder {
 		$code .= "// THIS FILE IS AUTO-GENERATED. DO NOT EDIT MANUALLY.\n\n";
 		$code .= "namespace {$namespace};\n\n";
 		$code .= "use {$node_type_namespace}\\{$node_type_class} as {$node_type_alias};\n";
-		$code .= "use Automattic\\WooCommerce\\Vendor\\GraphQL\\Type\\Definition\\ObjectType;\n";
-		$code .= "use Automattic\\WooCommerce\\Vendor\\GraphQL\\Type\\Definition\\Type;\n\n";
+		$code .= "use Automattic\\WooCommerce\\Internal\\Api\\Schema\\ObjectType;\n";
+		$code .= "use Automattic\\WooCommerce\\Internal\\Api\\Schema\\Type;\n\n";
 		$code .= "class {$connection_class_name} {\n";
 		$code .= "\tprivate static ?ObjectType \$instance = null;\n\n";
 		$code .= "\tpublic static function get(): ObjectType {\n";
@@ -1232,8 +1406,8 @@ class ApiBuilder {
 		$code .= "// THIS FILE IS AUTO-GENERATED. DO NOT EDIT MANUALLY.\n\n";
 		$code .= "namespace {$namespace};\n\n";
 		$code .= "use {$node_type_namespace}\\{$node_type_class} as {$node_type_alias};\n";
-		$code .= "use Automattic\\WooCommerce\\Vendor\\GraphQL\\Type\\Definition\\ObjectType;\n";
-		$code .= "use Automattic\\WooCommerce\\Vendor\\GraphQL\\Type\\Definition\\Type;\n\n";
+		$code .= "use Automattic\\WooCommerce\\Internal\\Api\\Schema\\ObjectType;\n";
+		$code .= "use Automattic\\WooCommerce\\Internal\\Api\\Schema\\Type;\n\n";
 		$code .= "class {$edge_class_name} {\n";
 		$code .= "\tprivate static ?ObjectType \$instance = null;\n\n";
 		$code .= "\tpublic static function get(): ObjectType {\n";
@@ -1263,11 +1437,11 @@ class ApiBuilder {
 		$code = $this->render_template(
 			'PageInfoTypeTemplate.php',
 			array(
-				'namespace' => self::AUTOGENERATED_NAMESPACE . '\\GraphQLTypes\\Pagination',
+				'namespace' => $this->autogenerated_namespace . '\\GraphQLTypes\\Pagination',
 			)
 		);

-		$path = self::AUTOGENERATED_DIR . '/GraphQLTypes/Pagination/PageInfo.php';
+		$path = $this->autogenerated_dir . '/GraphQLTypes/Pagination/PageInfo.php';
 		file_put_contents( $path, $code );
 	}

@@ -1278,7 +1452,7 @@ class ApiBuilder {
 		foreach ( $queries as $fqcn => $ref ) {
 			$query_data[] = array(
 				'class_name'   => $ref->getShortName(),
-				'fqcn'         => self::AUTOGENERATED_NAMESPACE . '\\GraphQLQueries\\' . $ref->getShortName(),
+				'fqcn'         => $this->autogenerated_namespace . '\\GraphQLQueries\\' . $ref->getShortName(),
 				'graphql_name' => $this->root_field_name( $fqcn, $ref ),
 			);
 		}
@@ -1286,12 +1460,12 @@ class ApiBuilder {
 		$code = $this->render_template(
 			'RootQueryTypeTemplate.php',
 			array(
-				'namespace' => self::AUTOGENERATED_NAMESPACE,
+				'namespace' => $this->autogenerated_namespace,
 				'queries'   => $query_data,
 			)
 		);

-		$path = self::AUTOGENERATED_DIR . '/RootQueryType.php';
+		$path = $this->autogenerated_dir . '/RootQueryType.php';
 		file_put_contents( $path, $code );
 	}

@@ -1300,7 +1474,7 @@ class ApiBuilder {
 		foreach ( $mutations as $fqcn => $ref ) {
 			$mutation_data[] = array(
 				'class_name'   => $ref->getShortName(),
-				'fqcn'         => self::AUTOGENERATED_NAMESPACE . '\\GraphQLMutations\\' . $ref->getShortName(),
+				'fqcn'         => $this->autogenerated_namespace . '\\GraphQLMutations\\' . $ref->getShortName(),
 				'graphql_name' => $this->root_field_name( $fqcn, $ref ),
 			);
 		}
@@ -1308,12 +1482,12 @@ class ApiBuilder {
 		$code = $this->render_template(
 			'RootMutationTypeTemplate.php',
 			array(
-				'namespace' => self::AUTOGENERATED_NAMESPACE,
+				'namespace' => $this->autogenerated_namespace,
 				'mutations' => $mutation_data,
 			)
 		);

-		$path = self::AUTOGENERATED_DIR . '/RootMutationType.php';
+		$path = $this->autogenerated_dir . '/RootMutationType.php';
 		file_put_contents( $path, $code );
 	}

@@ -1324,7 +1498,7 @@ class ApiBuilder {
 				$short   = ( new \ReflectionClass( $impl_fqcn ) )->getShortName();
 				$types[] = array(
 					'short_name' => $short,
-					'fqcn'       => self::AUTOGENERATED_NAMESPACE . "\\GraphQLTypes\\Output\\{$short}",
+					'fqcn'       => $this->autogenerated_namespace . "\\GraphQLTypes\\Output\\{$short}",
 				);
 			}
 		}
@@ -1332,12 +1506,24 @@ class ApiBuilder {
 		$code = $this->render_template(
 			'TypeRegistryTemplate.php',
 			array(
-				'namespace' => self::AUTOGENERATED_NAMESPACE,
+				'namespace' => $this->autogenerated_namespace,
 				'types'     => $types,
 			)
 		);

-		$path = self::AUTOGENERATED_DIR . '/TypeRegistry.php';
+		$path = $this->autogenerated_dir . '/TypeRegistry.php';
+		file_put_contents( $path, $code );
+	}
+
+	private function generate_graphql_controller(): void {
+		$code = $this->render_template(
+			'GraphQLControllerTemplate.php',
+			array(
+				'namespace' => $this->autogenerated_namespace,
+			)
+		);
+
+		$path = $this->autogenerated_dir . '/GraphQLController.php';
 		file_put_contents( $path, $code );
 	}

@@ -1347,7 +1533,7 @@ class ApiBuilder {

 	private function write_timestamp(): void {
 		file_put_contents(
-			self::AUTOGENERATED_DIR . '/api_generation_date.txt',
+			$this->autogenerated_dir . '/api_generation_date.txt',
 			gmdate( 'c' )
 		);
 	}
@@ -1457,7 +1643,7 @@ class ApiBuilder {
 			$scalar_class = $scalar_attr[0]->newInstance()->type;
 			$scalar_short = ( new \ReflectionClass( $scalar_class ) )->getShortName();
 			$alias        = $scalar_short . 'Type';
-			$use_stmts[]  = self::AUTOGENERATED_NAMESPACE . "\\GraphQLTypes\\Scalars\\{$scalar_short} as {$alias}";
+			$use_stmts[]  = $this->autogenerated_namespace . "\\GraphQLTypes\\Scalars\\{$scalar_short} as {$alias}";
 			$expr         = "{$alias}::get()";
 			return $nullable ? $expr : "Type::nonNull({$expr})";
 		}
@@ -1477,7 +1663,7 @@ class ApiBuilder {
 			$node_type   = $conn_attr[0]->newInstance()->type;
 			$node_short  = ( new \ReflectionClass( $node_type ) )->getShortName();
 			$conn_alias  = $node_short . 'ConnectionType';
-			$use_stmts[] = self::AUTOGENERATED_NAMESPACE . "\\GraphQLTypes\\Pagination\\{$node_short}Connection as {$conn_alias}";
+			$use_stmts[] = $this->autogenerated_namespace . "\\GraphQLTypes\\Pagination\\{$node_short}Connection as {$conn_alias}";

 			// Register the connection for generation.
 			$this->connections[ $node_type ] = array(
@@ -1508,15 +1694,15 @@ class ApiBuilder {
 			$short = ( new \ReflectionClass( $type_name ) )->getShortName();
 			if ( $class_info['kind'] === 'enum' ) {
 				$alias       = $short . 'Type';
-				$use_stmts[] = self::AUTOGENERATED_NAMESPACE . "\\GraphQLTypes\\Enums\\{$short} as {$alias}";
+				$use_stmts[] = $this->autogenerated_namespace . "\\GraphQLTypes\\Enums\\{$short} as {$alias}";
 				$expr        = "{$alias}::get()";
 			} elseif ( $class_info['kind'] === 'type' ) {
-				$use_stmts[] = self::AUTOGENERATED_NAMESPACE . "\\GraphQLTypes\\Output\\{$short}";
+				$use_stmts[] = $this->autogenerated_namespace . "\\GraphQLTypes\\Output\\{$short}";
 				$expr        = "{$short}::get()";
 			} elseif ( $class_info['kind'] === 'input_type' ) {
 				$gen_name    = str_ends_with( $short, 'Input' ) ? substr( $short, 0, -5 ) : $short;
 				$alias       = $gen_name . 'Input';
-				$use_stmts[] = self::AUTOGENERATED_NAMESPACE . "\\GraphQLTypes\\Input\\{$gen_name} as {$alias}";
+				$use_stmts[] = $this->autogenerated_namespace . "\\GraphQLTypes\\Input\\{$gen_name} as {$alias}";
 				$expr        = "{$alias}::get()";
 			} else {
 				$expr = 'Type::string()'; // Fallback.
@@ -1552,11 +1738,11 @@ class ApiBuilder {
 		return match ( $class_info['kind'] ) {
 			'enum' => ( function () use ( $short, &$use_stmts ) {
 				$alias       = $short . 'Type';
-				$use_stmts[] = self::AUTOGENERATED_NAMESPACE . "\\GraphQLTypes\\Enums\\{$short} as {$alias}";
+				$use_stmts[] = $this->autogenerated_namespace . "\\GraphQLTypes\\Enums\\{$short} as {$alias}";
 				return "{$alias}::get()";
 			} )(),
 			'type' => ( function () use ( $short, &$use_stmts ) {
-				$use_stmts[] = self::AUTOGENERATED_NAMESPACE . "\\GraphQLTypes\\Output\\{$short}";
+				$use_stmts[] = $this->autogenerated_namespace . "\\GraphQLTypes\\Output\\{$short}";
 				return "{$short}::get()";
 			} )(),
 			default => 'Type::string()',
@@ -1600,7 +1786,7 @@ class ApiBuilder {
 			if ( null !== $iface_info && 'interface' === $iface_info['kind'] ) {
 				$iface_short = ( new \ReflectionClass( $iface_class ) )->getShortName();
 				$alias       = $iface_short . 'Interface';
-				$use_stmts[] = self::AUTOGENERATED_NAMESPACE . "\\GraphQLTypes\\Interfaces\\{$iface_short} as {$alias}";
+				$use_stmts[] = $this->autogenerated_namespace . "\\GraphQLTypes\\Interfaces\\{$iface_short} as {$alias}";
 				$expr        = "{$alias}::get()";
 				return $nullable ? $expr : "Type::nonNull({$expr})";
 			}
@@ -1612,7 +1798,7 @@ class ApiBuilder {
 			$node_type   = $conn_attr[0]->newInstance()->type;
 			$node_short  = ( new \ReflectionClass( $node_type ) )->getShortName();
 			$conn_alias  = $node_short . 'ConnectionType';
-			$use_stmts[] = self::AUTOGENERATED_NAMESPACE . "\\GraphQLTypes\\Pagination\\{$node_short}Connection as {$conn_alias}";
+			$use_stmts[] = $this->autogenerated_namespace . "\\GraphQLTypes\\Pagination\\{$node_short}Connection as {$conn_alias}";

 			$this->connections[ $node_type ] = array(
 				'node_type' => $node_type,
@@ -1628,7 +1814,7 @@ class ApiBuilder {
 		if ( $class_info !== null && $class_info['kind'] === 'type' ) {
 			$short       = ( new \ReflectionClass( $type_name ) )->getShortName();
 			$alias       = $short . 'Type';
-			$use_stmts[] = self::AUTOGENERATED_NAMESPACE . "\\GraphQLTypes\\Output\\{$short} as {$alias}";
+			$use_stmts[] = $this->autogenerated_namespace . "\\GraphQLTypes\\Output\\{$short} as {$alias}";
 			$expr        = "{$alias}::get()";
 			return $nullable ? $expr : "Type::nonNull({$expr})";
 		}
@@ -1675,9 +1861,7 @@ class ApiBuilder {
 	}

 	private function format_with_phpcbf( string $file_path ): void {
-		$wc_dir = realpath( __DIR__ . '/../../../../..' );
-		$phpcbf = $wc_dir . '/vendor/bin/phpcbf';
-		exec( $phpcbf . ' -q ' . escapeshellarg( $file_path ) . ' 2>&1' );
+		exec( escapeshellarg( $this->phpcbf_path ) . ' -q ' . escapeshellarg( $file_path ) . ' 2>&1' );
 	}

 	private function rmdir_recursive( string $dir ): void {
diff --git a/plugins/woocommerce/src/Internal/Api/DesignTime/Scripts/build-api.php b/plugins/woocommerce/src/Internal/Api/DesignTime/Scripts/build-api.php
index a3ed5eddd88..e1cc261de0c 100644
--- a/plugins/woocommerce/src/Internal/Api/DesignTime/Scripts/build-api.php
+++ b/plugins/woocommerce/src/Internal/Api/DesignTime/Scripts/build-api.php
@@ -10,6 +10,69 @@ if ( PHP_SAPI !== 'cli' ) {
 	exit;
 }

+$options = getopt(
+	'h',
+	array(
+		'help',
+		'no-linter',
+		'api-dir:',
+		'autogen-dir:',
+		'api-namespace:',
+		'autogen-namespace:',
+		'composer-working-dir:',
+		'phpcbf-path:',
+	)
+);
+
+if ( isset( $options['h'] ) || isset( $options['help'] ) ) {
+	echo <<<'TXT'
+Usage: php build-api.php [options]
+
+When invoked without options the script regenerates WooCommerce core's
+GraphQL API. Sibling WooCommerce plugins that ship their own code-API
+classes can reuse the same script by passing all four --*-dir / --*-namespace
+flags to point at their own source and output trees.
+
+Options:
+  --api-dir=PATH              Directory containing code-API classes to scan.
+                              Default: WooCommerce core's src/Api.
+  --autogen-dir=PATH          Directory where generated code is written.
+                              WARNING: this directory is wiped on every run.
+                              Default: WooCommerce core's src/Internal/Api/Autogenerated.
+  --api-namespace=NAMESPACE   PSR-4 namespace that maps to --api-dir.
+                              Default: Automattic\WooCommerce\Api.
+  --autogen-namespace=NS      PSR-4 namespace that maps to --autogen-dir.
+                              Default: Automattic\WooCommerce\Internal\Api\Autogenerated.
+  --composer-working-dir=DIR  Directory passed to "composer dump-autoload"
+                              after generation. When --api-dir is NOT given
+                              (core build) this defaults to WooCommerce's
+                              plugin directory; when --api-dir IS given
+                              (plugin build) omission skips autoload
+                              regeneration so the plugin wrapper can drive
+                              composer itself.
+  --phpcbf-path=PATH          Path to a phpcbf executable used to format
+                              generated files.
+                              Default: WooCommerce's vendored phpcbf.
+  --no-linter                 Skip the phpcbf pass entirely.
+  -h, --help                  Show this message.
+
+TXT;
+	exit( 0 );
+}
+
+// Enforce the "all or nothing" rule for the four core path/namespace flags:
+// mixing them (e.g. passing --api-dir but leaving --autogen-namespace at
+// core's default) would silently emit files into WooCommerce's own namespace
+// from an external plugin's source tree, which is almost certainly a mistake.
+$path_flags     = array( 'api-dir', 'autogen-dir', 'api-namespace', 'autogen-namespace' );
+$provided_paths = array_filter( $path_flags, static fn( $f ) => isset( $options[ $f ] ) );
+if ( count( $provided_paths ) > 0 && count( $provided_paths ) < count( $path_flags ) ) {
+	$missing = array_diff( $path_flags, $provided_paths );
+	fwrite( STDERR, "Error: when overriding any of --api-dir / --autogen-dir / --api-namespace / --autogen-namespace, all four must be provided.\n" );
+	fwrite( STDERR, 'Missing: --' . implode( ', --', $missing ) . "\n" );
+	exit( 2 );
+}
+
 if ( PHP_VERSION_ID < 80100 ) {
 	fwrite(
 		STDERR,
@@ -25,7 +88,14 @@ require_once __DIR__ . '/../../../../../vendor/autoload.php';

 use Automattic\WooCommerce\Internal\Api\DesignTime\Scripts\ApiBuilder;

-$skip_linter = in_array( '--no-linter', $argv, true );
+$skip_linter = isset( $options['no-linter'] );

-$builder = new ApiBuilder();
+$builder = new ApiBuilder(
+	$options['api-dir'] ?? null,
+	$options['autogen-dir'] ?? null,
+	$options['api-namespace'] ?? null,
+	$options['autogen-namespace'] ?? null,
+	$options['composer-working-dir'] ?? null,
+	$options['phpcbf-path'] ?? null,
+);
 $builder->build( $skip_linter );
diff --git a/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/EnumTypeTemplate.php b/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/EnumTypeTemplate.php
index a9f4d597fd8..ef99bdb95cf 100644
--- a/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/EnumTypeTemplate.php
+++ b/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/EnumTypeTemplate.php
@@ -22,7 +22,7 @@ declare(strict_types=1);
 namespace <?php echo $namespace; ?>;

 use <?php echo $enum_fqcn; ?> as <?php echo $enum_alias; ?>;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\EnumType;
+use Automattic\WooCommerce\Internal\Api\Schema\EnumType;

 class <?php echo $class_name; ?> {
 	private static ?EnumType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/GraphQLControllerTemplate.php b/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/GraphQLControllerTemplate.php
new file mode 100644
index 00000000000..04c36587b22
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/GraphQLControllerTemplate.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Template for generating the GraphQLController subclass.
+ *
+ * Emitted by ApiBuilder. The generated class extends the hand-written
+ * core controller in Automattic\WooCommerce\Internal\Api and overrides
+ * build_schema() to reference the root types that ApiBuilder has just
+ * generated in the same autogenerated namespace.
+ *
+ * @var string $namespace
+ */
+?>
+<?php echo '<?php'; ?>
+
+declare(strict_types=1);
+
+// THIS FILE IS AUTO-GENERATED. DO NOT EDIT MANUALLY.
+
+namespace <?php echo $namespace; ?>;
+
+use Automattic\WooCommerce\Internal\Api\Schema\Schema;
+
+class GraphQLController extends \Automattic\WooCommerce\Internal\Api\GraphQLController {
+	protected function build_schema(): Schema {
+		return new Schema(
+			array(
+				'query'    => RootQueryType::get(),
+				'mutation' => RootMutationType::get(),
+				'types'    => TypeRegistry::get_interface_implementors(),
+			)
+		);
+	}
+}
diff --git a/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/InputObjectTypeTemplate.php b/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/InputObjectTypeTemplate.php
index a406f488a61..6ee83867b05 100644
--- a/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/InputObjectTypeTemplate.php
+++ b/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/InputObjectTypeTemplate.php
@@ -20,11 +20,36 @@ declare(strict_types=1);

 namespace <?php echo $namespace; ?>;

+<?php
+// Drop any caller-supplied import whose effective short name would collide
+// with one of the hardcoded imports emitted below, otherwise the generated
+// file wouldn't compile ("Cannot use ... because the name is already in use").
+$reserved_short_names = array( 'InputObjectType', 'Type' );
+// PHP class-name resolution (including `use`) is case-insensitive, so the
+// collision check has to be too — a caller-supplied `Foo\type` would
+// otherwise slip past and fail at compile time of the generated file.
+$reserved_short_names_lower = array_map( 'strtolower', $reserved_short_names );
+$use_statements             = array_values(
+	array_filter(
+		$use_statements,
+		static function ( $use ) use ( $reserved_short_names_lower ) {
+			$as_pos = stripos( $use, ' as ' );
+			if ( false !== $as_pos ) {
+				$short = trim( substr( $use, $as_pos + 4 ) );
+			} else {
+				$sep_pos = strrpos( $use, '\\' );
+				$short   = false !== $sep_pos ? substr( $use, $sep_pos + 1 ) : $use;
+			}
+			return ! in_array( strtolower( $short ), $reserved_short_names_lower, true );
+		}
+	)
+);
+?>
 <?php foreach ( $use_statements as $use ) : ?>
 use <?php echo $use; ?>;
 <?php endforeach; ?>
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\InputObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\InputObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class <?php echo $class_name; ?> {
 	private static ?InputObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/InterfaceTypeTemplate.php b/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/InterfaceTypeTemplate.php
index 37351291f0e..ae4987c820f 100644
--- a/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/InterfaceTypeTemplate.php
+++ b/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/InterfaceTypeTemplate.php
@@ -21,11 +21,36 @@ declare(strict_types=1);

 namespace <?php echo $namespace; ?>;

+<?php
+// Drop any caller-supplied import whose effective short name would collide
+// with one of the hardcoded imports emitted below, otherwise the generated
+// file wouldn't compile ("Cannot use ... because the name is already in use").
+$reserved_short_names = array( 'InterfaceType', 'Type' );
+// PHP class-name resolution (including `use`) is case-insensitive, so the
+// collision check has to be too — a caller-supplied `Foo\type` would
+// otherwise slip past and fail at compile time of the generated file.
+$reserved_short_names_lower = array_map( 'strtolower', $reserved_short_names );
+$use_statements             = array_values(
+	array_filter(
+		$use_statements,
+		static function ( $use ) use ( $reserved_short_names_lower ) {
+			$as_pos = stripos( $use, ' as ' );
+			if ( false !== $as_pos ) {
+				$short = trim( substr( $use, $as_pos + 4 ) );
+			} else {
+				$sep_pos = strrpos( $use, '\\' );
+				$short   = false !== $sep_pos ? substr( $use, $sep_pos + 1 ) : $use;
+			}
+			return ! in_array( strtolower( $short ), $reserved_short_names_lower, true );
+		}
+	)
+);
+?>
 <?php foreach ( $use_statements as $use ) : ?>
 use <?php echo $use; ?>;
 <?php endforeach; ?>
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\InterfaceType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\InterfaceType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class <?php echo $class_name; ?> {
 	private static ?InterfaceType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/ObjectTypeTemplate.php b/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/ObjectTypeTemplate.php
index c6c954fbf68..89692d0e473 100644
--- a/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/ObjectTypeTemplate.php
+++ b/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/ObjectTypeTemplate.php
@@ -29,6 +29,33 @@ foreach ( $fields as $f ) {
 		break;
 	}
 }
+// Drop any caller-supplied import whose effective short name would collide
+// with one of the hardcoded imports emitted below, otherwise the generated
+// file wouldn't compile ("Cannot use ... because the name is already in use").
+$reserved_short_names = array( 'ObjectType', 'Type' );
+if ( $has_paginated_connection ) {
+	$reserved_short_names[] = 'Connection';
+	$reserved_short_names[] = 'Utils';
+}
+// PHP class-name resolution (including `use`) is case-insensitive, so the
+// collision check has to be too — a caller-supplied `Foo\resolveinfo` would
+// otherwise slip past and fail at compile time of the generated file.
+$reserved_short_names_lower = array_map( 'strtolower', $reserved_short_names );
+$use_statements             = array_values(
+	array_filter(
+		$use_statements,
+		static function ( $use ) use ( $reserved_short_names_lower ) {
+			$as_pos = stripos( $use, ' as ' );
+			if ( false !== $as_pos ) {
+				$short = trim( substr( $use, $as_pos + 4 ) );
+			} else {
+				$sep_pos = strrpos( $use, '\\' );
+				$short   = false !== $sep_pos ? substr( $use, $sep_pos + 1 ) : $use;
+			}
+			return ! in_array( strtolower( $short ), $reserved_short_names_lower, true );
+		}
+	)
+);
 ?>
 <?php foreach ( $use_statements as $use ) : ?>
 use <?php echo $use; ?>;
@@ -37,8 +64,8 @@ use <?php echo $use; ?>;
 use Automattic\WooCommerce\Api\Pagination\Connection;
 use Automattic\WooCommerce\Internal\Api\Utils;
 <?php endif; ?>
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class <?php echo $class_name; ?> {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/PageInfoTypeTemplate.php b/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/PageInfoTypeTemplate.php
index 6b14497f119..53c344ebe41 100644
--- a/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/PageInfoTypeTemplate.php
+++ b/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/PageInfoTypeTemplate.php
@@ -13,8 +13,8 @@ declare(strict_types=1);

 namespace <?php echo $namespace; ?>;

-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class PageInfo {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/QueryResolverTemplate.php b/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/QueryResolverTemplate.php
index dd228569dad..9a974d8d047 100644
--- a/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/QueryResolverTemplate.php
+++ b/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/QueryResolverTemplate.php
@@ -20,7 +20,8 @@
  * @var ?array $authorize_param_names - if non-null, the authorize() method param names (subset of execute params)
  * @var bool   $has_preauthorized - true when authorize() declares a bool $_preauthorized infrastructure param
  * @var string $preauthorized_expr - PHP expression that evaluates to the $_preauthorized bool at runtime
- * @var bool   $scalar_return - true when execute() returns a scalar (bool, int, float, string)
+ * @var bool    $scalar_return - true when execute() returns a scalar (bool, int, float, string)
+ * @var ?string $container_fqcn - FQCN of a user-provided container with static get(string): object; null for direct `new` instantiation
  */

 $escaped_description = addslashes( $description );
@@ -38,17 +39,43 @@ namespace <?php echo $namespace; ?>;
 use <?php echo $command_fqcn; ?> as <?php echo $command_alias; ?>;
 use Automattic\WooCommerce\Internal\Api\QueryInfoExtractor;
 use Automattic\WooCommerce\Internal\Api\Utils;
+<?php
+// Drop any caller-supplied import whose effective short name would collide
+// with one of the imports emitted unconditionally above and below, otherwise
+// the generated file would fail to compile ("Cannot use ... because the name
+// is already in use").
+$reserved_short_names = array( $command_alias, 'QueryInfoExtractor', 'Utils', 'ResolveInfo', 'Type' );
+// PHP class-name resolution (including `use`) is case-insensitive, so the
+// collision check has to be too — a caller-supplied `Foo\resolveinfo` would
+// otherwise slip past and fail at compile time of the generated file.
+$reserved_short_names_lower = array_map( 'strtolower', $reserved_short_names );
+$use_statements             = array_values(
+	array_filter(
+		$use_statements,
+		static function ( $use ) use ( $reserved_short_names_lower ) {
+			$as_pos = stripos( $use, ' as ' );
+			if ( false !== $as_pos ) {
+				$short = trim( substr( $use, $as_pos + 4 ) );
+			} else {
+				$sep_pos = strrpos( $use, '\\' );
+				$short   = false !== $sep_pos ? substr( $use, $sep_pos + 1 ) : $use;
+			}
+			return ! in_array( strtolower( $short ), $reserved_short_names_lower, true );
+		}
+	)
+);
+?>
 <?php foreach ( $use_statements as $use ) : ?>
 use <?php echo $use; ?>;
 <?php endforeach; ?>
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ResolveInfo;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
+use Automattic\WooCommerce\Internal\Api\Schema\ResolveInfo;
+use Automattic\WooCommerce\Internal\Api\Schema\Type;

 class <?php echo $class_name; ?> {
 	public static function get_field_definition(): array {
 		return array(
 <?php if ( $scalar_return ) : ?>
-			'type' => Type::nonNull(new \Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType(array(
+			'type' => Type::nonNull(new \Automattic\WooCommerce\Internal\Api\Schema\ObjectType(array(
 				'name' => '<?php echo $class_name; ?>Result',
 				'fields' => array(
 					'result' => array( 'type' => <?php echo $return_type_expr; ?> ),
@@ -87,7 +114,11 @@ class <?php echo $class_name; ?> {
 <?php endforeach; ?>

 <?php endif; ?>
-		$command = wc_get_container()->get( <?php echo $command_alias; ?>::class );
+<?php if ( null !== $container_fqcn ) : ?>
+		$command = \<?php echo $container_fqcn; ?>::get( <?php echo $command_alias; ?>::class );
+<?php else : ?>
+		$command = new <?php echo $command_alias; ?>();
+<?php endif; ?>

 		$execute_args = array();
 <?php
@@ -126,7 +157,7 @@ foreach ( $execute_params as $param ) :
 			'_preauthorized' => <?php echo $preauthorized_expr; ?>,
 <?php endif; ?>
 		) ) ) {
-			throw new \Automattic\WooCommerce\Vendor\GraphQL\Error\Error(
+			throw new \Automattic\WooCommerce\Internal\Api\Schema\Error(
 				'You do not have permission to perform this action.',
 				extensions: array( 'code' => 'UNAUTHORIZED' )
 			);
diff --git a/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/RootMutationTypeTemplate.php b/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/RootMutationTypeTemplate.php
index d50d44265b3..d0ed1702515 100644
--- a/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/RootMutationTypeTemplate.php
+++ b/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/RootMutationTypeTemplate.php
@@ -17,7 +17,7 @@ namespace <?php echo $namespace; ?>;
 <?php foreach ( $mutations as $mutation ) : ?>
 use <?php echo $mutation['fqcn']; ?>;
 <?php endforeach; ?>
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;

 class RootMutationType {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/RootQueryTypeTemplate.php b/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/RootQueryTypeTemplate.php
index 7c4f402569c..fea9414ed2c 100644
--- a/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/RootQueryTypeTemplate.php
+++ b/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/RootQueryTypeTemplate.php
@@ -17,7 +17,7 @@ namespace <?php echo $namespace; ?>;
 <?php foreach ( $queries as $query ) : ?>
 use <?php echo $query['fqcn']; ?>;
 <?php endforeach; ?>
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType;
+use Automattic\WooCommerce\Internal\Api\Schema\ObjectType;

 class RootQueryType {
 	private static ?ObjectType $instance = null;
diff --git a/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/ScalarTypeTemplate.php b/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/ScalarTypeTemplate.php
index c4677f2fd03..58a4bfdac90 100644
--- a/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/ScalarTypeTemplate.php
+++ b/plugins/woocommerce/src/Internal/Api/DesignTime/Templates/ScalarTypeTemplate.php
@@ -21,7 +21,7 @@ declare(strict_types=1);
 namespace <?php echo $namespace; ?>;

 use <?php echo $scalar_fqcn; ?> as <?php echo $scalar_alias; ?>;
-use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\CustomScalarType;
+use Automattic\WooCommerce\Internal\Api\Schema\CustomScalarType;

 class <?php echo $class_name; ?> {
 	private static ?CustomScalarType $instance = null;
@@ -39,18 +39,18 @@ class <?php echo $class_name; ?> {
 						try {
 							return <?php echo $scalar_alias; ?>::parse( $value );
 						} catch ( \InvalidArgumentException $e ) {
-							throw new \Automattic\WooCommerce\Vendor\GraphQL\Error\Error( $e->getMessage() );
+							throw new \Automattic\WooCommerce\Internal\Api\Schema\Error( $e->getMessage() );
 						}
 					},
 					'parseLiteral' => function ( $value_node, ?array $variables = null ) {
-						if ( $value_node instanceof \Automattic\WooCommerce\Vendor\GraphQL\Language\AST\StringValueNode ) {
+						if ( $value_node instanceof \Automattic\WooCommerce\Internal\Api\Schema\AST\StringValueNode ) {
 							try {
 								return <?php echo $scalar_alias; ?>::parse( $value_node->value );
 							} catch ( \InvalidArgumentException $e ) {
-								throw new \Automattic\WooCommerce\Vendor\GraphQL\Error\Error( $e->getMessage() );
+								throw new \Automattic\WooCommerce\Internal\Api\Schema\Error( $e->getMessage() );
 							}
 						}
-						throw new \Automattic\WooCommerce\Vendor\GraphQL\Error\Error(
+						throw new \Automattic\WooCommerce\Internal\Api\Schema\Error(
 							'<?php echo $graphql_name; ?> must be a string, got: ' . $value_node->kind
 						);
 					},
diff --git a/plugins/woocommerce/src/Internal/Api/GraphQLController.php b/plugins/woocommerce/src/Internal/Api/GraphQLController.php
index 16eb3a13ce9..325733fa4be 100644
--- a/plugins/woocommerce/src/Internal/Api/GraphQLController.php
+++ b/plugins/woocommerce/src/Internal/Api/GraphQLController.php
@@ -5,9 +5,6 @@ declare(strict_types=1);
 namespace Automattic\WooCommerce\Internal\Api;

 use Automattic\WooCommerce\Api\ApiException;
-use Automattic\WooCommerce\Internal\Api\Autogenerated\RootQueryType;
-use Automattic\WooCommerce\Internal\Api\Autogenerated\RootMutationType;
-use Automattic\WooCommerce\Internal\Api\Autogenerated\TypeRegistry;
 use Automattic\WooCommerce\Vendor\GraphQL\GraphQL;
 use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\DocumentNode;
 use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\FieldNode;
@@ -24,7 +21,7 @@ use Automattic\WooCommerce\Vendor\GraphQL\Validator\Rules\QueryDepth;
 /**
  * Handles incoming GraphQL requests over the WooCommerce REST API.
  */
-class GraphQLController {
+abstract class GraphQLController {
 	/**
 	 * Maximum nesting depth allowed in a GraphQL query.
 	 *
@@ -90,7 +87,10 @@ class GraphQLController {
 	 * Register the GraphQL REST route.
 	 */
 	public function register(): void {
-		$methods = Main::is_get_endpoint_enabled() ? array( 'GET', 'POST' ) : array( 'POST' );
+		$methods = Main::filter_methods_against_settings( array( 'GET', 'POST' ) );
+		if ( empty( $methods ) ) {
+			return;
+		}

 		register_rest_route(
 			'wc',
@@ -210,6 +210,24 @@ class GraphQLController {
 					$formatted['extensions']['code'] = $client_safe ? 'BAD_USER_INPUT' : 'INTERNAL_ERROR';
 				}

+				// SerializationError (thrown during schema-type coercion, e.g. when
+				// a resolver returns an Int that doesn't fit 32 bits) extends
+				// \Exception rather than webonyx's ClientAware Error, so it lands
+				// in the INTERNAL_ERROR bucket above. Its message is actually
+				// client-actionable ("value out of range — send smaller inputs"),
+				// so promote it to BAD_USER_INPUT when it shows up anywhere in
+				// the previous-exception chain.
+				if ( 'BAD_USER_INPUT' !== ( $formatted['extensions']['code'] ?? null ) ) {
+					$cursor = $error;
+					while ( $cursor instanceof \Throwable ) {
+						if ( $cursor instanceof \Automattic\WooCommerce\Vendor\GraphQL\Error\SerializationError ) {
+							$formatted['extensions']['code'] = 'BAD_USER_INPUT';
+							break;
+						}
+						$cursor = $cursor->getPrevious();
+					}
+				}
+
 				if ( $debug_mode ) {
 					$chain = $this->extract_previous_chain( $error );
 					if ( ! empty( $chain ) ) {
@@ -253,17 +271,21 @@ class GraphQLController {
 	 */
 	private function get_schema(): Schema {
 		if ( null === $this->schema ) {
-			$this->schema = new Schema(
-				array(
-					'query'    => RootQueryType::get(),
-					'mutation' => RootMutationType::get(),
-					'types'    => TypeRegistry::get_interface_implementors(),
-				)
-			);
+			$this->schema = $this->build_schema();
 		}
 		return $this->schema;
 	}

+	/**
+	 * Construct the GraphQL schema.
+	 *
+	 * Implemented by the autogenerated subclass emitted by ApiBuilder
+	 * (both for WooCommerce core and for sibling plugins that reuse this
+	 * infrastructure) so the base class stays agnostic to any specific
+	 * autogenerated namespace.
+	 */
+	abstract protected function build_schema(): Schema;
+
 	/**
 	 * Decode an optional JSON-object param (`variables` / `extensions`) into an array.
 	 *
diff --git a/plugins/woocommerce/src/Internal/Api/GraphQLEndpointRegistrar.php b/plugins/woocommerce/src/Internal/Api/GraphQLEndpointRegistrar.php
new file mode 100644
index 00000000000..ff599fb02a0
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Api/GraphQLEndpointRegistrar.php
@@ -0,0 +1,64 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Automattic\WooCommerce\Internal\Api;
+
+/**
+ * Deferred-registration helper for GraphQL endpoints declared via
+ * {@see Main::register_graphql_endpoint()}.
+ *
+ * Each instance captures the arguments of a single registration request and
+ * exposes {@see self::handle_rest_api_init()} as the callback target for the
+ * rest_api_init action, so Main doesn't have to carry per-registration state
+ * through a closure.
+ */
+class GraphQLEndpointRegistrar {
+	/**
+	 * Capture the arguments of a single register_graphql_endpoint() call.
+	 *
+	 * @param string   $controller_class_name Fully-qualified name of a concrete GraphQLController subclass.
+	 * @param string   $route_namespace       REST namespace passed to register_rest_route().
+	 * @param string   $route                 REST route path passed to register_rest_route().
+	 * @param string[] $methods               HTTP methods accepted on the endpoint.
+	 */
+	public function __construct(
+		private readonly string $controller_class_name,
+		private readonly string $route_namespace,
+		private readonly string $route,
+		private readonly array $methods
+	) {}
+
+	/**
+	 * Hook callback for rest_api_init. Instantiates the controller and
+	 * registers the REST route.
+	 *
+	 * The caller-declared methods are narrowed by
+	 * {@see Main::filter_methods_against_settings()} so plugin endpoints honour
+	 * the same site-wide settings (e.g. the GET-endpoint toggle) as
+	 * WooCommerce core's `/wc/graphql`. If the filter empties the list the
+	 * endpoint is not registered.
+	 */
+	public function handle_rest_api_init(): void {
+		$methods = Main::filter_methods_against_settings( $this->methods );
+		if ( empty( $methods ) ) {
+			return;
+		}
+
+		$controller = Main::instantiate_graphql_controller( $this->controller_class_name );
+		if ( null === $controller ) {
+			return;
+		}
+
+		register_rest_route(
+			$this->route_namespace,
+			$this->route,
+			array(
+				'methods'             => $methods,
+				'callback'            => array( $controller, 'handle_request' ),
+				// Auth is handled per-query/mutation.
+				'permission_callback' => '__return_true',
+			)
+		);
+	}
+}
diff --git a/plugins/woocommerce/src/Internal/Api/Main.php b/plugins/woocommerce/src/Internal/Api/Main.php
index faba0442ce3..58754922b25 100644
--- a/plugins/woocommerce/src/Internal/Api/Main.php
+++ b/plugins/woocommerce/src/Internal/Api/Main.php
@@ -60,6 +60,29 @@ class Main {
 		return wc_string_to_bool( get_option( self::OPTION_GET_ENDPOINT_ENABLED, 'yes' ) );
 	}

+	/**
+	 * Apply the GraphQL-scoped site settings to a caller-declared list of HTTP
+	 * methods.
+	 *
+	 * Centralises the "what verbs does the admin actually allow on a GraphQL
+	 * endpoint" rule so both WooCommerce core's own endpoint and every sibling
+	 * plugin endpoint applied through {@see self::register_graphql_endpoint()}
+	 * honour the same settings.
+	 *
+	 * Currently the only rule is: if the GET endpoint has been disabled in the
+	 * GraphQL settings section, strip GET from the list.
+	 *
+	 * @param string[] $methods HTTP methods the caller declared.
+	 * @return string[] Possibly narrowed list — may be empty, in which case the
+	 *                 caller should skip the route registration entirely.
+	 */
+	public static function filter_methods_against_settings( array $methods ): array {
+		if ( ! self::is_get_endpoint_enabled() ) {
+			$methods = array_values( array_diff( $methods, array( 'GET' ) ) );
+		}
+		return $methods;
+	}
+
 	/**
 	 * Register the GraphQL endpoint when the feature is active.
 	 *
@@ -74,14 +97,239 @@ class Main {
 			return;
 		}

-		add_action(
-			'rest_api_init',
-			static function () {
-				wc_get_container()->get( GraphQLController::class )->register();
-			}
-		);
+		add_action( 'rest_api_init', array( self::class, 'handle_rest_api_init_for_core' ) );

 		$settings = wc_get_container()->get( Settings::class );
 		$settings->register();
 	}
+
+	/**
+	 * Hook callback: register WooCommerce core's GraphQL endpoint.
+	 *
+	 * Extracted from {@see self::register()} so the hook target is a named
+	 * class method rather than an inline closure, matching WooCommerce's
+	 * usual hook-registration style.
+	 *
+	 * @internal
+	 */
+	public static function handle_rest_api_init_for_core(): void {
+		wc_get_container()->get( Autogenerated\GraphQLController::class )->register();
+	}
+
+	/**
+	 * Instantiate a GraphQL controller subclass and wire up its dependencies.
+	 *
+	 * Intended for sibling WooCommerce plugins that ship their own
+	 * autogenerated GraphQLController subclass (emitted by build-api.php
+	 * into their own autogenerated namespace). The returned controller is
+	 * ready to have handle_request() attached to a REST route.
+	 *
+	 * Returns null when the feature flag is off or PHP is < 8.1, so callers
+	 * can invoke this unconditionally from inside their own rest_api_init
+	 * handler.
+	 *
+	 * @param string $controller_class_name Fully-qualified name of a subclass of GraphQLController.
+	 *
+	 * @throws \InvalidArgumentException If $controller_class_name does not extend GraphQLController.
+	 */
+	public static function instantiate_graphql_controller( string $controller_class_name ): ?GraphQLController {
+		if ( ! self::is_enabled() ) {
+			return null;
+		}
+
+		self::assert_is_controller_subclass( $controller_class_name );
+
+		$controller = new $controller_class_name();
+		$controller->init( wc_get_container()->get( QueryCache::class ) );
+		return $controller;
+	}
+
+	/**
+	 * Register a GraphQL REST endpoint backed by a plugin-provided controller subclass.
+	 *
+	 * May be called at any time up to and including the `rest_api_init` hook:
+	 * if called earlier, registration is deferred to that hook; if called from
+	 * inside another plugin's `rest_api_init` handler, registration happens
+	 * immediately. Calls made after `rest_api_init` has already completed
+	 * register a REST route that WP_REST_Server won't honour on the current
+	 * request — callers should avoid deferring registration past bootstrap.
+	 *
+	 * When the feature flag is off or PHP is < 8.1 this is a silent no-op.
+	 *
+	 * The first argument accepts either of two forms:
+	 *
+	 *  - A plugin root directory (recommended): pass `__DIR__` from the plugin's
+	 *    bootstrap file. The controller class is resolved by convention from
+	 *    `{dir}/src/Internal/Api/Autogenerated/GraphQLController.php` — ApiBuilder
+	 *    always emits the generated subclass at that path.
+	 *  - A controller class FQCN: use this when the plugin keeps its generated
+	 *    code somewhere other than the conventional location, or registers
+	 *    multiple endpoints backed by different controller classes.
+	 *
+	 * Plugins calling this method should guard the call with method_exists():
+	 *
+	 *     if ( method_exists( \Automattic\WooCommerce\Internal\Api\Main::class, 'register_graphql_endpoint' ) ) {
+	 *         \Automattic\WooCommerce\Internal\Api\Main::register_graphql_endpoint(
+	 *             __DIR__,
+	 *             'my-plugin',
+	 *             '/graphql'
+	 *         );
+	 *     }
+	 *
+	 * @param string   $plugin_dir_or_controller_class Plugin root directory, OR a fully-qualified GraphQLController subclass name. See above.
+	 * @param string   $route_namespace                REST route namespace, as passed to register_rest_route().
+	 * @param string   $route                          REST route path, as passed to register_rest_route().
+	 * @param string[] $methods                        HTTP methods accepted on the endpoint. Defaults to GET + POST.
+	 *
+	 * @throws \InvalidArgumentException If no controller can be resolved from a directory argument, or if the resolved class does not extend GraphQLController.
+	 */
+	public static function register_graphql_endpoint(
+		string $plugin_dir_or_controller_class,
+		string $route_namespace,
+		string $route,
+		array $methods = array( 'GET', 'POST' )
+	): void {
+		if ( ! self::is_enabled() ) {
+			return;
+		}
+
+		$controller_class_name = self::resolve_controller_class( $plugin_dir_or_controller_class );
+
+		// Validate up front so a typo surfaces here at bootstrap rather than
+		// deep inside the deferred rest_api_init callback.
+		self::assert_is_controller_subclass( $controller_class_name );
+
+		$registrar = new GraphQLEndpointRegistrar( $controller_class_name, $route_namespace, $route, $methods );
+
+		if ( did_action( 'rest_api_init' ) ) {
+			$registrar->handle_rest_api_init();
+		} else {
+			add_action( 'rest_api_init', array( $registrar, 'handle_rest_api_init' ) );
+		}
+	}
+
+	/**
+	 * Resolve the first argument of {@see self::register_graphql_endpoint()} to a
+	 * controller class name. If the argument is an existing directory, treat it
+	 * as the plugin root and read the namespace from the generated controller
+	 * file at the conventional path. Otherwise return the argument unchanged so
+	 * it's used as a class FQCN.
+	 *
+	 * @param string $arg Either a plugin root directory or a controller class FQCN.
+	 *
+	 * @throws \InvalidArgumentException If the argument is a directory but the
+	 *                                   generated controller file is missing or
+	 *                                   doesn't declare a PHP namespace.
+	 */
+	private static function resolve_controller_class( string $arg ): string {
+		if ( ! is_dir( $arg ) ) {
+			return $arg;
+		}
+
+		$controller_file = rtrim( $arg, '/\\' ) . '/src/Internal/Api/Autogenerated/GraphQLController.php';
+		if ( ! is_file( $controller_file ) ) {
+			throw new \InvalidArgumentException(
+				sprintf(
+					'Expected a generated GraphQL controller at %s, but the file does not exist. Run the plugin\'s API build script first, or pass an explicit controller class name.',
+					esc_html( $controller_file )
+				)
+			);
+		}
+
+		// The generated controller always declares its namespace near the top
+		// of the file; reading the first few KB is more than enough. Reading a
+		// local file — not a URL — so wp_remote_get() does not apply here.
+		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
+		$head = file_get_contents( $controller_file, false, null, 0, 4096 );
+		if ( false === $head ) {
+			throw new \InvalidArgumentException(
+				sprintf( 'Could not read the controller file at %s.', esc_html( $controller_file ) )
+			);
+		}
+
+		$namespace = self::extract_namespace_from_php_source( $head );
+		if ( null === $namespace ) {
+			throw new \InvalidArgumentException(
+				sprintf( 'Could not determine the PHP namespace of the controller at %s.', esc_html( $controller_file ) )
+			);
+		}
+
+		return $namespace . '\\GraphQLController';
+	}
+
+	/**
+	 * Extract an unbracketed `namespace …;` declaration from a PHP source fragment.
+	 *
+	 * Uses PHP's tokenizer rather than a regex so the parse isn't fooled by
+	 * declarations inside heredocs, comments, or bracketed-namespace syntax,
+	 * and continues to work if the generator's output format changes
+	 * (e.g. attributes before the declaration, single-line `<?php namespace`).
+	 *
+	 * @param string $source PHP source code.
+	 * @return ?string The namespace FQN without leading or trailing separator, or null if none found.
+	 */
+	private static function extract_namespace_from_php_source( string $source ): ?string {
+		$tokens = token_get_all( $source );
+		$count  = count( $tokens );
+		for ( $i = 0; $i < $count; $i++ ) {
+			if ( ! is_array( $tokens[ $i ] ) || T_NAMESPACE !== $tokens[ $i ][0] ) {
+				continue;
+			}
+			$namespace = '';
+			for ( $j = $i + 1; $j < $count; $j++ ) {
+				$token = $tokens[ $j ];
+				if ( is_array( $token ) ) {
+					// T_NAME_QUALIFIED and T_NAME_FULLY_QUALIFIED exist on PHP 8+ and already
+					// contain the full namespace path; T_STRING / T_NS_SEPARATOR are the
+					// equivalent pieces on older versions.
+					if ( in_array( $token[0], array( T_STRING, T_NS_SEPARATOR ), true )
+						|| ( defined( 'T_NAME_QUALIFIED' ) && T_NAME_QUALIFIED === $token[0] )
+						|| ( defined( 'T_NAME_FULLY_QUALIFIED' ) && T_NAME_FULLY_QUALIFIED === $token[0] ) ) {
+						$namespace .= $token[1];
+						continue;
+					}
+					if ( T_WHITESPACE === $token[0] ) {
+						continue;
+					}
+				}
+				// Single-character tokens `;` (unbracketed namespace) and `{` (bracketed) end the declaration.
+				break;
+			}
+			$namespace = trim( $namespace, '\\' );
+			if ( '' !== $namespace ) {
+				return $namespace;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Assert that a class name resolves to a concrete GraphQLController subclass.
+	 *
+	 * @param string $controller_class_name Fully-qualified name of the class to validate.
+	 *
+	 * @throws \InvalidArgumentException If the class cannot be autoloaded, or does not extend GraphQLController.
+	 */
+	private static function assert_is_controller_subclass( string $controller_class_name ): void {
+		// Differentiate "class does not exist" (typo / stale autoloader) from
+		// "class exists but is not a subclass" — is_subclass_of() collapses
+		// both into false, which is confusing when debugging a bootstrap typo.
+		if ( ! class_exists( $controller_class_name ) ) {
+			throw new \InvalidArgumentException(
+				sprintf(
+					'GraphQL controller class "%s" does not exist or is not autoloadable. Check the spelling, or run `composer dump-autoload` if it was added since the last autoloader regeneration.',
+					esc_html( $controller_class_name )
+				)
+			);
+		}
+		if ( ! is_subclass_of( $controller_class_name, GraphQLController::class ) ) {
+			throw new \InvalidArgumentException(
+				sprintf(
+					'Class "%s" must extend %s.',
+					esc_html( $controller_class_name ),
+					esc_html( GraphQLController::class )
+				)
+			);
+		}
+	}
 }
diff --git a/plugins/woocommerce/src/Internal/Api/Schema/CustomScalarType.php b/plugins/woocommerce/src/Internal/Api/Schema/CustomScalarType.php
new file mode 100644
index 00000000000..6e145ea48fd
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Api/Schema/CustomScalarType.php
@@ -0,0 +1,18 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Automattic\WooCommerce\Internal\Api\Schema;
+
+/**
+ * Stable subclass of the webonyx CustomScalarType used by autogenerated
+ * custom scalar types.
+ *
+ * The constructor accepts the same associative-array config webonyx
+ * documents (keys: `name`, `description`, `serialize`, `parseValue`,
+ * `parseLiteral`).
+ *
+ * @internal Reserved for autogenerated resolver code.
+ */
+class CustomScalarType extends \Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\CustomScalarType {
+}
diff --git a/plugins/woocommerce/src/Internal/Api/Schema/EnumType.php b/plugins/woocommerce/src/Internal/Api/Schema/EnumType.php
new file mode 100644
index 00000000000..67e284188c2
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Api/Schema/EnumType.php
@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Automattic\WooCommerce\Internal\Api\Schema;
+
+/**
+ * Stable subclass of the webonyx EnumType used by autogenerated enums.
+ *
+ * The constructor accepts the same associative-array config webonyx
+ * documents (keys: `name`, `description`, `values`).
+ *
+ * @internal Reserved for autogenerated resolver code.
+ */
+class EnumType extends \Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\EnumType {
+}
diff --git a/plugins/woocommerce/src/Internal/Api/Schema/Error.php b/plugins/woocommerce/src/Internal/Api/Schema/Error.php
new file mode 100644
index 00000000000..a1cb4abd6bc
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Api/Schema/Error.php
@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Automattic\WooCommerce\Internal\Api\Schema;
+
+/**
+ * Stable subclass of the webonyx Error used by autogenerated resolvers
+ * when they need to surface a GraphQL-spec error directly (e.g. the
+ * UNAUTHORIZED error an autogenerated authorize()-backed resolver
+ * throws before invoking the command).
+ *
+ * Behaviour is inherited verbatim, including the named `extensions`
+ * argument that callers rely on to attach an error code.
+ *
+ * @internal Reserved for autogenerated resolver code.
+ */
+class Error extends \Automattic\WooCommerce\Vendor\GraphQL\Error\Error {
+}
diff --git a/plugins/woocommerce/src/Internal/Api/Schema/InputObjectType.php b/plugins/woocommerce/src/Internal/Api/Schema/InputObjectType.php
new file mode 100644
index 00000000000..cc4d40ed76a
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Api/Schema/InputObjectType.php
@@ -0,0 +1,17 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Automattic\WooCommerce\Internal\Api\Schema;
+
+/**
+ * Stable subclass of the webonyx InputObjectType used by autogenerated
+ * input types.
+ *
+ * The constructor accepts the same associative-array config webonyx
+ * documents (keys: `name`, `description`, `fields`).
+ *
+ * @internal Reserved for autogenerated resolver code.
+ */
+class InputObjectType extends \Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\InputObjectType {
+}
diff --git a/plugins/woocommerce/src/Internal/Api/Schema/InterfaceType.php b/plugins/woocommerce/src/Internal/Api/Schema/InterfaceType.php
new file mode 100644
index 00000000000..67b1191f4c7
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Api/Schema/InterfaceType.php
@@ -0,0 +1,17 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Automattic\WooCommerce\Internal\Api\Schema;
+
+/**
+ * Stable subclass of the webonyx InterfaceType used by autogenerated
+ * interface types.
+ *
+ * The constructor accepts the same associative-array config webonyx
+ * documents (keys: `name`, `description`, `fields`, `resolveType`).
+ *
+ * @internal Reserved for autogenerated resolver code.
+ */
+class InterfaceType extends \Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\InterfaceType {
+}
diff --git a/plugins/woocommerce/src/Internal/Api/Schema/ObjectType.php b/plugins/woocommerce/src/Internal/Api/Schema/ObjectType.php
new file mode 100644
index 00000000000..50fc98cb4af
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Api/Schema/ObjectType.php
@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Automattic\WooCommerce\Internal\Api\Schema;
+
+/**
+ * Stable subclass of the webonyx ObjectType used by autogenerated output
+ * types, pagination types and root Query/Mutation types.
+ *
+ * The constructor accepts the same associative-array config webonyx
+ * documents (keys: `name`, `description`, `fields`, `interfaces`). The
+ * `fields` entry is either an array or a callable returning an array of
+ * field definitions.
+ *
+ * @internal Reserved for autogenerated resolver code.
+ */
+class ObjectType extends \Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ObjectType {
+}
diff --git a/plugins/woocommerce/src/Internal/Api/Schema/README.md b/plugins/woocommerce/src/Internal/Api/Schema/README.md
new file mode 100644
index 00000000000..3f99c9d2cc5
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Api/Schema/README.md
@@ -0,0 +1,55 @@
+# GraphQL Schema Surface
+
+This directory is the sole point of contact between autogenerated GraphQL code and the underlying GraphQL engine (currently webonyx/graphql-php, renamespaced under `Automattic\WooCommerce\Vendor\GraphQL\*`).
+
+## Why it exists
+
+The dual-code API architecture treats the GraphQL engine as an implementation detail: given a set of code-API classes under `src/Api/`, ApiBuilder regenerates the autogenerated tree, and switching engines should be a matter of updating templates and regenerating. That contract only holds as long as the autogenerated output never references engine-specific symbols directly.
+
+Sibling WooCommerce plugins that reuse this infrastructure commit their autogenerated trees to their own repos. If those trees imported from `Automattic\WooCommerce\Vendor\GraphQL\*`, an engine switch in WooCommerce would break every already-committed plugin. Routing every engine reference through this namespace prevents that: the generator emits imports from `Automattic\WooCommerce\Internal\Api\Schema\*` only, and the classes here translate to whichever engine is current.
+
+## What's in here
+
+Every symbol a generated resolver, type, or root-type class can touch at runtime:
+
+| Symbol | Used by generated code as |
+| --- | --- |
+| `Schema` | Root schema object constructed in each autogenerated `GraphQLController::build_schema()`. |
+| `ObjectType` | Output types, pagination connection/edge types, root Query/Mutation, scalar-result wrappers. |
+| `InputObjectType` | Input types. |
+| `EnumType` | Enums. |
+| `InterfaceType` | Interface types. |
+| `CustomScalarType` | Custom scalars. |
+| `Type` | Static facade: `int()`, `string()`, `boolean()`, `float()`, `id()`, `nonNull($inner)`, `listOf($inner)`. |
+| `Error` | Thrown directly from resolver code when surfacing a GraphQL-spec error (e.g. the `UNAUTHORIZED` error in authorize()-backed resolvers). |
+| `ResolveInfo` | Fourth-parameter type hint on every resolver's `resolve()` method. Registered via `class_alias` in `aliases.php`. |
+| `AST\StringValueNode` | Referenced via `instanceof` inside custom scalar `parseLiteral()` callbacks. Registered via `class_alias` in `aliases.php`. |
+
+## Three implementation patterns
+
+- **Subclass** (`Schema`, `ObjectType`, `InputObjectType`, `EnumType`, `InterfaceType`, `CustomScalarType`, `Error`) — extends the webonyx class with an empty body. Today the subclass is a no-op indirection; in a future migration the constructor would translate the webonyx-shaped config array into whatever the new engine expects. Webonyx accepts subclasses of its own types wherever it accepts the parent, so there's no runtime friction today.
+- **Static facade** (`Type`) — delegates each static method to the webonyx equivalent. Return types are intentionally omitted so a future migration can change the concrete return class without breaking callers.
+- **Class alias** (`ResolveInfo`, `AST\StringValueNode`) — used when the engine itself constructs the instances and hands them to resolver code. Subclassing doesn't help because the engine creates the parent class directly. The aliases are registered eagerly in `aliases.php`, wired in via `composer.json`'s `autoload.files` entry so they run at every boot (plain Composer autoload and the Jetpack autoloader both honour this list). A future engine switch replaces the alias with a real class whose public shape matches what generated code expects.
+
+## Rules
+
+- **No implementation logic in the subclasses.** They exist to be stable FQCNs, nothing more. Behaviour that would diverge from the webonyx parent is engine-specific and belongs in a per-engine adapter, not in the surface.
+- **Generated code references this namespace only, never `Vendor\GraphQL\*`.** If a template needs a webonyx symbol that isn't here, add it here first.
+- **Versioning is implicit in the namespace.** If a future change would break already-committed plugin code, add a sibling namespace (e.g. `Internal\Api\Schema\V2`) and teach ApiBuilder to emit against it; keep the current surface until the last dependent plugin has migrated.
+
+## Adding a new symbol
+
+1. Add a subclass / facade method / alias file in the matching style.
+2. Update the template that needs it to import from this namespace.
+3. Regenerate core (`pnpm build:api`) and confirm the Autogenerated/ diff is imports-only.
+4. Add a row to the table above.
+
+## Engine migration checklist
+
+If WooCommerce switches off webonyx, the changes localized to this directory are:
+
+1. Each subclass's constructor accepts the webonyx-shaped config array and translates internally to the new engine's shape.
+2. Each class alias becomes a real class whose public members mirror what generated code accesses.
+3. `Type`'s static methods return the new engine's equivalents.
+
+Generated code already committed to plugin repos keeps working without the plugins having to regenerate.
diff --git a/plugins/woocommerce/src/Internal/Api/Schema/Schema.php b/plugins/woocommerce/src/Internal/Api/Schema/Schema.php
new file mode 100644
index 00000000000..66354e51eda
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Api/Schema/Schema.php
@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Automattic\WooCommerce\Internal\Api\Schema;
+
+/**
+ * Stable subclass of the webonyx Schema used by autogenerated
+ * GraphQLController subclasses.
+ *
+ * The constructor accepts the same associative-array config that webonyx
+ * documents (keys: `query`, `mutation`, `types`, etc.). Extending the
+ * webonyx class means webonyx's executor treats an instance of this class
+ * identically to its own parent, so no adaptation is needed at runtime.
+ *
+ * @internal Reserved for autogenerated resolver code.
+ */
+class Schema extends \Automattic\WooCommerce\Vendor\GraphQL\Type\Schema {
+}
diff --git a/plugins/woocommerce/src/Internal/Api/Schema/Type.php b/plugins/woocommerce/src/Internal/Api/Schema/Type.php
new file mode 100644
index 00000000000..daab5afaf28
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Api/Schema/Type.php
@@ -0,0 +1,79 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Automattic\WooCommerce\Internal\Api\Schema;
+
+use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type as WebonyxType;
+
+/**
+ * Static facade for GraphQL scalar types and type modifiers referenced by
+ * autogenerated resolvers.
+ *
+ * Autogenerated code emitted by ApiBuilder only touches webonyx through
+ * this (and the other classes in the Internal\Api\Schema namespace), so
+ * the underlying GraphQL engine can be swapped without invalidating
+ * already-committed generated code. Return types are intentionally
+ * omitted so a future migration can change the concrete return type
+ * without breaking callers.
+ *
+ * @internal Reserved for autogenerated resolver code.
+ */
+final class Type {
+	/**
+	 * The built-in GraphQL Int scalar.
+	 */
+	public static function int() {
+		return WebonyxType::int();
+	}
+
+	/**
+	 * The built-in GraphQL String scalar.
+	 */
+	public static function string() {
+		return WebonyxType::string();
+	}
+
+	/**
+	 * The built-in GraphQL Boolean scalar.
+	 */
+	public static function boolean() {
+		return WebonyxType::boolean();
+	}
+
+	/**
+	 * The built-in GraphQL Float scalar.
+	 */
+	public static function float() {
+		return WebonyxType::float();
+	}
+
+	/**
+	 * The built-in GraphQL ID scalar.
+	 */
+	public static function id() {
+		return WebonyxType::id();
+	}
+
+	// phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid -- Method names mirror the webonyx Type factories so autogenerated imports of this facade and its webonyx counterpart are interchangeable.
+
+	/**
+	 * Wrap a nullable schema type as non-null (`T!`).
+	 *
+	 * @param mixed $inner A nullable schema type.
+	 */
+	public static function nonNull( $inner ) {
+		return WebonyxType::nonNull( $inner );
+	}
+
+	/**
+	 * Wrap a schema type as a list (`[T]`).
+	 *
+	 * @param mixed $inner A schema type.
+	 */
+	public static function listOf( $inner ) {
+		return WebonyxType::listOf( $inner );
+	}
+
+	// phpcs:enable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
+}
diff --git a/plugins/woocommerce/src/Internal/Api/Schema/aliases.php b/plugins/woocommerce/src/Internal/Api/Schema/aliases.php
new file mode 100644
index 00000000000..6b3db75393d
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Api/Schema/aliases.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * Class-alias bootstrap for the Internal\Api\Schema surface.
+ *
+ * Some symbols in the surface — ResolveInfo and StringValueNode — cannot be
+ * subclasses because the GraphQL engine constructs them itself and hands them
+ * to resolver code. A subclass would be a distinct type and fail resolver
+ * parameter type-hint checks. Instead we register them as class_alias of
+ * their webonyx counterparts so the two FQCNs resolve to the same class.
+ *
+ * This file is loaded eagerly via composer's `autoload.files` entry (which
+ * the Jetpack autoloader in turn exposes through its filemap), so the aliases
+ * are available before any resolver is invoked.
+ *
+ * @internal Reserved for the GraphQL autogeneration infrastructure.
+ */
+
+declare(strict_types=1);
+
+class_alias(
+	\Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\ResolveInfo::class,
+	'Automattic\\WooCommerce\\Internal\\Api\\Schema\\ResolveInfo'
+);
+
+class_alias(
+	\Automattic\WooCommerce\Vendor\GraphQL\Language\AST\StringValueNode::class,
+	'Automattic\\WooCommerce\\Internal\\Api\\Schema\\AST\\StringValueNode'
+);
diff --git a/plugins/woocommerce/tests/php/src/Internal/Api/GraphQLControllerTest.php b/plugins/woocommerce/tests/php/src/Internal/Api/GraphQLControllerTest.php
index c700f4821a1..34fb2e5198c 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Api/GraphQLControllerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Api/GraphQLControllerTest.php
@@ -3,6 +3,7 @@ declare( strict_types = 1 );

 namespace Automattic\WooCommerce\Tests\Internal\Api;

+use Automattic\WooCommerce\Internal\Api\Autogenerated\GraphQLController as AutogeneratedGraphQLController;
 use Automattic\WooCommerce\Internal\Api\GraphQLController;
 use Automattic\WooCommerce\Internal\Api\Main;
 use WC_REST_Unit_Test_Case;
@@ -34,7 +35,9 @@ class GraphQLControllerTest extends WC_REST_Unit_Test_Case {
 			$this->markTestSkipped( 'GraphQLController requires PHP 8.1+.' );
 		}

-		$this->sut = wc_get_container()->get( GraphQLController::class );
+		// GraphQLController is abstract; instantiate the autogenerated subclass
+		// that wc_get_container() also uses via Main::handle_rest_api_init_for_core().
+		$this->sut = wc_get_container()->get( AutogeneratedGraphQLController::class );
 	}

 	/**