Release notes
Release 1.50.1
2026-09-22
Fixed
-
shippingOptions,cartShippingOptions— a single shipping method that could not be assembled no longer fails the whole query. -
shippingOptions,shippingOption,shippingOptionSelections,cartShippingOptions— a failure while recording internal usage statistics no longer fails the query.
Release 1.49.1
2026-09-22
New
-
propertyConnection— a new paginated query for product property definitions. -
Availability.overrideVisible— tells you whether an availability status allows a product to be shown on its own product page even though it is hidden in listings. -
ProductFilter.overrideVisible— set totrueonproductConnectionto include products that are only partially visible, i.e. hidden from listings but reachable on their own product page. Default behaviour is unchanged.
Changed
-
product(id:)now returns products that are only visible on their own product page. Previously such a product resolved to nothing even when you had its id. Variant groups follow the same rule, so a variant that is only visible on its product page now resolves its group and siblings correctly. -
Shipping options are no longer filtered on the integration-type flag of the shipping method. All active methods on the cart's shipping class are now returned. If you have methods configured that never appeared in
shippingOptions, expect them to show up now.
Fixed
-
Amounts sent to Kustom and Svea were truncated rather than rounded when converted to minor units. A price like 16.99 could be sent as 1698 instead of 1699, which meant a payment total or a single order line could end up an öre below the corresponding cart total. Order lines, fees, totals, discount amounts and tax rates are all affected. Totals reported by the API itself were always correct — this was the amount handed to the payment provider.
-
The Svea invoice fee was returned with its inclusive price in minor units rather than in the currency, so a 29 kr fee appeared as 2900. Its tax
valuewas also left at zero and the taxpercentwas unrounded. The fee now reports a correct inclusive price, excluding price, tax value and tax percent, using the currency's standard VAT rate.
Documentation
-
CREATED_ATandUPDATED_ATin the product dynamic filter document that they accept a full datetime (2006-01-02T15:04:05Z07:00) as well as a plain date. Values given without a timezone are interpreted as UTC. This is a documentation correction; the behaviour was already there. -
productConnection(after:)— the description said the cursor was a customer ID. It is the product ID of the last item on the previous page.
Release 1.48.1
2026-09-17
Changed
-
BatchProductPropertyInput.valuenow accepts rich HTML. The field type changed fromString!toRichHTML!, so property values sent tobatchCreateProductsandbatchUpdateProductskeep safe formatting and media markup (<strong>,<em>,<p>,<br>, lists, links, images and similar) instead of having every tag stripped and<,>and&escaped into entities. Unsafe markup —<script>,<style>, event handler attributes and the like — is still removed.Breaking, for one case: if your operation declares the property value through a variable typed as
String!— for examplemutation ($value: String!)passed tovalue: $value— the operation no longer validates and will be rejected. Change the variable declaration toRichHTML!. Operations that pass the value inline, or that already use an untyped/matching declaration, are unaffected, and values that contain no HTML behave exactly as before apart from the whitespace note above.
Release 1.47.1
2026-09-17
Changed
ProductDynamicFilterField.ACTIVE(breaking) — the field now takes a boolean string:true,TRUEor1for active products,falseor0for cancelled ones. Previously it only accepted the two raw storage values, where the value for active was the counter-intuitive"N", so a filter that read as "active" in the query selected the opposite set. Those raw values are now rejected: the condition is dropped, the rest of the group still applies, and the response carries an error alongside the unfiltered result. If you filter onACTIVEtoday, switch totrue/false— otherwise your filter silently stops narrowing the result set.
Release 1.46.1
2026-09-16
New
Product.accessories(overrideVisible: Boolean)— new optional argument on the accessories field. Whentrue, an accessory list also returns products whose stock status hides them from normal listings but still marks them as viewable on their own product page. The argument defaults tofalse, so existing queries are unaffected.
Release 1.45.1
2026-09-15
New
ChosenBundlePrice.unitPrice— the price of one bundle at the requested quantity, alongside the existingtotalPrice.totalPriceisunitPricetimes the requested quantity, so quantity-dependent (tiered) bundle pricing is now visible per unit instead of only in the total.
Changed
salesStatisticsis now Stable — the query and itsSalesStatisticsDimension,SalesStatisticsMetric,SalesStatisticsRow, filter and input types are promoted from Upcoming to Stable. Behaviour is unchanged; the shape is now settled and will not change without notice. It still requires an admin user and an API key with theSTATISTICS_READpermission.
Fixed
-
Bundle rows in the cart were priced by the quantity twice.
CartRow.unitPricereturned the price of the whole row rather than of one bundle, andCartRow.pricethen multiplied that by the quantity again, so a bundle row with quantity 2 showed four times the bundle price, and quantity 3 nine times — inflating the cart total with it. Rows with quantity 1 were correct.unitPriceis now the price of one bundle andpriceis the row total. -
Bundle rows never showed a before-discount price. The regular price on a bundle cart row was copied from the sale price, so a discounted bundle looked undiscounted.
regularPriceon bothunitPriceandpricenow carries the real before-discount amount. -
A bundle cart row without its bundle data no longer fails the whole request. It now reports a warning for that row instead of aborting the query.
-
Customer.addressesno longer fails for customers with a missing address.
Release 1.44.1
2026-09-14
Changed
-
Postal code restrictions on shipping methods now match by prefix. A shipping method limited to
662now covers66234; previously the customer's whole postal code had to appear in the configured list, so prefix rules matched nothing and the method was offered or hidden for the wrong addresses. This affectsshippingOptions,shippingOptionandcartShippingOptions. If your store has postal code rules written as prefixes, the set of options a customer sees will change.. -
Currency.prefixandCurrency.suffixare null when not configured. They previously came back as empty strings. If you test for an empty string, test for null as well. -
Shipping options are resolved in batched queries. Validation and interval pricing for all methods in a request now run as one query each instead of one per method. Results are unchanged; responses are faster on stores with many shipping methods.
Fixed
-
Shipping prices carried an incomplete currency. The currency on a shipping option's price — and on a free-shipping price — was returned with only its code and rate, dropping
prefixandsuffix. All shipping prices now carry the full currency. -
A shipping method with a price interval but no price failed the whole request. When the interval matching the cart value had no price configured,
shippingOptionsreturned an error instead of any options. That method now falls back to its normal configured price. -
Bundles containing an always-buyable product were removed from the cart. When a bundle's chosen sub-product is buyable whether or not it is in stock, the reservation check still measured it against its stock balance, so the bundle row was reported as out of stock and removed once the reservation expired. If the bundle was the only row, the cart then held no rows and
cartcame back null — an emptied cart from the storefront's point of view. Such rows now survive bothreconcileCartReservationsand an ordinary cart read. -
Always-buyable products could not be added beyond their stock balance.
addToCartrejected them with an out-of-stock error even though the product is sold when out of stock. Products that require a reservation are still limited by what is actually available. -
A bundle category configured with a quantity of zero broke the reservation check.
reconcileCartReservationsfailed for any cart holding such a bundle. It now completes and leaves the row alone. -
Reservations could expire the moment they were extended. The new expiry was written from the API's clock and compared against the database's, so where the two ran in different time zones a reservation was stored as already expired — every cart read then reconciled the cart and adjusted it down. Expiry is now both written and read on the database clock, and the returned expiry is the value actually stored.
-
A removed bundle row now reports the sub-product that caused it. When several sub-products failed,
details.subProductIdcould name a later one instead of the one that triggered the removal. -
Kustom Checkout no longer loses its fallback shipping when the options cannot be determined.
Release 1.43.2
2026-09-09
No release notes.
Release 1.43.1
2026-09-09
New
-
productConnection(dynamicFilter: ProductDynamicFilter)— build your own product filters instead of picking from the fixed ones. A filter holdsgroups, each group holdsconditionsoffield,operationandvalues, and both levels combine withANDorOR(ANDwhen omitted), giving two levels of nesting.ProductDynamicFilterFieldcovers id, name, description, EAN, price, campaign price, stock, weight, brand, supplier, owner, category, default category, VAT, shipping class, availability, product type, active state, creation and update dates, and the language, price list and domain restrictions.ProductDynamicFilterOperationoffersEQUALS,CONTAINS,IN,LESS_THAN,LESS_THAN_OR_EQUAL,GREATER_THAN,GREATER_THAN_OR_EQUAL,EMPTYandNOT_EMPTY. Requires an admin user. It cannot be combined withfilter— passing both returns an error and no products.Values are always given as strings and converted per field; each enum value documents what it expects (an integer id, a float, a date such as
2006-01-02, or plain text). A condition with the wrong number of values for its operation, or a value that cannot be converted, is dropped: the query still returns the products matching the remaining conditions and reports the dropped ones as an error entry alongside the result, so checkerrorseven on a successful response. -
ProductFilter.categoryIdsandProductFilter.brandIds— narrowproductConnectionto products in the given categories or of the given brands, without reaching for the dynamic filter. -
Product.manufacturerandProduct.importer— the responsible economic operators behind a product, for EU General Product Safety Regulation notices.ProductManufacturerandProductImporterboth carryid,name,contactPerson,website,noteand anaddressof the new typeCONTACT, plusinternalName, which requires an admin user. -
OAuth 2.1 authorization server for administrator accounts. Administrators can now sign in to a third-party client through a browser redirect instead of the client handling their password. The server exposes
/oauth/authorize,/oauth/token,/oauth/registerand the discovery documents/.well-known/oauth-authorization-serverand/.well-known/jwks.json. It supports theauthorization_codeandrefresh_tokengrants, requires PKCE withS256, registers public clients dynamically with no client secret, and prompts for the second factor when the administrator has one configured. The access token you receive is the same token the GraphQL admin login issues and is accepted everywhere that one is, so no other part of your integration changes.
Changed
AddressTypehas a new value,CONTACT. It is used for manufacturer and importer addresses. Existing billing and shipping addresses are unaffected, but if your client rejects or crashes on unknown enum values, handle it before reading these fields.
Fixed
-
updateCustomersilently discarded most billing address fields. Only street address, postal code and city were saved onbillingAddress;firstName,lastName,streetAddress2,streetAddress3,houseNumber,houseExtension,phone,email,countryCodeandstateCodewere accepted and then dropped. OnstandardAddress,houseExtensionandstateCodewere dropped the same way. The mutation reported success throughout, so the values simply never appeared on the next read. All of these are now stored. -
Campaign prices depended on how products were paginated. When several products were fetched in one request, only one campaign offer per campaign survived, so a product could be given another product's campaign price or lose its campaign entirely — and the same product could come back with a different price on a different page or in a differently sized batch. Each product now gets its own offer.
-
A product with several campaigns of the same type got an arbitrary one. The tie-break between equally typed campaigns was unstable, so identical requests could return different campaign prices. The configured priority, and then the best price, now decide, and repeated requests agree.
-
LowestPrice.productIdslisted variants that were not at the lowest price.
Release 1.42.1
2026-08-28
Breaking
-
order(id:, checkoutId:)— thecheckoutIdargument is gone. It previously let a caller who was not logged in fetch an order by passing the matching checkout id, which is how order confirmation pages usually worked.ordernow always requires a logged-in user, and a customer token reaches only that customer's own orders. For a confirmation page, read the order throughcheckout(checkoutId:, secretKey:) { order { … } }, or take it fromfinalizeCheckoutwhen you pass the secret key. -
The
settingsquery has been removed, along with theSettingstype and theSettingsFilterinput.themeSettingsis unaffected -
createOrdernow requires an input with at least one row.inputchanged fromCreateOrderInputtoCreateOrderInput!, androwsfrom[OrderRowInput!]to[OrderRowInput!]!with a minimum of one. Calls that omitted either are now rejected during validation instead of failing later. -
Text you send is no longer HTML-escaped. Every
Stringinput is still stripped of HTML tags, but the characters that remain —&,<,>,",'— are now stored and returned as written. A customer namedO'Brienused to come back asO'Brien; it now comes back asO'Brien.
New
-
salesStatistics— aggregated sales figures in one query, grouped by up to threeSalesStatisticsDimensionvalues (day, week, month, year, country, brand, category, product, customer) over up to tenSalesStatisticsMetricvalues (quantity, revenue excluding and including tax, tax amount, order count, customer count, new customer count). Takes a requireddateRange, an optionalfilterandorderBy, and caps rows atlimit(default 1000, max 5000) with atruncatedflag when it bites. Requires an admin user and an API key with the newSTATISTICS_READpermission. Marked Upcoming — give feedback before it settles. -
orderWithdrawalConnectionandOrder.withdrawals— right-of-withdrawal cases with their status (NEW,HANDLED,REJECTED), customer comment, timestamps and the order rows they cover, filterable by withdrawal, order, customer, status and creation date. Needs an API key withORDER_READand, for an admin user,API_ORDER_MANAGE. A customer token sees only withdrawals on that customer's own orders. Thelogfield is admin-only. -
Signed shipping selections. Every
ShippingOption, selection and addon now carries asecureId— an encrypted token holding the option's id and price. Send the customer's choice back asCheckoutInput.selectedShipping(aSecuredSelectedShippingInputwith the option'ssecureIdplus any chosen addons and selection), and the API validates it and stores the result asCheckout.selectedShippingandOrder.selectedShipping: aSelectedShippingwithtotalPriceand abreakdownof option, addons and selection.validateShippingSelectionresolves a selection without storing it. Tokens expire — two hours by default, configurable per store — so re-fetch options if the customer sits on the checkout page. The oldshippingMethodIdandpickupPointIdfields still work but are deprecated. -
updatePageandupdateHomePage— update a page's or home page's content, active flag, and for home pages a publication timeslot, per language. Requires an admin user and an API key with theBETApermission. -
giftCardConnection(sort:)— sort gift cards byID,CREATED_ATorEXPIRES_ATin either direction. -
OrderRowInput.unitPrice— the price per single unit of an order row. The existingtotalPricewas always treated as a unit price despite its name; it is now deprecated andunitPricewins when both are given. -
RowCampaignType.BUY_X_PAY_Y— order and cart rows discounted by a buy-X-pay-Y campaign now report anappliedCampaignwith this type instead of none. -
X-User-Token— send the user's access token in this header.Authorizationstill works and now also accepts aBearerprefix, which was previously rejected as an invalid token.
Changed
-
An invalid user token now answers
401 Unauthorizedinstead of403 Forbidden. An invalid API key still answers 403. -
Shipping options and pickup points are no longer cached. Every request now goes to the carrier, so results are always current; expect responses to take longer where a cache used to answer. Carrier timeouts were raised from two to three seconds at the same time, so slow carriers now make it into the result more often instead of dropping out.
-
availableCampaigns.upsellCampaignsnow applies context rules when you filter byids. Passing ids used to bypass the check, returning campaigns that were not valid for the customer's domain, price list or country. -
Deprecations, all still functional:
Checkout.shippingMethodId,Checkout.pickupPointIdand the same two fields onCheckoutInput(useselectedShipping);Order.shippingDetails.pickupLocationand.shippingMethodId(useOrder.selectedShipping);OrderRowInput.totalPrice(useunitPrice); theshippingMethodsandpickupPointsqueries andShippingMethod.pickupPoints(usecartShippingOptions,shippingOptionsand a shipping option's selections).
Fixed
-
createOrderfailed when a row had no price. The input documented leaving the price empty to use contextual pricing, but doing so made the mutation fail. Rows without a price now use the product's current price for the request's currency and customer context, preferring the sale price. -
addToCartandremoveFromCartreported a misleading error. When the cart did not exist and the inventory check failed, the response also complained that the cart did not exist, burying the real reason. Thecartfield is now simply null and the inventory result carries the explanation. -
A payment provider hiccup could create a duplicate payment order. When updating an existing Svea checkout order came back as anything but success, the reference was discarded and a fresh order created, even if the original was alive. The existing order is now checked first and only replaced when it is genuinely cancelled or gone. A network failure to the provider also crashed the request instead of returning an error.
-
A postal code with no nearby DHL service points returned an error for the whole request. It now returns an empty list of selections.
-
An unrecognised country name on a customer or address made the query fail. Country lookup now also matches translated country names, and an unknown name resolves to no country instead of an error.
-
giftCardConnectionpaged incorrectly. Cursor paging withafterassumed ascending id order, so it skipped or repeated rows under any other ordering. Paging now follows the requested sort. -
Localized shop texts could come back in the wrong language. Texts were cached without the language, so whichever language asked first was served to everyone until the entry expired. This affected shipping addon titles, fee row names on orders and the contact form subject.
-
A discount code linked to a parent code ignored the parent's terms. Order-level discount amount, percentage and free shipping are now inherited from the parent code, so
CouponCodereports what the code actually does. -
Contact form submissions arrived with escaped characters and no line breaks. The mail is now sent as HTML with the submitted values in readable form.
-
New orders record their stated delivery time.
Order.shippingDetails.scheduledDeliveryAtis now filled in when an order is created, in the order's language, instead of being left empty.
Documentation
- Access restrictions are now stated in the schema for the operations that carry them, so
orderConnection,order,returnConnection,customerConnection, the batch mutations,createOrder,createBrandandjobStatussay in their descriptions whether they need an admin user and what a customer token sees. Several operations were also marked Stable:createOrder,createCustomer,updateCustomer,batchCreateCustomers,initiateResetCustomerPassword,submitContactForm,logoutandversion.