From 02ea792f1924d229da63a68c94441cd4e6fc6ab1 Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Tue, 5 May 2026 09:25:29 +0200 Subject: [PATCH] docs(sync): document foreign key resolvers Adds a new "Foreign key resolvers" section to the Sync API guide covering the {resolver, value, nullOnMissing} payload shape and listing the eight built-in resolvers (product.number plus the seven new tier-1 resolvers for currency, locale, payment_method, shipping_method, document_type, salutation, tax). Includes a warning about tax.tax_rate's non-unique column behavior. --- .../writing-entities/bulk-payloads.md | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/docs/concepts/endpoint-structure/writing-entities/bulk-payloads.md b/docs/concepts/endpoint-structure/writing-entities/bulk-payloads.md index 9d1582e..825c442 100644 --- a/docs/concepts/endpoint-structure/writing-entities/bulk-payloads.md +++ b/docs/concepts/endpoint-structure/writing-entities/bulk-payloads.md @@ -110,6 +110,76 @@ Writing entities is performed with `upsert` operation. This operation is used to } ``` +### Foreign key resolvers + +When importing data from an external system, you usually don't have Shopware's UUIDs at hand but you do have stable human-readable keys (SKUs, ISO codes, technical names). Foreign key resolvers let you reference an entity by such a key inside a Sync payload, and the API resolves it to the UUID before writing. + +Anywhere a UUID is expected, you can pass an object with `resolver` and `value`: + +```json +{ + "productId": { "resolver": "product.number", "value": "SW10001" } +} +``` + +If the value cannot be resolved, the request fails with `FRAMEWORK__API_INVALID_SYNC_RESOLVERS`. To allow missing references to fall back to `null` instead, add `"nullOnMissing": true`: + +```json +{ "currencyId": { "resolver": "currency.iso_code", "value": "XYZ", "nullOnMissing": true } } +``` + +#### Built-in resolvers + +| Resolver name | Entity | Looked up by | +|:--------------------------------|:------------------|:----------------------------------------------------------------------------| +| `product.number` | `product` | `productNumber` (unique per live version) | +| `currency.iso_code` | `currency` | `isoCode` (e.g. `EUR`, `USD`) | +| `locale.code` | `locale` | `code` (e.g. `en-GB`, `de-DE`) | +| `payment_method.technical_name` | `payment_method` | `technicalName` | +| `shipping_method.technical_name`| `shipping_method` | `technicalName` | +| `document_type.technical_name` | `document_type` | `technicalName` | +| `salutation.salutation_key` | `salutation` | `salutationKey` (e.g. `mr`, `mrs`) | +| `tax.tax_rate` | `tax` | `taxRate` — only resolves when **exactly one** tax record has the given rate | + + +> The `tax.tax_rate` resolver targets a non-unique column. If multiple tax records share the same rate (e.g. two 19% taxes), the resolver intentionally leaves the reference unresolved rather than picking one arbitrarily. Combine it with `nullOnMissing: true` or fall back to a `taxId` if your data model allows ambiguous rates. + +#### Example + +```sample http +{ + "method": "POST", + "url": "http://localhost/api/_action/sync", + "headers": { + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": "Bearer YOUR_ACCESS_TOKEN" + }, + "body": { + "import-products": { + "entity": "product", + "action": "upsert", + "payload": [ + { + "productNumber": "SW10001", + "name": "Imported product", + "stock": 10, + "price": [ + { + "gross": 19.99, + "net": 16.80, + "linked": false, + "currencyId": { "resolver": "currency.iso_code", "value": "EUR" } + } + ], + "taxId": { "resolver": "tax.tax_rate", "value": 19 } + } + ] + } + } +} +``` + ### Deleting entities To delete entities, the `payload` of an operation contains the IDs. If the entity is a `MappingEntityDefinition` \(e.g. `product_category`\) the foreign keys, which are the primary keys of the corresponding entities, must be passed.