diff --git a/core/broadleaf-framework-web/pom.xml b/core/broadleaf-framework-web/pom.xml
index c732740dce..4a8752142e 100644
--- a/core/broadleaf-framework-web/pom.xml
+++ b/core/broadleaf-framework-web/pom.xml
@@ -94,5 +94,9 @@
org.springframework.security
spring-security-oauth2-client
+
+ org.springframework.boot
+ spring-boot-starter-graphql
+
diff --git a/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/GraphQLContextInterceptor.java b/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/GraphQLContextInterceptor.java
new file mode 100644
index 0000000000..4a781bc8c4
--- /dev/null
+++ b/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/GraphQLContextInterceptor.java
@@ -0,0 +1,89 @@
+/*-
+ * #%L
+ * BroadleafCommerce Framework Web
+ * %%
+ * Copyright (C) 2009 - 2026 Broadleaf Commerce
+ * %%
+ * Licensed under the Broadleaf Fair Use License Agreement, Version 1.0
+ * (the "Fair Use License" located at http://license.broadleafcommerce.org/fair_use_license-1.0.txt)
+ * unless the restrictions on use therein are violated and require payment to Broadleaf in which case
+ * the Broadleaf End User License Agreement (EULA), Version 1.1
+ * (the "Commercial License" located at http://license.broadleafcommerce.org/commercial_license-1.1.txt)
+ * shall apply.
+ *
+ * Alternatively, the Commercial License may be replaced with a mutually agreed upon license (the "Custom License")
+ * between you and Broadleaf Commerce. You may not use this file except in compliance with the applicable license.
+ * #L%
+ */
+package org.broadleafcommerce.core.web.graphql;
+
+import org.broadleafcommerce.common.web.BroadleafRequestContext;
+import org.broadleafcommerce.core.order.domain.Order;
+import org.broadleafcommerce.core.order.service.OrderService;
+import org.broadleafcommerce.core.web.order.CartState;
+import org.broadleafcommerce.profile.core.domain.Customer;
+import org.broadleafcommerce.profile.core.service.CustomerService;
+import org.broadleafcommerce.profile.web.core.CustomerState;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.graphql.server.WebGraphQlInterceptor;
+import org.springframework.graphql.server.WebGraphQlRequest;
+import org.springframework.graphql.server.WebGraphQlResponse;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.context.request.ServletWebRequest;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import reactor.core.publisher.Mono;
+
+/**
+ * Populates the Broadleaf customer and cart context for GraphQL requests. This mirrors the identity
+ * resolution pattern used by {@code RestApiCustomerStateFilter} so that query/mutation resolvers
+ * can rely on {@link CustomerState} and {@link CartState}.
+ */
+@Component
+public class GraphQLContextInterceptor implements WebGraphQlInterceptor {
+
+ @Autowired
+ @Qualifier("blCustomerService")
+ protected CustomerService customerService;
+
+ @Autowired
+ @Qualifier("blOrderService")
+ protected OrderService orderService;
+
+ @Override
+ public Mono intercept(WebGraphQlRequest request, Chain chain) {
+ RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
+ if (attributes instanceof ServletRequestAttributes servletAttributes) {
+ HttpServletRequest httpRequest = servletAttributes.getRequest();
+ HttpServletResponse httpResponse = servletAttributes.getResponse();
+ populateContext(httpRequest, httpResponse);
+ }
+ return chain.next(request);
+ }
+
+ protected void populateContext(HttpServletRequest request, HttpServletResponse response) {
+ BroadleafRequestContext brc = BroadleafRequestContext.getBroadleafRequestContext();
+ if (brc != null && brc.getWebRequest() == null) {
+ brc.setWebRequest(new ServletWebRequest(request, response));
+ }
+
+ Customer customer = CustomerState.getCustomer();
+ if (customer == null) {
+ customer = customerService.createCustomer();
+ CustomerState.setCustomer(customer);
+ }
+
+ if (CartState.getCart() == null && customer != null && customer.getId() != null) {
+ Order cart = orderService.findCartForCustomer(customer);
+ if (cart != null) {
+ CartState.setCart(cart);
+ }
+ }
+ }
+
+}
diff --git a/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/GraphQLExceptionResolver.java b/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/GraphQLExceptionResolver.java
new file mode 100644
index 0000000000..b43cf918b2
--- /dev/null
+++ b/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/GraphQLExceptionResolver.java
@@ -0,0 +1,73 @@
+/*-
+ * #%L
+ * BroadleafCommerce Framework Web
+ * %%
+ * Copyright (C) 2009 - 2026 Broadleaf Commerce
+ * %%
+ * Licensed under the Broadleaf Fair Use License Agreement, Version 1.0
+ * (the "Fair Use License" located at http://license.broadleafcommerce.org/fair_use_license-1.0.txt)
+ * unless the restrictions on use therein are violated and require payment to Broadleaf in which case
+ * the Broadleaf End User License Agreement (EULA), Version 1.1
+ * (the "Commercial License" located at http://license.broadleafcommerce.org/commercial_license-1.1.txt)
+ * shall apply.
+ *
+ * Alternatively, the Commercial License may be replaced with a mutually agreed upon license (the "Custom License")
+ * between you and Broadleaf Commerce. You may not use this file except in compliance with the applicable license.
+ * #L%
+ */
+package org.broadleafcommerce.core.web.graphql;
+
+import graphql.GraphQLError;
+import graphql.GraphqlErrorBuilder;
+import graphql.schema.DataFetchingEnvironment;
+import org.broadleafcommerce.core.offer.service.exception.OfferMaxUseExceededException;
+import org.broadleafcommerce.core.order.service.exception.AddToCartException;
+import org.broadleafcommerce.core.order.service.exception.IllegalCartOperationException;
+import org.broadleafcommerce.core.order.service.exception.RemoveFromCartException;
+import org.broadleafcommerce.core.order.service.exception.UpdateCartException;
+import org.broadleafcommerce.core.pricing.service.exception.PricingException;
+import org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter;
+import org.springframework.graphql.execution.ErrorType;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class GraphQLExceptionResolver extends DataFetcherExceptionResolverAdapter {
+
+ @Override
+ protected GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironment env) {
+ String code = resolveCode(ex);
+ Map extensions = new HashMap<>();
+ extensions.put("code", code);
+ return GraphqlErrorBuilder.newError(env)
+ .errorType(ErrorType.INTERNAL_ERROR)
+ .message(ex.getMessage() != null ? ex.getMessage() : code)
+ .extensions(extensions)
+ .build();
+ }
+
+ protected String resolveCode(Throwable ex) {
+ if (ex instanceof OfferMaxUseExceededException) {
+ return "OFFER_MAX_USE_EXCEEDED";
+ }
+ if (ex instanceof AddToCartException) {
+ return "ADD_TO_CART_ERROR";
+ }
+ if (ex instanceof RemoveFromCartException) {
+ return "REMOVE_FROM_CART_ERROR";
+ }
+ if (ex instanceof UpdateCartException) {
+ return "UPDATE_CART_ERROR";
+ }
+ if (ex instanceof IllegalCartOperationException) {
+ return "ILLEGAL_CART_OPERATION";
+ }
+ if (ex instanceof PricingException) {
+ return "PRICING_ERROR";
+ }
+ return "INTERNAL_ERROR";
+ }
+
+}
diff --git a/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/dto/AddToCartInput.java b/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/dto/AddToCartInput.java
new file mode 100644
index 0000000000..d3432ed278
--- /dev/null
+++ b/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/dto/AddToCartInput.java
@@ -0,0 +1,61 @@
+/*-
+ * #%L
+ * BroadleafCommerce Framework Web
+ * %%
+ * Copyright (C) 2009 - 2026 Broadleaf Commerce
+ * %%
+ * Licensed under the Broadleaf Fair Use License Agreement, Version 1.0
+ * (the "Fair Use License" located at http://license.broadleafcommerce.org/fair_use_license-1.0.txt)
+ * unless the restrictions on use therein are violated and require payment to Broadleaf in which case
+ * the Broadleaf End User License Agreement (EULA), Version 1.1
+ * (the "Commercial License" located at http://license.broadleafcommerce.org/commercial_license-1.1.txt)
+ * shall apply.
+ *
+ * Alternatively, the Commercial License may be replaced with a mutually agreed upon license (the "Custom License")
+ * between you and Broadleaf Commerce. You may not use this file except in compliance with the applicable license.
+ * #L%
+ */
+package org.broadleafcommerce.core.web.graphql.dto;
+
+import java.util.List;
+
+public class AddToCartInput {
+
+ private String productId;
+ private String skuId;
+ private int quantity;
+ private List itemAttributes;
+
+ public String getProductId() {
+ return productId;
+ }
+
+ public void setProductId(String productId) {
+ this.productId = productId;
+ }
+
+ public String getSkuId() {
+ return skuId;
+ }
+
+ public void setSkuId(String skuId) {
+ this.skuId = skuId;
+ }
+
+ public int getQuantity() {
+ return quantity;
+ }
+
+ public void setQuantity(int quantity) {
+ this.quantity = quantity;
+ }
+
+ public List getItemAttributes() {
+ return itemAttributes;
+ }
+
+ public void setItemAttributes(List itemAttributes) {
+ this.itemAttributes = itemAttributes;
+ }
+
+}
diff --git a/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/dto/ItemAttributeInput.java b/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/dto/ItemAttributeInput.java
new file mode 100644
index 0000000000..fc5a86745d
--- /dev/null
+++ b/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/dto/ItemAttributeInput.java
@@ -0,0 +1,41 @@
+/*-
+ * #%L
+ * BroadleafCommerce Framework Web
+ * %%
+ * Copyright (C) 2009 - 2026 Broadleaf Commerce
+ * %%
+ * Licensed under the Broadleaf Fair Use License Agreement, Version 1.0
+ * (the "Fair Use License" located at http://license.broadleafcommerce.org/fair_use_license-1.0.txt)
+ * unless the restrictions on use therein are violated and require payment to Broadleaf in which case
+ * the Broadleaf End User License Agreement (EULA), Version 1.1
+ * (the "Commercial License" located at http://license.broadleafcommerce.org/commercial_license-1.1.txt)
+ * shall apply.
+ *
+ * Alternatively, the Commercial License may be replaced with a mutually agreed upon license (the "Custom License")
+ * between you and Broadleaf Commerce. You may not use this file except in compliance with the applicable license.
+ * #L%
+ */
+package org.broadleafcommerce.core.web.graphql.dto;
+
+public class ItemAttributeInput {
+
+ private String name;
+ private String value;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+}
diff --git a/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/dto/PromoCodeResult.java b/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/dto/PromoCodeResult.java
new file mode 100644
index 0000000000..50aa6582d1
--- /dev/null
+++ b/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/dto/PromoCodeResult.java
@@ -0,0 +1,46 @@
+/*-
+ * #%L
+ * BroadleafCommerce Framework Web
+ * %%
+ * Copyright (C) 2009 - 2026 Broadleaf Commerce
+ * %%
+ * Licensed under the Broadleaf Fair Use License Agreement, Version 1.0
+ * (the "Fair Use License" located at http://license.broadleafcommerce.org/fair_use_license-1.0.txt)
+ * unless the restrictions on use therein are violated and require payment to Broadleaf in which case
+ * the Broadleaf End User License Agreement (EULA), Version 1.1
+ * (the "Commercial License" located at http://license.broadleafcommerce.org/commercial_license-1.1.txt)
+ * shall apply.
+ *
+ * Alternatively, the Commercial License may be replaced with a mutually agreed upon license (the "Custom License")
+ * between you and Broadleaf Commerce. You may not use this file except in compliance with the applicable license.
+ * #L%
+ */
+package org.broadleafcommerce.core.web.graphql.dto;
+
+import org.broadleafcommerce.core.order.domain.Order;
+
+public class PromoCodeResult {
+
+ private final Order order;
+ private final boolean promoAdded;
+ private final String errorMessage;
+
+ public PromoCodeResult(Order order, boolean promoAdded, String errorMessage) {
+ this.order = order;
+ this.promoAdded = promoAdded;
+ this.errorMessage = errorMessage;
+ }
+
+ public Order getOrder() {
+ return order;
+ }
+
+ public boolean isPromoAdded() {
+ return promoAdded;
+ }
+
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+}
diff --git a/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/resolvers/CartMutationResolver.java b/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/resolvers/CartMutationResolver.java
new file mode 100644
index 0000000000..0d4c69b404
--- /dev/null
+++ b/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/resolvers/CartMutationResolver.java
@@ -0,0 +1,177 @@
+/*-
+ * #%L
+ * BroadleafCommerce Framework Web
+ * %%
+ * Copyright (C) 2009 - 2026 Broadleaf Commerce
+ * %%
+ * Licensed under the Broadleaf Fair Use License Agreement, Version 1.0
+ * (the "Fair Use License" located at http://license.broadleafcommerce.org/fair_use_license-1.0.txt)
+ * unless the restrictions on use therein are violated and require payment to Broadleaf in which case
+ * the Broadleaf End User License Agreement (EULA), Version 1.1
+ * (the "Commercial License" located at http://license.broadleafcommerce.org/commercial_license-1.1.txt)
+ * shall apply.
+ *
+ * Alternatively, the Commercial License may be replaced with a mutually agreed upon license (the "Custom License")
+ * between you and Broadleaf Commerce. You may not use this file except in compliance with the applicable license.
+ * #L%
+ */
+package org.broadleafcommerce.core.web.graphql.resolvers;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.broadleafcommerce.core.offer.domain.OfferCode;
+import org.broadleafcommerce.core.offer.service.OfferService;
+import org.broadleafcommerce.core.offer.service.exception.OfferAlreadyAddedException;
+import org.broadleafcommerce.core.offer.service.exception.OfferException;
+import org.broadleafcommerce.core.offer.service.exception.OfferExpiredException;
+import org.broadleafcommerce.core.offer.service.exception.OfferMaxUseExceededException;
+import org.broadleafcommerce.core.order.domain.NullOrderImpl;
+import org.broadleafcommerce.core.order.domain.Order;
+import org.broadleafcommerce.core.order.service.OrderService;
+import org.broadleafcommerce.core.order.service.call.OrderItemRequestDTO;
+import org.broadleafcommerce.core.order.service.exception.AddToCartException;
+import org.broadleafcommerce.core.order.service.exception.RemoveFromCartException;
+import org.broadleafcommerce.core.order.service.exception.UpdateCartException;
+import org.broadleafcommerce.core.pricing.service.exception.PricingException;
+import org.broadleafcommerce.core.web.graphql.dto.AddToCartInput;
+import org.broadleafcommerce.core.web.graphql.dto.ItemAttributeInput;
+import org.broadleafcommerce.core.web.graphql.dto.PromoCodeResult;
+import org.broadleafcommerce.core.web.order.CartState;
+import org.broadleafcommerce.core.web.service.UpdateCartService;
+import org.broadleafcommerce.profile.web.core.CustomerState;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.graphql.data.method.annotation.Argument;
+import org.springframework.graphql.data.method.annotation.MutationMapping;
+import org.springframework.stereotype.Controller;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Controller
+public class CartMutationResolver {
+
+ @Autowired
+ @Qualifier("blOrderService")
+ protected OrderService orderService;
+
+ @Autowired
+ @Qualifier("blOfferService")
+ protected OfferService offerService;
+
+ @Autowired
+ @Qualifier("blUpdateCartService")
+ protected UpdateCartService updateCartService;
+
+ @MutationMapping
+ public Order addToCart(@Argument AddToCartInput input) throws AddToCartException, PricingException {
+ Order cart = CartState.getCart();
+ if (cart == null || cart instanceof NullOrderImpl) {
+ cart = orderService.createNewCartForCustomer(CustomerState.getCustomer());
+ CartState.setCart(cart);
+ }
+
+ OrderItemRequestDTO itemRequest = new OrderItemRequestDTO();
+ itemRequest.setProductId(Long.parseLong(input.getProductId()));
+ if (input.getSkuId() != null) {
+ itemRequest.setSkuId(Long.parseLong(input.getSkuId()));
+ }
+ itemRequest.setQuantity(input.getQuantity());
+ if (CollectionUtils.isNotEmpty(input.getItemAttributes())) {
+ Map attributes = new HashMap<>();
+ for (ItemAttributeInput attr : input.getItemAttributes()) {
+ attributes.put(attr.getName(), attr.getValue());
+ }
+ itemRequest.setItemAttributes(attributes);
+ }
+
+ updateCartService.validateAddToCartRequest(itemRequest, cart);
+
+ cart = orderService.addItem(cart.getId(), itemRequest, false);
+ cart = orderService.save(cart, true);
+ CartState.setCart(cart);
+ return cart;
+ }
+
+ @MutationMapping
+ public Order updateCartItemQuantity(@Argument Long orderItemId, @Argument int quantity)
+ throws UpdateCartException, PricingException, RemoveFromCartException {
+ Order cart = requireActiveCart();
+ OrderItemRequestDTO itemRequest = new OrderItemRequestDTO();
+ itemRequest.setOrderItemId(orderItemId);
+ itemRequest.setQuantity(quantity);
+ cart = orderService.updateItemQuantity(cart.getId(), itemRequest, true);
+ cart = orderService.save(cart, false);
+ CartState.setCart(cart);
+ return cart;
+ }
+
+ @MutationMapping
+ public Order removeFromCart(@Argument Long orderItemId)
+ throws PricingException, RemoveFromCartException {
+ Order cart = requireActiveCart();
+ cart = orderService.removeItem(cart.getId(), orderItemId, false);
+ cart = orderService.save(cart, true);
+ CartState.setCart(cart);
+ return cart;
+ }
+
+ protected Order requireActiveCart() {
+ Order cart = CartState.getCart();
+ if (cart == null || cart instanceof NullOrderImpl) {
+ throw new IllegalStateException("No active cart for the current customer");
+ }
+ return cart;
+ }
+
+ @MutationMapping
+ public PromoCodeResult applyPromoCode(@Argument String code) throws PricingException {
+ Order cart = CartState.getCart();
+ boolean promoAdded = false;
+ String errorMessage = null;
+
+ if (cart != null && !(cart instanceof NullOrderImpl)) {
+ List offerCodes = offerService.lookupAllOfferCodesByCode(code);
+ if (CollectionUtils.isNotEmpty(offerCodes)) {
+ for (OfferCode offerCode : offerCodes) {
+ try {
+ cart = orderService.addOfferCode(cart, offerCode, false);
+ promoAdded = true;
+ } catch (OfferMaxUseExceededException e) {
+ errorMessage = "Use Limit Exceeded";
+ } catch (OfferExpiredException e) {
+ errorMessage = "Offer Has Expired";
+ } catch (OfferAlreadyAddedException e) {
+ errorMessage = "Offer Has Already Been Added";
+ } catch (OfferException e) {
+ errorMessage = "An Unknown Offer Error Has Occurred";
+ }
+ }
+ if (errorMessage == null) {
+ cart = orderService.save(cart, true);
+ CartState.setCart(cart);
+ }
+ } else {
+ errorMessage = "Unknown Code";
+ }
+ } else {
+ errorMessage = "Invalid Cart";
+ }
+
+ return new PromoCodeResult(cart, promoAdded, errorMessage);
+ }
+
+ @MutationMapping
+ public Order removePromoCode(@Argument Long offerCodeId) throws PricingException {
+ Order cart = requireActiveCart();
+ OfferCode offerCode = offerService.findOfferCodeById(offerCodeId);
+ if (offerCode == null) {
+ throw new IllegalArgumentException("Offer code not found for id: " + offerCodeId);
+ }
+ cart = orderService.removeOfferCode(cart, offerCode, false);
+ cart = orderService.save(cart, true);
+ CartState.setCart(cart);
+ return cart;
+ }
+
+}
diff --git a/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/resolvers/CartQueryResolver.java b/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/resolvers/CartQueryResolver.java
new file mode 100644
index 0000000000..834a9fd884
--- /dev/null
+++ b/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/resolvers/CartQueryResolver.java
@@ -0,0 +1,93 @@
+/*-
+ * #%L
+ * BroadleafCommerce Framework Web
+ * %%
+ * Copyright (C) 2009 - 2026 Broadleaf Commerce
+ * %%
+ * Licensed under the Broadleaf Fair Use License Agreement, Version 1.0
+ * (the "Fair Use License" located at http://license.broadleafcommerce.org/fair_use_license-1.0.txt)
+ * unless the restrictions on use therein are violated and require payment to Broadleaf in which case
+ * the Broadleaf End User License Agreement (EULA), Version 1.1
+ * (the "Commercial License" located at http://license.broadleafcommerce.org/commercial_license-1.1.txt)
+ * shall apply.
+ *
+ * Alternatively, the Commercial License may be replaced with a mutually agreed upon license (the "Custom License")
+ * between you and Broadleaf Commerce. You may not use this file except in compliance with the applicable license.
+ * #L%
+ */
+package org.broadleafcommerce.core.web.graphql.resolvers;
+
+import org.broadleafcommerce.core.order.domain.Order;
+import org.broadleafcommerce.core.order.service.OrderService;
+import org.broadleafcommerce.core.order.service.type.OrderStatus;
+import org.broadleafcommerce.profile.core.domain.Customer;
+import org.broadleafcommerce.profile.web.core.CustomerState;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.graphql.data.method.annotation.Argument;
+import org.springframework.graphql.data.method.annotation.QueryMapping;
+import org.springframework.stereotype.Controller;
+
+import java.util.Collections;
+import java.util.List;
+
+@Controller
+public class CartQueryResolver {
+
+ @Autowired
+ @Qualifier("blOrderService")
+ protected OrderService orderService;
+
+ @QueryMapping
+ public Order cart() {
+ Customer customer = CustomerState.getCustomer();
+ if (customer == null) {
+ return null;
+ }
+ return orderService.findCartForCustomer(customer);
+ }
+
+ @QueryMapping
+ public Order order(@Argument Long id) {
+ Order order = orderService.findOrderById(id);
+ return filterOrderForCurrentCustomer(order);
+ }
+
+ @QueryMapping
+ public Order orderByNumber(@Argument String orderNumber) {
+ Order order = orderService.findOrderByOrderNumber(orderNumber);
+ return filterOrderForCurrentCustomer(order);
+ }
+
+ protected Order filterOrderForCurrentCustomer(Order order) {
+ if (order == null) {
+ return null;
+ }
+ Customer currentCustomer = CustomerState.getCustomer();
+ Customer orderCustomer = order.getCustomer();
+ if (currentCustomer == null || orderCustomer == null
+ || !currentCustomer.equals(orderCustomer)) {
+ return null;
+ }
+ return order;
+ }
+
+ @QueryMapping
+ public List orderHistory(@Argument String status) {
+ Customer customer = CustomerState.getCustomer();
+ if (customer == null) {
+ return Collections.emptyList();
+ }
+ if (status != null) {
+ OrderStatus orderStatus = OrderStatus.getInstance(status);
+ return orderService.findOrdersForCustomer(customer, orderStatus);
+ }
+ return orderService.findOrdersForCustomer(customer);
+ }
+
+ @QueryMapping
+ public Customer customer() {
+ return CustomerState.getCustomer();
+ }
+
+}
diff --git a/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/resolvers/OrderFieldResolver.java b/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/resolvers/OrderFieldResolver.java
new file mode 100644
index 0000000000..ebd331c8f5
--- /dev/null
+++ b/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/resolvers/OrderFieldResolver.java
@@ -0,0 +1,72 @@
+/*-
+ * #%L
+ * BroadleafCommerce Framework Web
+ * %%
+ * Copyright (C) 2009 - 2026 Broadleaf Commerce
+ * %%
+ * Licensed under the Broadleaf Fair Use License Agreement, Version 1.0
+ * (the "Fair Use License" located at http://license.broadleafcommerce.org/fair_use_license-1.0.txt)
+ * unless the restrictions on use therein are violated and require payment to Broadleaf in which case
+ * the Broadleaf End User License Agreement (EULA), Version 1.1
+ * (the "Commercial License" located at http://license.broadleafcommerce.org/commercial_license-1.1.txt)
+ * shall apply.
+ *
+ * Alternatively, the Commercial License may be replaced with a mutually agreed upon license (the "Custom License")
+ * between you and Broadleaf Commerce. You may not use this file except in compliance with the applicable license.
+ * #L%
+ */
+package org.broadleafcommerce.core.web.graphql.resolvers;
+
+import org.broadleafcommerce.common.money.Money;
+import org.broadleafcommerce.core.order.domain.FulfillmentGroup;
+import org.broadleafcommerce.core.order.domain.Order;
+import org.broadleafcommerce.core.order.domain.OrderItem;
+import org.broadleafcommerce.core.order.service.OrderService;
+import org.broadleafcommerce.core.order.service.type.OrderStatus;
+import org.broadleafcommerce.core.payment.domain.OrderPayment;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.graphql.data.method.annotation.SchemaMapping;
+import org.springframework.stereotype.Controller;
+
+import java.util.List;
+
+@Controller
+public class OrderFieldResolver {
+
+ @Autowired
+ @Qualifier("blOrderService")
+ protected OrderService orderService;
+
+ @SchemaMapping(typeName = "Order", field = "orderItems")
+ public List orderItems(Order order) {
+ return order.getOrderItems();
+ }
+
+ @SchemaMapping(typeName = "Order", field = "fulfillmentGroups")
+ public List fulfillmentGroups(Order order) {
+ return order.getFulfillmentGroups();
+ }
+
+ @SchemaMapping(typeName = "Order", field = "payments")
+ public List payments(Order order) {
+ return orderService.findPaymentsForOrder(order);
+ }
+
+ @SchemaMapping(typeName = "Order", field = "subTotal")
+ public Money subTotal(Order order) {
+ return order.getSubTotal();
+ }
+
+ @SchemaMapping(typeName = "Order", field = "total")
+ public Money total(Order order) {
+ return order.getTotal();
+ }
+
+ @SchemaMapping(typeName = "Order", field = "status")
+ public String status(Order order) {
+ OrderStatus status = order.getStatus();
+ return status != null ? status.getType() : null;
+ }
+
+}
diff --git a/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/resolvers/OrderItemFieldResolver.java b/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/resolvers/OrderItemFieldResolver.java
new file mode 100644
index 0000000000..67ed4ca05c
--- /dev/null
+++ b/core/broadleaf-framework-web/src/main/java/org/broadleafcommerce/core/web/graphql/resolvers/OrderItemFieldResolver.java
@@ -0,0 +1,46 @@
+/*-
+ * #%L
+ * BroadleafCommerce Framework Web
+ * %%
+ * Copyright (C) 2009 - 2026 Broadleaf Commerce
+ * %%
+ * Licensed under the Broadleaf Fair Use License Agreement, Version 1.0
+ * (the "Fair Use License" located at http://license.broadleafcommerce.org/fair_use_license-1.0.txt)
+ * unless the restrictions on use therein are violated and require payment to Broadleaf in which case
+ * the Broadleaf End User License Agreement (EULA), Version 1.1
+ * (the "Commercial License" located at http://license.broadleafcommerce.org/commercial_license-1.1.txt)
+ * shall apply.
+ *
+ * Alternatively, the Commercial License may be replaced with a mutually agreed upon license (the "Custom License")
+ * between you and Broadleaf Commerce. You may not use this file except in compliance with the applicable license.
+ * #L%
+ */
+package org.broadleafcommerce.core.web.graphql.resolvers;
+
+import org.broadleafcommerce.core.catalog.domain.Product;
+import org.broadleafcommerce.core.catalog.domain.Sku;
+import org.broadleafcommerce.core.order.domain.DiscreteOrderItem;
+import org.broadleafcommerce.core.order.domain.OrderItem;
+import org.springframework.graphql.data.method.annotation.SchemaMapping;
+import org.springframework.stereotype.Controller;
+
+@Controller
+public class OrderItemFieldResolver {
+
+ @SchemaMapping(typeName = "OrderItem", field = "product")
+ public Product product(OrderItem orderItem) {
+ if (orderItem instanceof DiscreteOrderItem discreteOrderItem) {
+ return discreteOrderItem.getProduct();
+ }
+ return null;
+ }
+
+ @SchemaMapping(typeName = "OrderItem", field = "sku")
+ public Sku sku(OrderItem orderItem) {
+ if (orderItem instanceof DiscreteOrderItem discreteOrderItem) {
+ return discreteOrderItem.getSku();
+ }
+ return null;
+ }
+
+}
diff --git a/core/broadleaf-framework-web/src/main/resources/graphql-application.properties b/core/broadleaf-framework-web/src/main/resources/graphql-application.properties
new file mode 100644
index 0000000000..bdbfa221b0
--- /dev/null
+++ b/core/broadleaf-framework-web/src/main/resources/graphql-application.properties
@@ -0,0 +1,20 @@
+###
+# #%L
+# BroadleafCommerce Framework Web
+# %%
+# Copyright (C) 2009 - 2026 Broadleaf Commerce
+# %%
+# Licensed under the Broadleaf Fair Use License Agreement, Version 1.0
+# (the "Fair Use License" located at http://license.broadleafcommerce.org/fair_use_license-1.0.txt)
+# unless the restrictions on use therein are violated and require payment to Broadleaf in which case
+# the Broadleaf End User License Agreement (EULA), Version 1.1
+# (the "Commercial License" located at http://license.broadleafcommerce.org/commercial_license-1.1.txt)
+# shall apply.
+#
+# Alternatively, the Commercial License may be replaced with a mutually agreed upon license (the "Custom License")
+# between you and Broadleaf Commerce. You may not use this file except in compliance with the applicable license.
+# #L%
+###
+spring.graphql.graphiql.enabled=true
+spring.graphql.path=/graphql
+spring.graphql.schema.locations=classpath:graphql/
diff --git a/core/broadleaf-framework-web/src/main/resources/graphql/schema.graphqls b/core/broadleaf-framework-web/src/main/resources/graphql/schema.graphqls
new file mode 100644
index 0000000000..e3c22a8154
--- /dev/null
+++ b/core/broadleaf-framework-web/src/main/resources/graphql/schema.graphqls
@@ -0,0 +1,204 @@
+type Query {
+ cart: Order
+ order(id: ID!): Order
+ orderByNumber(orderNumber: String!): Order
+ orderHistory(status: OrderStatus): [Order!]!
+ customer: Customer
+}
+
+type Mutation {
+ addToCart(input: AddToCartInput!): Order!
+ updateCartItemQuantity(orderItemId: ID!, quantity: Int!): Order!
+ removeFromCart(orderItemId: ID!): Order!
+ applyPromoCode(code: String!): PromoCodeResult!
+ removePromoCode(offerCodeId: ID!): Order!
+}
+
+type Money {
+ amount: BigDecimal!
+ currency: String
+}
+
+scalar BigDecimal
+
+type Product {
+ id: ID!
+ name: String
+ description: String
+ longDescription: String
+ url: String
+ manufacturer: String
+ defaultCategory: Category
+ defaultSku: Sku
+ additionalSkus: [Sku!]
+ productOptions: [ProductOption!]
+}
+
+type Category {
+ id: ID!
+ name: String
+ url: String
+ description: String
+ longDescription: String
+ activeStartDate: String
+ activeEndDate: String
+}
+
+type Sku {
+ id: ID!
+ name: String
+ description: String
+ longDescription: String
+ salePrice: Money
+ retailPrice: Money
+ cost: Money
+ upc: String
+ quantityAvailable: Int
+ available: Boolean
+ activeStartDate: String
+ activeEndDate: String
+}
+
+type ProductOption {
+ id: ID!
+ label: String
+ type: String
+ required: Boolean
+ allowedValues: [ProductOptionValue!]
+}
+
+type ProductOptionValue {
+ id: ID!
+ attributeValue: String
+ displayOrder: Int
+ priceAdjustment: Money
+}
+
+type Order {
+ id: ID!
+ orderNumber: String
+ status: OrderStatus
+ subTotal: Money
+ total: Money
+ totalTax: Money
+ totalShipping: Money
+ emailAddress: String
+ orderItems: [OrderItem!]
+ fulfillmentGroups: [FulfillmentGroup!]
+ payments: [OrderPayment!]
+}
+
+enum OrderStatus {
+ IN_PROCESS
+ NAMED
+ SUBMITTED
+ CANCELLED
+ QUOTE
+ CSR_OWNED
+ ARCHIVED
+}
+
+type OrderItem {
+ id: ID!
+ name: String
+ quantity: Int
+ price: Money
+ salePrice: Money
+ retailPrice: Money
+ totalPrice: Money
+ product: Product
+ sku: Sku
+ orderItemAttributes: [OrderItemAttribute!]
+}
+
+type OrderItemAttribute {
+ id: ID!
+ name: String
+ value: String
+}
+
+type FulfillmentGroup {
+ id: ID!
+ address: Address
+ shippingPrice: Money
+ merchandiseTotal: Money
+ total: Money
+ type: String
+ fulfillmentGroupItems: [FulfillmentGroupItem!]
+}
+
+type FulfillmentGroupItem {
+ id: ID!
+ quantity: Int
+ totalItemAmount: Money
+ orderItem: OrderItem
+}
+
+type Address {
+ id: ID!
+ firstName: String
+ lastName: String
+ addressLine1: String
+ addressLine2: String
+ city: String
+ stateProvinceRegion: String
+ postalCode: String
+ country: String
+ phonePrimary: String
+}
+
+type OrderPayment {
+ id: ID!
+ amount: Money
+ type: String
+ gatewayType: String
+ referenceNumber: String
+}
+
+type Customer {
+ id: ID!
+ firstName: String
+ lastName: String
+ emailAddress: String
+ username: String
+}
+
+type PromoCodeResult {
+ order: Order
+ promoAdded: Boolean!
+ errorMessage: String
+}
+
+input AddToCartInput {
+ productId: ID!
+ skuId: ID
+ quantity: Int!
+ itemAttributes: [ItemAttributeInput!]
+}
+
+input ItemAttributeInput {
+ name: String!
+ value: String!
+}
+
+input ShippingInfoInput {
+ address: AddressInput!
+ fulfillmentOption: String
+}
+
+input AddressInput {
+ firstName: String
+ lastName: String
+ addressLine1: String!
+ addressLine2: String
+ city: String!
+ stateProvinceRegion: String!
+ postalCode: String!
+ country: String!
+ phonePrimary: String
+}
+
+input BillingInfoInput {
+ address: AddressInput!
+ paymentType: String!
+}
diff --git a/pom.xml b/pom.xml
index 6836d3b269..69bfa2a1a2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -581,6 +581,18 @@
test
+
+
+ org.springframework.boot
+ spring-boot-starter-graphql
+ ${spring.boot.version}
+
+
+ org.reactivestreams
+ reactive-streams
+ 1.0.4
+
+
org.hibernate