GraphQL v26.7+
Maho exposes the same resources over GraphQL as over REST, via API Platform's built-in GraphQL support. Every resource's fields, arguments, and types are introspectable from the schema.
Endpoint: POST /api/graphql Admin GraphQL: POST /api/admin/graphql (requires a fully-authenticated admin session)
Both are opt-in: enable the graphql / admin_graphql protocols under System → Configuration → Services → API → API Protocols. Authentication is the same JWT bearer token as REST (see Authentication); send it in the Authorization header.
# Query: fetch a product by its node IRI
query {
product(id: "/api/rest/v2/products/123") {
id
sku
name
price
}
}
# Mutation: update stock for a SKU
mutation {
updateStock(input: {sku: "ABC-123", qty: 50}) {
sku
qty
previousQty
success
}
}
Field naming
API Platform derives every custom GraphQL field name as lcfirst(operationName + ResourceShortName) - the resource noun is always appended. Only the reserved item/collection queries escape this and yield the clean singular/plural (product / products, order / orders). So a custom operation named setPaymentMethodOn on the Cart resource becomes the field setPaymentMethodOnCart; operations are named with a natural qualifier (a verb, or on / to / from / my / guest / current) so the appended noun reads well rather than stuttering (setPaymentMethodCart).
Two consequences worth knowing:
- The signed-in customer is
currentCustomer(notme), a guest cart/order isguestCart/guestOrder, and a caller's own collections aremyAddresses/myReviews/myWishlistItems/myRevocationRequests. - Natural-key lookups are collection filter arguments, not separate
by<X>queries. Filter the collection to fetch by a key:products(sku:),products(barcode:),categories(urlKey:),productAttributes(code:),cmsPages(identifier:),cmsBlocks(identifier:)- each returns the 0-or-1 match.
Reference
Item queries take a node IRI (id: "/api/rest/v2/products/123"); collection queries are plural and accept pagination plus any listed filter arguments. Admin-only queries/mutations require an admin or service token with the relevant permission (see Authentication).
Catalog
| Resource | Queries | Mutations |
|---|---|---|
| Product | product(id:), products - filters sku:, barcode:, search: | - (writes are REST-only) |
| Category | category(id:), categories - filter urlKey:, categoryProducts(id:) | - |
| Product attribute | productAttribute(id:), productAttributes - filter code: | - |
| Attribute set | attributeSet(id:), attributeSets | - |
| Layered filter | layeredFilter(id:), layeredFilters | - |
| Review | review(id:), reviews, productReviews(productId:), myReviews | submitReview |
Cart & checkout
| Resource | Queries | Mutations |
|---|---|---|
| Cart | cart(id:), carts, customerCart, guestCart(maskedId:) | createCart, addToCart, updateItemQtyInCart, removeItemFromCart, applyCouponToCart, removeCouponFromCart, setShippingAddressOnCart, setBillingAddressOnCart, setShippingMethodOnCart, setPaymentMethodOnCart, assignCustomerToCart, applyGiftcardToCart, removeGiftcardFromCart, setGiftMessageOnCart, removeGiftMessageFromCart |
Gift messages have no separate resource - they are the setGiftMessageOnCart / removeGiftMessageFromCart mutations above.
Customers
| Resource | Queries | Mutations |
|---|---|---|
| Customer | currentCustomer, customer(id:) (admin), customers (admin) | loginCustomer, logoutCustomer, quickCreateCustomer (register), updateCustomer, changePasswordCustomer, forgotPasswordCustomer, resetPasswordCustomer |
| Address | address(id:), addresses, myAddresses | createAddress, updateAddress, deleteAddress |
| Customer group | customerGroup(id:), customerGroups (admin) | - |
Sales
| Resource | Queries | Mutations |
|---|---|---|
| Order | order(id:), orders (admin), guestOrder(incrementId:, accessToken:), customerOrders | placeOrder, cancelOrder, holdOrder, unholdOrder, addCommentOrder |
| Shipment | shipment(id:), shipments, orderShipments(orderId:) | createShipment, addTrackShipment, removeTrackShipment |
| Credit memo | creditMemo(id:), creditMemos, orderCreditMemos(orderId:) | createCreditMemo |
| Revocation request | revocationRequest(id:), revocationRequests (admin), myRevocationRequests | submitRevocationRequest |
Inventory & promotions
| Resource | Queries | Mutations |
|---|---|---|
| Stock | stock(id:), stocks (admin) | updateStock, bulkUpdateStock |
| Coupon | coupon(id:), coupons (admin) | createCoupon, updateCoupon, deleteCoupon, validateCoupon |
| Gift card | giftCard(id:) (admin), giftCards (admin), checkBalanceGiftCard(code:) (public) | createGiftCard, adjustBalanceGiftCard |
checkBalanceGiftCard(code:) is the public balance check (returns balance + status with the code masked, no recipient/sender PII).
Content
| Resource | Queries | Mutations |
|---|---|---|
| CMS page | cmsPage(id:), cmsPages - filter identifier: | - (writes are REST-only) |
| CMS block | cmsBlock(id:), cmsBlocks - filter identifier: | - |
| Blog post | blogPost(id:), blogPosts | - |
| Blog category | blogCategory(id:), blogCategories | - |
Marketing
| Resource | Queries | Mutations |
|---|---|---|
| Newsletter | newsletter(id:) (admin), newsletters (admin), statusNewsletter | subscribeNewsletter, unsubscribeNewsletter |
| Wishlist item | wishlistItem(id:) (admin), wishlistItems (admin), myWishlistItems | addWishlistItem, removeWishlistItem, moveToCartWishlistItem, syncWishlistItem |
Store, directory & tax
| Resource | Queries | Mutations |
|---|---|---|
| Store config | currentStoreConfig, storeConfig(id:), storeConfigs | - |
| Country | country(id:), countries | - |
| Tax class / rate / rule (admin) | taxClass / taxClasses, taxRate / taxRates, taxRule / taxRules | - (writes are REST-only) |
| URL resolver | resolveUrlResolveResult(path:) | - |
resolveUrlResolveResult resolves a storefront path to its target entity. The doubled noun is unavoidable under the naming rule - naming it resolveUrl would force a Url type and a nonsense url / urls list query - so it's kept as-is.