@spree/docs 0.1.307 → 0.1.308

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,226 @@
1
+ ---
2
+ title: External References
3
+ description: Record the keys your ERP, PIM, DAM or CRM knows each record by, and address Spree records with those keys instead of Spree IDs.
4
+ ---
5
+
6
+ ## Overview
7
+
8
+ When Spree is connected to other systems, the same product lives in several places at once. Your ERP knows it as `MAT-100`, your PIM as `SKU-88412`, and Spree as `prod_86Rf07xd4z`.
9
+
10
+ External references are how Spree remembers those other keys. Each record can carry one key per system, and every integration can then find, create and update records using the key it already knows — without ever storing a Spree ID.
11
+
12
+ They solve three problems that come up in every integration:
13
+
14
+ - **Mapping.** Which Spree product is ERP item `MAT-100`?
15
+ - **Repeatable feeds.** A nightly import cannot know whether Spree has seen a row before. Sending the same row twice must update the record, not create a duplicate.
16
+ - **Several systems at once.** A PIM sync must never erase the key the ERP wrote.
17
+
18
+ ## How references are stored
19
+
20
+ ```mermaid
21
+ erDiagram
22
+ Product ||--o{ ExternalReference : "is known as"
23
+ Order ||--o{ ExternalReference : "is known as"
24
+ Company ||--o{ ExternalReference : "is known as"
25
+
26
+ ExternalReference {
27
+ string system
28
+ string external_id
29
+ json metadata
30
+ }
31
+ ```
32
+
33
+ An external reference has three parts:
34
+
35
+ | Field | What it holds | Example |
36
+ |---|---|---|
37
+ | `system` | Which system the key belongs to | `erp`, `pim`, `netsuite` |
38
+ | `external_id` | The key that system knows the record by | `MAT-100` |
39
+ | `metadata` | Optional bookkeeping for the integration (a version, an etag, when it last synced) | `{ "etag": "W/\"42\"" }` |
40
+
41
+ Two rules keep the mapping unambiguous, and the database enforces both:
42
+
43
+ - **One key per system per record.** A product has at most one `erp` key.
44
+ - **One record per key.** Within a store, `erp:MAT-100` names exactly one product.
45
+
46
+ ### System keys
47
+
48
+ A system key is a short lowercase name made of letters, digits and underscores, such as `erp`, `akeneo` or `legacy_pim`. Spree lowercases and trims what you send, so `ERP` and `erp` are the same system.
49
+
50
+ The system does not have to be a connected [integration](../providers/overview.md). A nightly CSV from a legacy system that has no live connection still needs somewhere to record its keys. When a connector does exist, use the same name it uses, so the connector, its references and its settings page all agree.
51
+
52
+ ## What can carry external references
53
+
54
+ | Record | Typical source of the key |
55
+ |---|---|
56
+ | Products | PIM or ERP item number |
57
+ | Variants | ERP or PIM SKU-level key |
58
+ | Categories | PIM category code |
59
+ | Media | DAM asset ID |
60
+ | Stock locations | ERP warehouse code |
61
+ | Stock levels | ERP inventory record |
62
+ | Orders | ERP sales order number, written back after the order is sent there |
63
+ | Companies | ERP or CRM account number (B2B) |
64
+ | Sellers | Payout provider account, recorded by the [payout provider](../providers/payouts.md) itself |
65
+
66
+ Customers are deliberately absent. A customer account can shop in more than one store, so a store-level key for it has no single owner. In B2B, the buyer's ERP identity belongs on their [company](companies.md) instead.
67
+
68
+ ## Writing references
69
+
70
+ Send `external_references` with any create or update on the Admin API. It accepts the same map the API returns:
71
+
72
+
73
+ ```typescript Admin SDK
74
+ const product = await adminClient.products.create({
75
+ name: 'Canvas Tote',
76
+ external_references: { erp: 'MAT-100', pim: 'SKU-88412' },
77
+ })
78
+ ```
79
+
80
+ ```bash CLI
81
+ spree api post /products -d '{
82
+ "name": "Canvas Tote",
83
+ "external_references": { "erp": "MAT-100", "pim": "SKU-88412" }
84
+ }'
85
+ ```
86
+
87
+
88
+ A list is accepted too, which some feeds find more natural:
89
+
90
+ ```json
91
+ {
92
+ "external_references": [
93
+ { "system": "erp", "external_id": "MAT-100" },
94
+ { "system": "pim", "external_id": "SKU-88412" }
95
+ ]
96
+ }
97
+ ```
98
+
99
+ What a write changes:
100
+
101
+ - **Systems you name are set.** A system the record has no key for gets one; a system it already has gets the new key.
102
+ - **Systems you don't name are left alone.** A PIM sending only `pim` never touches the `erp` key.
103
+ - **A blank key removes the reference.** Send `{ "erp": "" }` to unlink a record from the ERP.
104
+
105
+ ## Reading references
106
+
107
+ Admin API responses for every record above except sellers include `external_references` as a flat map — the same shape you send:
108
+
109
+ ```json Response
110
+ {
111
+ "id": "prod_86Rf07xd4z",
112
+ "name": "Canvas Tote",
113
+ "external_references": {
114
+ "erp": "MAT-100",
115
+ "pim": "SKU-88412"
116
+ }
117
+ }
118
+ ```
119
+
120
+ External references never appear in the Store API. Which ERP a merchant runs, and the keys records have in it, is back-office information.
121
+
122
+ In the dashboard, references show in the **JSON** view on each record's detail page. There is no form for editing them: they are written by integrations and read by whoever is debugging a sync.
123
+
124
+ ## Addressing records by your keys
125
+
126
+ For any record type listed in [What can carry external references](#what-can-carry-external-references), you can write `external:<system>:<key>` in the path instead of its Spree ID. An integration that only ever learned the ERP's key can then read, update and delete without first asking Spree for the record's ID.
127
+
128
+
129
+ ```typescript Admin SDK
130
+ const product = await adminClient.products.get('external:erp:MAT-100')
131
+
132
+ await adminClient.products.update('external:erp:MAT-100', {
133
+ name: 'Canvas Tote — Natural',
134
+ })
135
+ ```
136
+
137
+ ```bash CLI
138
+ spree api get /products/external:erp:MAT-100
139
+
140
+ spree api patch /products/external:erp:MAT-100 -d '{
141
+ "name": "Canvas Tote — Natural"
142
+ }'
143
+ ```
144
+
145
+
146
+ The lookup follows the same rules as a Spree ID. It only finds records in the current store, and an API key or admin user that cannot see the record gets a `404`, exactly as they would for its Spree ID.
147
+
148
+ > **WARNING:** Use this form only for keys made of letters, digits, dashes, underscores and colons. A key containing a slash or a dot cannot be part of a URL path; address that record by its Spree ID instead.
149
+
150
+ ## Repeatable feeds
151
+
152
+ A create that names a key Spree already knows **updates that record instead of creating a new one**. A feed can therefore send every row on every run, and the result is the same as if it had sent each row once.
153
+
154
+
155
+ ```typescript Admin SDK
156
+ // First run: creates the product.
157
+ // Every later run: updates the same product.
158
+ await adminClient.products.create({
159
+ name: 'Canvas Tote',
160
+ external_references: { erp: 'MAT-100' },
161
+ })
162
+ ```
163
+
164
+ ```bash CLI
165
+ spree api post /products -d '{
166
+ "name": "Canvas Tote",
167
+ "external_references": { "erp": "MAT-100" }
168
+ }'
169
+ ```
170
+
171
+
172
+ The update needs the same permission an ordinary update would, so a key that may create records but not edit them cannot use a create to change one.
173
+
174
+ Image feeds work the same way. Media created from a URL with `external_references` is recorded under those keys, and a later run with the same key finds the existing image instead of adding a second copy.
175
+
176
+ ## When a key is already taken
177
+
178
+ If an update names a key that already belongs to a **different** record, Spree keeps the record's other changes and answers with `422`:
179
+
180
+ ```json Response
181
+ {
182
+ "error": {
183
+ "code": "conflicting_external_reference",
184
+ "message": "External has already been taken"
185
+ }
186
+ }
187
+ ```
188
+
189
+ This usually means two records in Spree map to one record in the other system — for example, a product that was duplicated by hand. Remove the reference from one of them (send a blank key), then retry.
190
+
191
+ ## Filtering companies by key
192
+
193
+ Companies can be filtered by their references, which helps when reconciling a list of accounts against an ERP:
194
+
195
+ ```http
196
+ GET /api/v3/admin/companies?q[external_references_system_eq]=erp&q[external_references_external_id_eq]=ACME-1
197
+ ```
198
+
199
+ For every other record, look it up by its key with the `external:<system>:<key>` path form above.
200
+
201
+ ## References belong to a store
202
+
203
+ A reference is owned by the store its record belongs to. Two stores can each map `erp:MAT-100` to their own product without colliding, and a key from one store never finds a record in another. A feed that serves several stores sends its rows to each store separately.
204
+
205
+ ## External references, custom fields or metadata?
206
+
207
+ All three can hold "an ID from another system", but only one is built for it.
208
+
209
+ | | External references | [Custom fields](metafields.md) | [Metadata](../customization/metadata.md) |
210
+ |---|---|---|---|
211
+ | **For** | A record's key in another system | Data a merchant curates | Any data your code keeps |
212
+ | **Unique per store** | Yes, enforced | No | No |
213
+ | **Find a record by it** | Yes, in the URL path | Through filters | No |
214
+ | **Create-or-update by it** | Yes | No | No |
215
+ | **Several systems at once** | Yes, one key each | One field per system | Your own structure |
216
+ | **Store API** | Never | Configurable | Never |
217
+
218
+ If an integration needs to find a record again by a key it owns, use an external reference. If a merchant should see and edit the value, use a custom field. Anything else your code needs to remember belongs in metadata.
219
+
220
+ ## Related
221
+
222
+ - [ERP](../providers/erp.md) — feeding stock and orders from an ERP
223
+ - [PIM](../providers/pim.md) — syncing product content from a PIM
224
+ - [Imports & Exports](imports-exports.md) — file-based feeds
225
+ - [Companies](companies.md) — B2B accounts and their ERP identity
226
+ - [Admin SDK](../sdk/admin/resources.md) — the TypeScript client used in the examples
@@ -5,7 +5,7 @@ description: Add your own structured, typed data to products, orders and other r
5
5
 
6
6
  ## Overview
7
7
 
8
- Sooner or later you need to store something Spree doesn't have a field for. A fabric composition. A care instruction. A gift message. An ID from the system you sync with.
8
+ Sooner or later you need to store something Spree doesn't have a field for. A fabric composition. A care instruction. A gift message.
9
9
 
10
10
  Custom fields are how you do that without changing the database. You declare a field once — its name, its type, who can see it — and from then on it can be set on any record of that kind, edited in the dashboard, returned by the API, and searched or filtered like any built-in field.
11
11
 
@@ -209,11 +209,12 @@ Spree has two ways to store your own data, and they are not competing — they s
209
209
  | **Visibility** | Configurable per field | Never read by the Store API |
210
210
  | **Searchable** | Yes, opt in | Not through the product search |
211
211
 
212
- Put it simply: **custom fields are for people, metadata is for machines.** If a merchant should type it, define a custom field. If it is a sync token or an external ID that only your integration reads, use metadata.
212
+ Put it simply: **custom fields are for people, metadata is for machines.** If a merchant should type it, define a custom field. If it is a sync token that only your integration reads, use metadata. If it is the key another system knows the record by, use an [external reference](external-references.md).
213
213
 
214
214
  ## Related
215
215
 
216
216
  - [Metadata](../customization/metadata.md) — the machine-readable alternative
217
+ - [External References](external-references.md) — keys from your ERP, PIM or CRM
217
218
  - [Products](products.md) — the most common place for custom fields
218
219
  - [Search & Filtering](search-filtering.md) — how filters and search work
219
220
  - [Admin SDK](../sdk/admin/resources.md) — managing definitions and values in TypeScript
@@ -877,7 +877,7 @@ To add your own B2B cards, such as a credit account or an ERP reference, the com
877
877
  The B2B building blocks are ordinary Spree resources, so the usual customization paths apply:
878
878
 
879
879
  - **Events.** Companies, company invitations and catalogs publish lifecycle events, and orders publish `order.placed`. Subscribe to them to sync accounts and orders with an ERP or CRM. See [Events](../core-concepts/events.md).
880
- - **External references.** Companies accept `external_references`, so an ERP can address a company by its own account number.
880
+ - **[External references](../core-concepts/external-references.md).** Companies accept `external_references`, so an ERP can address a company by its own account number.
881
881
  - **Tax identifier validators.** Register a validator that checks numbers against a live registry.
882
882
  - **Checkout requirements.** Register an extra requirement, such as "a cost center is required", and it appears in the cart's `requirements` next to the built-in ones. See [Checkout customization](../customization/checkout.md).
883
883
 
@@ -26,7 +26,7 @@ Two ways in, both writing stock movements rather than overwriting numbers — so
26
26
 
27
27
  ## Addressing records by your keys
28
28
 
29
- The same product is known to your ERP and your PIM under different keys. External references are store-scoped mappings that let any feed address a Spree record as `external:<system>:<id>` — a create that names a key Spree already knows updates that record instead of failing.
29
+ The same product is known to your ERP and your PIM under different keys. External references are store-scoped mappings that let any feed address a Spree record as `external:<system>:<id>` — a create that names a key Spree already knows updates that record instead of failing. See [External References](../core-concepts/external-references.md).
30
30
 
31
31
  ## Checkout holds and failure policy
32
32
 
@@ -179,7 +179,7 @@ If your rails have no idempotency keys, look the movement up by that key (or by
179
179
 
180
180
  ## Seller accounts
181
181
 
182
- A seller's account with your provider is stored as an [external reference](erp.md#addressing-records-by-your-keys) filed under your `reference_system`, so a marketplace that migrates between providers keeps every account on record:
182
+ A seller's account with your provider is stored as an [external reference](../core-concepts/external-references.md) filed under your `reference_system`, so a marketplace that migrates between providers keeps every account on record:
183
183
 
184
184
  | Need | Call |
185
185
  |---|---|
@@ -11,7 +11,7 @@ Your PIM owns product content — names, descriptions, attributes, associations,
11
11
 
12
12
  ## Product data feeds
13
13
 
14
- Products, variants and prices flow in through the Admin API and CSV imports. Feeds address records by the keys your PIM already uses via external references (`external:<system>:<id>`), so an integration never has to store Spree IDs to update what it created.
14
+ Products, variants and prices flow in through the Admin API and CSV imports. Feeds address records by the keys your PIM already uses via [external references](../core-concepts/external-references.md) (`external:<system>:<id>`), so an integration never has to store Spree IDs to update what it created.
15
15
 
16
16
  ## The pricing provider
17
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spree/docs",
3
- "version": "0.1.307",
3
+ "version": "0.1.308",
4
4
  "description": "Spree Commerce developer documentation for AI agents and local reference",
5
5
  "type": "module",
6
6
  "license": "CC-BY-4.0",