@okendo/shopify-hydrogen 2.1.4 → 2.1.6

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.
package/README.md CHANGED
@@ -1,635 +1,881 @@
1
- > Note: this package is to be used on stores built with Hydrogen v2, based on Remix. If your store is built with the now deprecated Hydrogen v1, please use [version 1 of this package](https://www.npmjs.com/package/@okendo/shopify-hydrogen/v/1.3.0).
2
-
3
- # Okendo Hydrogen 2 (Remix) React Components
4
-
5
- This package brings [Okendo's review widgets](https://www.okendo.io/blog/widget-plus/) to a Shopify Hydrogen store.
6
-
7
- ## Requirements
8
-
9
- - A Shopify store with the [**Okendo: Product Reviews & UCG**](https://apps.shopify.com/okendo-reviews) app installed and configured.
10
- - For existing merchants, your store must be upgraded to Okendo's Widget Plus widgets. It is free to upgrade. For more information please [contact Okendo Support](mailto:support@okendo.io).
11
- - A current Okendo subscription.
12
- - A [Storefront access token](https://github.com/okendo/okendo-shopify-hydrogen-demo/wiki/Configure-Shopify-Storefront-API).
13
- - A [Shopify Hydrogen](https://hydrogen.shopify.dev/) app.
14
-
15
- ## Demo Store
16
-
17
- Our demo store, which is based on the demo store provided by Shopify, can be found [here](https://github.com/okendo/okendo-shopify-hydrogen-demo).
18
-
19
- > Note: there have been multiple versions of Shopify's Hydrogen demo store. If your project is based on an old version of it, consult our version history to find out how to add Okendo to it.
20
-
21
- ## How it works
22
-
23
- This package provides:
24
-
25
- - one function: `getOkendoProviderData`
26
- - three React components: `OkendoProvider`, `OkendoStarRating`, and `OkendoReviews`
27
-
28
- The function `getOkendoProviderData` needs to be called in the `loader` function of the `root.tsx` file in the Hydrogen 2 store. The data is then retrieved in `App` through `useLoaderData` and provided to `OkendoProvider` which is added in the `body` of the HTML returned by `App`.
29
-
30
- Then, the components `OkendoStarRating` and `OkendoReviews` can be added on the store pages. There are a few more bits of configuration to do, please see below.
31
-
32
- ## Expose Shopify Metafields <a id="expose-shopify-metafields" name="expose-shopify-metafields"></a>
33
-
34
- Okendo Reviews use Product and Shop [metafields](https://shopify.dev/api/examples/metafields). You will need to expose these metafields so that they can be retrieved by your Hydrogen app.
35
-
36
- At the moment, Shopify does not have a way of exposing Shop Metafields through their admin UI, so the preferred method is to [contact Okendo Support](mailto:support@okendo.io).
37
-
38
- <details>
39
- <summary>For technical users, follow this method to expose metafields via the storefront API</summary>
40
-
41
- ### Exposing Metafields via GraphQL
42
-
43
- #### Using Curl
44
-
45
- You can also expose the required Okendo Shopify metafields by using GraphQL with curl.
46
-
47
- 1. Open a new terminal or PowerShell window.
48
- 2. Run the following command to expose the `WidgetPreRenderStyleTags` shop metafield:
49
-
50
- ```bash
51
- curl -X POST \
52
- https://{shop}.myshopify.com/admin/api/2022-04/graphql.json \
53
- -H 'Content-Type: application/graphql' \
54
- -H 'X-Shopify-Access-Token: {access_token}' \
55
- -d '
56
- mutation {
57
- metafieldStorefrontVisibilityCreate(
58
- input: {
59
- namespace: "okendo"
60
- key: "WidgetPreRenderStyleTags"
61
- ownerType: SHOP
62
- }
63
- ) {
64
- metafieldStorefrontVisibility {
65
- id
66
- }
67
- userErrors {
68
- field
69
- message
70
- }
71
- }
72
- }
73
- '
74
- ```
75
-
76
- 3. Run the following command to expose the `WidgetPreRenderBodyStyleTags` shop metafield:
77
-
78
- ```bash
79
- curl -X POST \
80
- https://{shop}.myshopify.com/admin/api/2022-04/graphql.json \
81
- -H 'Content-Type: application/graphql' \
82
- -H 'X-Shopify-Access-Token: {access_token}' \
83
- -d '
84
- mutation {
85
- metafieldStorefrontVisibilityCreate(
86
- input: {
87
- namespace: "okendo"
88
- key: "WidgetPreRenderBodyStyleTags"
89
- ownerType: SHOP
90
- }
91
- ) {
92
- metafieldStorefrontVisibility {
93
- id
94
- }
95
- userErrors {
96
- field
97
- message
98
- }
99
- }
100
- }
101
- '
102
- ```
103
-
104
- 4. Run the following command to expose the `ReviewsWidgetSnippet` product metafield:
105
-
106
- ```bash
107
- curl -X POST \
108
- https://{shop}.myshopify.com/admin/api/2022-04/graphql.json \
109
- -H 'Content-Type: application/graphql' \
110
- -H 'X-Shopify-Access-Token: {access_token}' \
111
- -d '
112
- mutation {
113
- metafieldStorefrontVisibilityCreate(
114
- input: {
115
- namespace: "okendo"
116
- key: "ReviewsWidgetSnippet"
117
- ownerType: PRODUCT
118
- }
119
- ) {
120
- metafieldStorefrontVisibility {
121
- id
122
- }
123
- userErrors {
124
- field
125
- message
126
- }
127
- }
128
- }
129
- '
130
- ```
131
-
132
- 5. Run the following command to expose the `StarRatingSnippet` the product metafield:
133
-
134
- ```bash
135
- curl -X POST \
136
- https://{shop}.myshopify.com/admin/api/2022-04/graphql.json \
137
- -H 'Content-Type: application/graphql' \
138
- -H 'X-Shopify-Access-Token: {access_token}' \
139
- -d '
140
- mutation {
141
- metafieldStorefrontVisibilityCreate(
142
- input: {
143
- namespace: "okendo"
144
- key: "StarRatingSnippet"
145
- ownerType: PRODUCT
146
- }
147
- ) {
148
- metafieldStorefrontVisibility {
149
- id
150
- }
151
- userErrors {
152
- field
153
- message
154
- }
155
- }
156
- }
157
- '
158
- ```
159
-
160
- ### Using GraphQL IDE
161
-
162
- 1. Open your GraphQL IDE (such as Postman) and make a `POST` request with the following details:
163
-
164
- - **URL:** https://{shop}.myshopify.com/admin/api/2022-04/graphql.json
165
- - **Headers:** - X-Shopify-Access-Token: {access_token} - Content-Type: application/json
166
-
167
- 2. Execute the following request to expose the `WidgetPreRenderStyleTags` shop metafield:
168
-
169
- ```graphql
170
- mutation {
171
- metafieldStorefrontVisibilityCreate(
172
- input: {
173
- namespace: "okendo"
174
- key: "WidgetPreRenderStyleTags"
175
- ownerType: SHOP
176
- }
177
- ) {
178
- metafieldStorefrontVisibility {
179
- id
180
- }
181
- userErrors {
182
- field
183
- message
184
- }
185
- }
186
- }
187
- ```
188
-
189
- 3. Execute the following request to expose the `WidgetPreRenderBodyStyleTags` shop metafield:
190
-
191
- ```graphql
192
- mutation {
193
- metafieldStorefrontVisibilityCreate(
194
- input: {
195
- namespace: "okendo"
196
- key: "WidgetPreRenderBodyStyleTags"
197
- ownerType: SHOP
198
- }
199
- ) {
200
- metafieldStorefrontVisibility {
201
- id
202
- }
203
- userErrors {
204
- field
205
- message
206
- }
207
- }
208
- }
209
- ```
210
-
211
- 4. Execute the following request to expose the `ReviewsWidgetSnippet` product metafield:
212
-
213
- ```graphql
214
- mutation {
215
- metafieldStorefrontVisibilityCreate(
216
- input: {
217
- namespace: "okendo"
218
- key: "ReviewsWidgetSnippet"
219
- ownerType: PRODUCT
220
- }
221
- ) {
222
- metafieldStorefrontVisibility {
223
- id
224
- }
225
- userErrors {
226
- field
227
- message
228
- }
229
- }
230
- }
231
- ```
232
-
233
- 5. Execute the following request to expose the `StarRatingSnippet` the product metafield:
234
-
235
- ```graphql
236
- mutation {
237
- metafieldStorefrontVisibilityCreate(
238
- input: { namespace: "okendo", key: "StarRatingSnippet", ownerType: PRODUCT }
239
- ) {
240
- metafieldStorefrontVisibility {
241
- id
242
- }
243
- userErrors {
244
- field
245
- message
246
- }
247
- }
248
- }
249
- ```
250
-
251
- **References**
252
-
253
- - [https://shopify.dev/api/examples/metafields#step-1-expose-metafields](https://shopify.dev/api/examples/metafields#step-1-expose-metafields)
254
- - [https://shopify.dev/api/admin-graphql/2022-04/mutations/metafieldstorefrontvisibilitycreate](https://shopify.dev/api/admin-graphql/2022-04/mutations/metafieldstorefrontvisibilitycreate)
255
- </details>
256
-
257
- ## Installation
258
-
259
- > The code examples provided in this section are based on the Shopify template store created by running `npm create @shopify/hydrogen@latest` (see [Shopify's documentation](https://shopify.dev/docs/custom-storefronts/hydrogen/getting-started)). You will find the following steps already done in [our demo store](https://github.com/okendo/okendo-shopify-hydrogen-demo).
260
-
261
- Run:
262
-
263
- ```bash
264
- npm i @okendo/shopify-hydrogen
265
- ```
266
-
267
- ### `app/root.tsx`
268
-
269
- Open `app/root.tsx` and add the following import:
270
-
271
- ```ts
272
- import {
273
- OkendoProvider,
274
- getOkendoProviderData,
275
- } from "@okendo/shopify-hydrogen";
276
- ```
277
-
278
- Locate the `loader` function, append `okendoProviderData` to the returned data as shown below, and set `subscriberId` to your Okendo subscriber ID:
279
-
280
- ```ts
281
- return defer(
282
- {
283
- ...
284
- okendoProviderData: await getOkendoProviderData({
285
- context,
286
- subscriberId: "<your-okendo-subscriber-id>",
287
- }),
288
- }
289
- );
290
- ```
291
-
292
- Locate the `App` function, add the `meta` tag `oke:subscriber_id` to `head`, and place your Okendo subscriber ID in its content:
293
-
294
- ```ts
295
- <head>
296
- <meta charSet="utf-8" />
297
- <meta name="viewport" content="width=device-width,initial-scale=1" />
298
- <meta name="oke:subscriber_id" content="<your-okendo-subscriber-id>" />
299
- ...
300
- ```
301
-
302
- Append `OkendoProvider` to `body`, and pass it the data returned by `getOkendoProviderData`. If Content Security Policy is active in your project, you also need to provide the `nonce` (available with `const nonce = useNonce()` in Shopify's Hydrogen demo store):
303
-
304
- ```tsx
305
- ...
306
- <body>
307
- <OkendoProvider
308
- nonce={nonce}
309
- okendoProviderData={data.okendoProviderData}
310
- />
311
- ...
312
- </body>
313
- ...
314
- ```
315
-
316
- ### `app/entry.server.tsx`
317
-
318
- > This is only necessary if Content Security Policy is active in your project.
319
-
320
- Locate the call to `createContentSecurityPolicy`, and add:
321
-
322
- - `https://d3hw6dc1ow8pp2.cloudfront.net`, `https://d3g5hqndtiniji.cloudfront.net`, and `data:` to `defaultSrc`
323
- - `https://d3hw6dc1ow8pp2.cloudfront.net` to `styleSrc`
324
- - `https://api.okendo.io` to `connectSrc`
325
-
326
- Note that it's necessary to to add the default values (`'self'`, etc.) when [extending the CSP](https://shopify.dev/docs/custom-storefronts/hydrogen/content-security-policy). The call to `createContentSecurityPolicy` should now look like the following:
327
-
328
- ```ts
329
- const { nonce, header, NonceProvider } = createContentSecurityPolicy({
330
- defaultSrc: [
331
- "'self'",
332
- "localhost:*",
333
- "https://cdn.shopify.com",
334
- "https://d3hw6dc1ow8pp2.cloudfront.net",
335
- "https://d3g5hqndtiniji.cloudfront.net",
336
- "https://cdn-static.okendo.io",
337
- "https://surveys.okendo.io",
338
- "data:",
339
- ],
340
- imgSrc: [
341
- "'self'",
342
- "https://cdn.shopify.com",
343
- "data:",
344
- "https://d3hw6dc1ow8pp2.cloudfront.net",
345
- "https://d3g5hqndtiniji.cloudfront.net",
346
- "https://cdn-static.okendo.io",
347
- "https://surveys.okendo.io",
348
- ],
349
- mediaSrc: [
350
- "'self'",
351
- "https://d3hw6dc1ow8pp2.cloudfront.net",
352
- "https://d3g5hqndtiniji.cloudfront.net",
353
- "https://cdn-static.okendo.io"
354
- ],
355
- styleSrcElem: [
356
- "'self'",
357
- "'unsafe-inline'",
358
- "https://cdn.shopify.com",
359
- "https://fonts.googleapis.com",
360
- "https://fonts.gstatic.com",
361
- "https://d3hw6dc1ow8pp2.cloudfront.net",
362
- "https://cdn-static.okendo.io",
363
- "https://surveys.okendo.io",
364
- ],
365
- scriptSrc: [
366
- "'self'",
367
- "https://cdn.shopify.com",
368
- "https://d3hw6dc1ow8pp2.cloudfront.net",
369
- "https://cdn-static.okendo.io",
370
- "https://surveys.okendo.io",
371
- ],
372
- fontSrc: [
373
- "'self'",
374
- "https://fonts.gstatic.com",
375
- "https://d3hw6dc1ow8pp2.cloudfront.net",
376
- "https://cdn.shopify.com",
377
- "https://cdn-static.okendo.io",
378
- "https://surveys.okendo.io",
379
- ],
380
- connectSrc: [
381
- "'self'",
382
- "https://monorail-edge.shopifysvc.com",
383
- "localhost:*",
384
- "ws://localhost:*",
385
- "ws://127.0.0.1:*",
386
- "https://api.okendo.io",
387
- "https://cdn-static.okendo.io",
388
- "https://surveys.okendo.io",
389
- ],
390
- });
391
- ```
392
-
393
- ### `app/routes/_index.tsx`
394
-
395
- Add the following imports:
396
-
397
- ```ts
398
- import {
399
- OkendoStarRating,
400
- type WithOkendoStarRatingSnippet,
401
- } from "@okendo/shopify-hydrogen";
402
- ```
403
-
404
- Add the following block just before the `RECOMMENDED_PRODUCTS_QUERY` GraphQL query:
405
-
406
- ```ts
407
- const OKENDO_PRODUCT_STAR_RATING_FRAGMENT = `#graphql
408
- fragment OkendoStarRatingSnippet on Product {
409
- okendoStarRatingSnippet: metafield(
410
- namespace: "okendo"
411
- key: "StarRatingSnippet"
412
- ) {
413
- value
414
- }
415
- }
416
- ` as const;
417
- ```
418
-
419
- Then append `${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}` and `...OkendoStarRatingSnippet` to `RECOMMENDED_PRODUCTS_QUERY`:
420
-
421
- ```ts
422
- const RECOMMENDED_PRODUCTS_QUERY = `#graphql
423
- ${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}
424
- fragment RecommendedProduct on Product {
425
- id
426
- title
427
- handle
428
- priceRange {
429
- minVariantPrice {
430
- amount
431
- currencyCode
432
- }
433
- }
434
- images(first: 1) {
435
- nodes {
436
- id
437
- url
438
- altText
439
- width
440
- height
441
- }
442
- }
443
- ...OkendoStarRatingSnippet
444
- }
445
- query RecommendedProducts ($country: CountryCode, $language: LanguageCode)
446
- @inContext(country: $country, language: $language) {
447
- products(first: 4, sortKey: UPDATED_AT, reverse: true) {
448
- nodes {
449
- ...RecommendedProduct
450
- }
451
- }
452
- }
453
- ` as const;
454
- ```
455
-
456
- Tweak the type of the `products` prop of `RecommendedProducts`:
457
-
458
- ```ts
459
- products: Promise<{
460
- products: {
461
- nodes: (RecommendedProductsQuery["products"]["nodes"][0] &
462
- WithOkendoStarRatingSnippet)[];
463
- };
464
- }>;
465
- ```
466
-
467
- Add `OkendoStarRating` to `RecommendedProducts`:
468
-
469
- ```tsx
470
- <OkendoStarRating
471
- productId={product.id}
472
- okendoStarRatingSnippet={product.okendoStarRatingSnippet}
473
- />
474
- ```
475
-
476
- For instance, we can add it below the product title, like this:
477
-
478
- ```tsx
479
- <Image
480
- data={product.images.nodes[0]}
481
- aspectRatio="1/1"
482
- sizes="(min-width: 45em) 20vw, 50vw"
483
- />
484
- <h4>{product.title}</h4>
485
- <OkendoStarRating
486
- productId={product.id}
487
- okendoStarRatingSnippet={product.okendoStarRatingSnippet}
488
- />
489
- <small>
490
- <Money data={product.priceRange.minVariantPrice} />
491
- </small>
492
- ```
493
-
494
- We now have the Okendo Star Rating widget visible on our page:
495
-
496
- ![Okendo's Star Rating widget](./okendo-star-rating-widget.png)
497
-
498
- ### `app/routes/products.$handle.tsx`
499
-
500
- Add the following imports:
501
-
502
- ```ts
503
- import {
504
- OKENDO_PRODUCT_REVIEWS_FRAGMENT,
505
- OKENDO_PRODUCT_STAR_RATING_FRAGMENT,
506
- OkendoReviews,
507
- OkendoStarRating,
508
- type WithOkendoReviewsSnippet,
509
- type WithOkendoStarRatingSnippet,
510
- } from "@okendo/shopify-hydrogen";
511
- ```
512
-
513
- Add the following block just before the `RECOMMENDED_PRODUCTS_QUERY` GraphQL query:
514
-
515
- ```ts
516
- const OKENDO_PRODUCT_STAR_RATING_FRAGMENT = `#graphql
517
- fragment OkendoStarRatingSnippet on Product {
518
- okendoStarRatingSnippet: metafield(
519
- namespace: "okendo"
520
- key: "StarRatingSnippet"
521
- ) {
522
- value
523
- }
524
- }
525
- ` as const;
526
-
527
- const OKENDO_PRODUCT_REVIEWS_FRAGMENT = `#graphql
528
- fragment OkendoReviewsSnippet on Product {
529
- okendoReviewsSnippet: metafield(
530
- namespace: "okendo"
531
- key: "ReviewsWidgetSnippet"
532
- ) {
533
- value
534
- }
535
- }
536
- ` as const;
537
- ```
538
-
539
- Then append `${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}`, `${OKENDO_PRODUCT_REVIEWS_FRAGMENT}`, `...OkendoStarRatingSnippet`, and `...OkendoReviewsSnippet` to `PRODUCT_FRAGMENT`:
540
-
541
- ```ts
542
- const PRODUCT_FRAGMENT = `#graphql
543
- ${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}
544
- ${OKENDO_PRODUCT_REVIEWS_FRAGMENT}
545
- fragment Product on Product {
546
- id
547
- title
548
- vendor
549
- handle
550
- descriptionHtml
551
- description
552
- options {
553
- name
554
- values
555
- }
556
- selectedVariant: variantBySelectedOptions(selectedOptions: $selectedOptions) {
557
- ...ProductVariant
558
- }
559
- variants(first: 1) {
560
- nodes {
561
- ...ProductVariant
562
- }
563
- }
564
- seo {
565
- description
566
- title
567
- }
568
- ...OkendoStarRatingSnippet
569
- ...OkendoReviewsSnippet
570
- }
571
- ${PRODUCT_VARIANT_FRAGMENT}
572
- ` as const;
573
- ```
574
-
575
- Add `OkendoReviews` to `Product`:
576
-
577
- ```tsx
578
- <OkendoReviews
579
- productId={product.id}
580
- okendoReviewsSnippet={product.okendoReviewsSnippet}
581
- />
582
- ```
583
-
584
- For instance, we can add it below the product section, like this:
585
-
586
- ```tsx
587
- <>
588
- <div className="product">
589
- <ProductImage image={selectedVariant?.image} />
590
- <ProductMain
591
- selectedVariant={selectedVariant}
592
- product={product}
593
- variants={variants}
594
- />
595
- </div>
596
-
597
- <OkendoReviews
598
- productId={product.id}
599
- okendoReviewsSnippet={product.okendoReviewsSnippet}
600
- />
601
- </>
602
- ```
603
-
604
- Tweak the type of the `product` prop of `ProductMain`:
605
-
606
- ```ts
607
- product: ProductFragment &
608
- WithOkendoStarRatingSnippet &
609
- WithOkendoReviewsSnippet;
610
- ```
611
-
612
- Add `OkendoStarRating` to `ProductMain`:
613
-
614
- ```tsx
615
- <OkendoStarRating
616
- productId={product.id}
617
- okendoStarRatingSnippet={product.okendoStarRatingSnippet}
618
- />
619
- ```
620
-
621
- For instance, we can add it below the product title, like this:
622
-
623
- ```tsx
624
- <div className="product-main">
625
- <h1>{title}</h1>
626
- <OkendoStarRating
627
- productId={product.id}
628
- okendoStarRatingSnippet={product.okendoStarRatingSnippet}
629
- />
630
- <ProductPrice selectedVariant={selectedVariant} />
631
- ```
632
-
633
- We now have the Okendo Star Rating and Reviews widgets visible on our product page:
634
-
635
- ![Okendo's Star Rating and Reviews widgets](./okendo-star-rating-and-reviews-widgets.png)
1
+ > Note: this package is to be used on stores built with Hydrogen v2, based on Remix. If your store is built with the now deprecated Hydrogen v1, please use [version 1 of this package](https://www.npmjs.com/package/@okendo/shopify-hydrogen/v/1.3.0).
2
+
3
+ # Okendo Hydrogen 2 (Remix) React Components
4
+
5
+ This package brings [Okendo's review widgets](https://www.okendo.io/blog/widget-plus/) to a Shopify Hydrogen store.
6
+
7
+ ## Requirements
8
+
9
+ - A Shopify store with the [**Okendo: Product Reviews & UCG**](https://apps.shopify.com/okendo-reviews) app installed and configured.
10
+ - For existing merchants, your store must be upgraded to Okendo's Widget Plus widgets. It is free to upgrade. For more information please [contact Okendo Support](mailto:support@okendo.io).
11
+ - A current Okendo subscription.
12
+ - A [Shopify Hydrogen](https://hydrogen.shopify.dev/) app.
13
+
14
+ ## Demo Store
15
+
16
+ Our demo store, which is based on the demo store provided by Shopify, can be found [here](https://github.com/okendo/okendo-shopify-hydrogen-demo).
17
+
18
+ > Note: there have been multiple versions of Shopify's Hydrogen demo store. If your project is based on an old version of it, consult our version history to find out how to add Okendo to it.
19
+
20
+ ## How it works
21
+
22
+ This package provides:
23
+
24
+ - one function: `getOkendoProviderData`
25
+ - three React components: `OkendoProvider`, `OkendoStarRating`, and `OkendoReviews`
26
+
27
+ The function `getOkendoProviderData` needs to be called in the `loader` function of the `root.tsx` file in the Hydrogen 2 store. The data is then retrieved in `App` through `useLoaderData` and provided to `OkendoProvider` which is added in the `body` of the HTML returned by `App`.
28
+
29
+ Then, the components `OkendoStarRating` and `OkendoReviews` can be added on the store pages. There are a few more bits of configuration to do, please see below.
30
+
31
+ ## Expose Shopify Metafields <a id="expose-shopify-metafields" name="expose-shopify-metafields"></a>
32
+
33
+ Okendo Reviews use Product and Shop [metafields](https://shopify.dev/api/examples/metafields). You will need to expose these metafields so that they can be retrieved by your Hydrogen app.
34
+
35
+ At the moment, Shopify does not have a way of exposing Shop Metafields through their admin UI, so the preferred method is to [contact Okendo Support](mailto:support@okendo.io).
36
+
37
+ <details>
38
+
39
+ <summary>If you're a technical user however, you can click here and follow the method to expose the metafields via the storefront API.</summary>
40
+
41
+ ### Exposing Metafields via GraphQL
42
+
43
+ You will need a **Storefront access token** with the following API access scopes:
44
+
45
+ ```
46
+ unauthenticated_read_content
47
+ unauthenticated_read_customers
48
+ unauthenticated_read_product_listings
49
+ unauthenticated_read_product_inventory
50
+ unauthenticated_read_product_pickup_locations
51
+ unauthenticated_read_product_tags
52
+ ```
53
+
54
+ Follow the instructions on [this page](https://help.shopify.com/en/manual/apps/app-types/custom-apps#create-and-install-a-custom-app) to create it.
55
+
56
+ #### Note
57
+
58
+ Shopify is in the process of deprecating `metafieldStorefrontVisibilityCreate` in favour of `metafieldDefinitionCreate`. The following method uses `metafieldDefinitionCreate`. If you're having trouble with it, you will find below the deprecated method using `metafieldStorefrontVisibilityCreate`.
59
+
60
+ #### Using Curl
61
+
62
+ Open a new terminal or PowerShell window, then:
63
+
64
+ 1. Run the following command to expose the `WidgetPreRenderStyleTags` shop metafield:
65
+
66
+ ```bash
67
+ curl -X POST \
68
+ https://{shop}.myshopify.com/admin/api/2023-07/graphql.json \
69
+ -H 'Content-Type: application/graphql' \
70
+ -H 'X-Shopify-Access-Token: {access_token}' \
71
+ -d '
72
+ mutation {
73
+ metafieldDefinitionCreate(
74
+ definition: {
75
+ name: "WidgetPreRenderStyleTags"
76
+ namespace: "okendo"
77
+ key: "WidgetPreRenderStyleTags"
78
+ type: "multi_line_text_field"
79
+ ownerType: SHOP
80
+ visibleToStorefrontApi: true
81
+ }
82
+ ) {
83
+ createdDefinition { id name }
84
+ userErrors { field message code }
85
+ }
86
+ }
87
+ '
88
+ ```
89
+
90
+ 2. Run the following command to expose the `WidgetPreRenderBodyStyleTags` shop metafield:
91
+
92
+ ```bash
93
+ curl -X POST \
94
+ https://{shop}.myshopify.com/admin/api/2023-07/graphql.json \
95
+ -H 'Content-Type: application/graphql' \
96
+ -H 'X-Shopify-Access-Token: {access_token}' \
97
+ -d '
98
+ mutation {
99
+ metafieldDefinitionCreate(
100
+ definition: {
101
+ name: "WidgetPreRenderBodyStyleTags"
102
+ namespace: "okendo"
103
+ key: "WidgetPreRenderBodyStyleTags"
104
+ type: "multi_line_text_field"
105
+ ownerType: SHOP
106
+ visibleToStorefrontApi: true
107
+ }
108
+ ) {
109
+ createdDefinition { id name }
110
+ userErrors { field message code }
111
+ }
112
+ }
113
+ '
114
+ ```
115
+
116
+ 3. Run the following command to expose the `ReviewsWidgetSnippet` product metafield:
117
+
118
+ ```bash
119
+ curl -X POST \
120
+ https://{shop}.myshopify.com/admin/api/2023-07/graphql.json \
121
+ -H 'Content-Type: application/graphql' \
122
+ -H 'X-Shopify-Access-Token: {access_token}' \
123
+ -d '
124
+ mutation {
125
+ metafieldDefinitionCreate(
126
+ definition: {
127
+ name: "ReviewsWidgetSnippet"
128
+ namespace: "okendo"
129
+ key: "ReviewsWidgetSnippet"
130
+ type: "multi_line_text_field"
131
+ ownerType: PRODUCT
132
+ visibleToStorefrontApi: true
133
+ }
134
+ ) {
135
+ createdDefinition { id name }
136
+ userErrors { field message code }
137
+ }
138
+ }
139
+ '
140
+ ```
141
+
142
+ 4. Run the following command to expose the `StarRatingSnippet` the product metafield:
143
+
144
+ ```bash
145
+ curl -X POST \
146
+ https://{shop}.myshopify.com/admin/api/2023-07/graphql.json \
147
+ -H 'Content-Type: application/graphql' \
148
+ -H 'X-Shopify-Access-Token: {access_token}' \
149
+ -d '
150
+ mutation {
151
+ metafieldDefinitionCreate(
152
+ definition: {
153
+ name: "StarRatingSnippet"
154
+ namespace: "okendo"
155
+ key: "StarRatingSnippet"
156
+ type: "multi_line_text_field"
157
+ ownerType: PRODUCT
158
+ visibleToStorefrontApi: true
159
+ }
160
+ ) {
161
+ createdDefinition { id name }
162
+ userErrors { field message code }
163
+ }
164
+ }
165
+ '
166
+ ```
167
+
168
+ ### Using GraphQL IDE
169
+
170
+ Open your GraphQL IDE (such as Postman) and make `POST` requests with the following details:
171
+
172
+ - **URL:** https://{shop}.myshopify.com/admin/api/2023-07/graphql.json
173
+ - **Headers:** - X-Shopify-Access-Token: {access_token} - Content-Type: application/json
174
+
175
+ 1. Execute the following request to expose the `WidgetPreRenderStyleTags` shop metafield:
176
+
177
+ ```graphql
178
+ mutation {
179
+ metafieldDefinitionCreate(
180
+ definition: {
181
+ name: "WidgetPreRenderStyleTags"
182
+ namespace: "okendo"
183
+ key: "WidgetPreRenderStyleTags"
184
+ type: "multi_line_text_field"
185
+ ownerType: SHOP
186
+ visibleToStorefrontApi: true
187
+ }
188
+ ) {
189
+ createdDefinition {
190
+ id
191
+ name
192
+ }
193
+ userErrors {
194
+ field
195
+ message
196
+ code
197
+ }
198
+ }
199
+ }
200
+ ```
201
+
202
+ 2. Execute the following request to expose the `WidgetPreRenderBodyStyleTags` shop metafield:
203
+
204
+ ```graphql
205
+ mutation {
206
+ metafieldDefinitionCreate(
207
+ definition: {
208
+ name: "WidgetPreRenderBodyStyleTags"
209
+ namespace: "okendo"
210
+ key: "WidgetPreRenderBodyStyleTags"
211
+ type: "multi_line_text_field"
212
+ ownerType: SHOP
213
+ visibleToStorefrontApi: true
214
+ }
215
+ ) {
216
+ createdDefinition {
217
+ id
218
+ name
219
+ }
220
+ userErrors {
221
+ field
222
+ message
223
+ code
224
+ }
225
+ }
226
+ }
227
+ ```
228
+
229
+ 3. Execute the following request to expose the `ReviewsWidgetSnippet` product metafield:
230
+
231
+ ```graphql
232
+ mutation {
233
+ metafieldDefinitionCreate(
234
+ definition: {
235
+ name: "ReviewsWidgetSnippet"
236
+ namespace: "okendo"
237
+ key: "ReviewsWidgetSnippet"
238
+ type: "multi_line_text_field"
239
+ ownerType: PRODUCT
240
+ visibleToStorefrontApi: true
241
+ }
242
+ ) {
243
+ createdDefinition {
244
+ id
245
+ name
246
+ }
247
+ userErrors {
248
+ field
249
+ message
250
+ code
251
+ }
252
+ }
253
+ }
254
+ ```
255
+
256
+ 4. Execute the following request to expose the `StarRatingSnippet` the product metafield:
257
+
258
+ ```graphql
259
+ mutation {
260
+ metafieldDefinitionCreate(
261
+ definition: {
262
+ name: "StarRatingSnippet"
263
+ namespace: "okendo"
264
+ key: "StarRatingSnippet"
265
+ type: "multi_line_text_field"
266
+ ownerType: PRODUCT
267
+ visibleToStorefrontApi: true
268
+ }
269
+ ) {
270
+ createdDefinition {
271
+ id
272
+ name
273
+ }
274
+ userErrors {
275
+ field
276
+ message
277
+ code
278
+ }
279
+ }
280
+ }
281
+ ```
282
+
283
+ **References**
284
+
285
+ - [https://shopify.dev/api/examples/metafields#step-1-expose-metafields](https://shopify.dev/api/examples/metafields#step-1-expose-metafields)
286
+ - [https://shopify.dev/api/admin-graphql/2023-07/mutations/metafieldDefinitionCreate](https://shopify.dev/api/admin-graphql/2023-07/mutations/metafieldDefinitionCreate)
287
+
288
+ <details>
289
+
290
+ <summary>If you're having trouble with `metafieldDefinitionCreate`, click here to see the deprecated method, using `metafieldStorefrontVisibilityCreate`.</summary>
291
+
292
+ #### Using Curl
293
+
294
+ Open a new terminal or PowerShell window, then:
295
+
296
+ 1. Run the following command to expose the `WidgetPreRenderStyleTags` shop metafield:
297
+
298
+ ```bash
299
+ curl -X POST \
300
+ https://{shop}.myshopify.com/admin/api/2023-07/graphql.json \
301
+ -H 'Content-Type: application/graphql' \
302
+ -H 'X-Shopify-Access-Token: {access_token}' \
303
+ -d '
304
+ mutation {
305
+ metafieldStorefrontVisibilityCreate(
306
+ input: {
307
+ namespace: "okendo"
308
+ key: "WidgetPreRenderStyleTags"
309
+ ownerType: SHOP
310
+ }
311
+ ) {
312
+ metafieldStorefrontVisibility {
313
+ id
314
+ }
315
+ userErrors {
316
+ field
317
+ message
318
+ }
319
+ }
320
+ }
321
+ '
322
+ ```
323
+
324
+ 2. Run the following command to expose the `WidgetPreRenderBodyStyleTags` shop metafield:
325
+
326
+ ```bash
327
+ curl -X POST \
328
+ https://{shop}.myshopify.com/admin/api/2023-07/graphql.json \
329
+ -H 'Content-Type: application/graphql' \
330
+ -H 'X-Shopify-Access-Token: {access_token}' \
331
+ -d '
332
+ mutation {
333
+ metafieldStorefrontVisibilityCreate(
334
+ input: {
335
+ namespace: "okendo"
336
+ key: "WidgetPreRenderBodyStyleTags"
337
+ ownerType: SHOP
338
+ }
339
+ ) {
340
+ metafieldStorefrontVisibility {
341
+ id
342
+ }
343
+ userErrors {
344
+ field
345
+ message
346
+ }
347
+ }
348
+ }
349
+ '
350
+ ```
351
+
352
+ 3. Run the following command to expose the `ReviewsWidgetSnippet` product metafield:
353
+
354
+ ```bash
355
+ curl -X POST \
356
+ https://{shop}.myshopify.com/admin/api/2023-07/graphql.json \
357
+ -H 'Content-Type: application/graphql' \
358
+ -H 'X-Shopify-Access-Token: {access_token}' \
359
+ -d '
360
+ mutation {
361
+ metafieldStorefrontVisibilityCreate(
362
+ input: {
363
+ namespace: "okendo"
364
+ key: "ReviewsWidgetSnippet"
365
+ ownerType: PRODUCT
366
+ }
367
+ ) {
368
+ metafieldStorefrontVisibility {
369
+ id
370
+ }
371
+ userErrors {
372
+ field
373
+ message
374
+ }
375
+ }
376
+ }
377
+ '
378
+ ```
379
+
380
+ 4. Run the following command to expose the `StarRatingSnippet` the product metafield:
381
+
382
+ ```bash
383
+ curl -X POST \
384
+ https://{shop}.myshopify.com/admin/api/2023-07/graphql.json \
385
+ -H 'Content-Type: application/graphql' \
386
+ -H 'X-Shopify-Access-Token: {access_token}' \
387
+ -d '
388
+ mutation {
389
+ metafieldStorefrontVisibilityCreate(
390
+ input: {
391
+ namespace: "okendo"
392
+ key: "StarRatingSnippet"
393
+ ownerType: PRODUCT
394
+ }
395
+ ) {
396
+ metafieldStorefrontVisibility {
397
+ id
398
+ }
399
+ userErrors {
400
+ field
401
+ message
402
+ }
403
+ }
404
+ }
405
+ '
406
+ ```
407
+
408
+ ### Using GraphQL IDE
409
+
410
+ Open your GraphQL IDE (such as Postman) and make `POST` requests with the following details:
411
+
412
+ - **URL:** https://{shop}.myshopify.com/admin/api/2023-07/graphql.json
413
+ - **Headers:** - X-Shopify-Access-Token: {access_token} - Content-Type: application/json
414
+
415
+ 1. Execute the following request to expose the `WidgetPreRenderStyleTags` shop metafield:
416
+
417
+ ```graphql
418
+ mutation {
419
+ metafieldStorefrontVisibilityCreate(
420
+ input: {
421
+ namespace: "okendo"
422
+ key: "WidgetPreRenderStyleTags"
423
+ ownerType: SHOP
424
+ }
425
+ ) {
426
+ metafieldStorefrontVisibility {
427
+ id
428
+ }
429
+ userErrors {
430
+ field
431
+ message
432
+ }
433
+ }
434
+ }
435
+ ```
436
+
437
+ 2. Execute the following request to expose the `WidgetPreRenderBodyStyleTags` shop metafield:
438
+
439
+ ```graphql
440
+ mutation {
441
+ metafieldStorefrontVisibilityCreate(
442
+ input: {
443
+ namespace: "okendo"
444
+ key: "WidgetPreRenderBodyStyleTags"
445
+ ownerType: SHOP
446
+ }
447
+ ) {
448
+ metafieldStorefrontVisibility {
449
+ id
450
+ }
451
+ userErrors {
452
+ field
453
+ message
454
+ }
455
+ }
456
+ }
457
+ ```
458
+
459
+ 3. Execute the following request to expose the `ReviewsWidgetSnippet` product metafield:
460
+
461
+ ```graphql
462
+ mutation {
463
+ metafieldStorefrontVisibilityCreate(
464
+ input: {
465
+ namespace: "okendo"
466
+ key: "ReviewsWidgetSnippet"
467
+ ownerType: PRODUCT
468
+ }
469
+ ) {
470
+ metafieldStorefrontVisibility {
471
+ id
472
+ }
473
+ userErrors {
474
+ field
475
+ message
476
+ }
477
+ }
478
+ }
479
+ ```
480
+
481
+ 4. Execute the following request to expose the `StarRatingSnippet` the product metafield:
482
+
483
+ ```graphql
484
+ mutation {
485
+ metafieldStorefrontVisibilityCreate(
486
+ input: { namespace: "okendo", key: "StarRatingSnippet", ownerType: PRODUCT }
487
+ ) {
488
+ metafieldStorefrontVisibility {
489
+ id
490
+ }
491
+ userErrors {
492
+ field
493
+ message
494
+ }
495
+ }
496
+ }
497
+ ```
498
+
499
+ </details>
500
+
501
+ </details>
502
+
503
+ ## Installation
504
+
505
+ > The code examples provided in this section are based on the Shopify template store created by running `npm create @shopify/hydrogen@latest` (see [Shopify's documentation](https://shopify.dev/docs/custom-storefronts/hydrogen/getting-started)). You will find the following steps already done in [our demo store](https://github.com/okendo/okendo-shopify-hydrogen-demo).
506
+
507
+ Run:
508
+
509
+ ```bash
510
+ npm i @okendo/shopify-hydrogen
511
+ ```
512
+
513
+ ### `app/root.tsx`
514
+
515
+ Open `app/root.tsx` and add the following import:
516
+
517
+ ```ts
518
+ import {
519
+ OkendoProvider,
520
+ getOkendoProviderData,
521
+ } from "@okendo/shopify-hydrogen";
522
+ ```
523
+
524
+ Locate the `loader` function, append `okendoProviderData` to the returned data as shown below, and set `subscriberId` to your Okendo subscriber ID:
525
+
526
+ ```ts
527
+ return defer(
528
+ {
529
+ ...
530
+ okendoProviderData: await getOkendoProviderData({
531
+ context,
532
+ subscriberId: "<your-okendo-subscriber-id>",
533
+ }),
534
+ }
535
+ );
536
+ ```
537
+
538
+ Locate the `App` function, add the `meta` tag `oke:subscriber_id` to `head`, and place your Okendo subscriber ID in its content:
539
+
540
+ ```ts
541
+ <head>
542
+ <meta charSet="utf-8" />
543
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
544
+ <meta name="oke:subscriber_id" content="<your-okendo-subscriber-id>" />
545
+ ...
546
+ ```
547
+
548
+ Append `OkendoProvider` to `body`, and pass it the data returned by `getOkendoProviderData`. If Content Security Policy is active in your project, you also need to provide the `nonce` (available with `const nonce = useNonce()` in Shopify's Hydrogen demo store):
549
+
550
+ ```tsx
551
+ ...
552
+ <body>
553
+ <OkendoProvider
554
+ nonce={nonce}
555
+ okendoProviderData={data.okendoProviderData}
556
+ />
557
+ ...
558
+ </body>
559
+ ...
560
+ ```
561
+
562
+ ### `app/entry.server.tsx`
563
+
564
+ > This is only necessary if Content Security Policy is active in your project.
565
+
566
+ Locate the call to `createContentSecurityPolicy`, and add:
567
+
568
+ - `https://d3hw6dc1ow8pp2.cloudfront.net`, `https://d3g5hqndtiniji.cloudfront.net`, and `data:` to `defaultSrc`
569
+ - `https://d3hw6dc1ow8pp2.cloudfront.net` to `styleSrc`
570
+ - `https://api.okendo.io` to `connectSrc`
571
+
572
+ Note that it's necessary to to add the default values (`'self'`, etc.) when [extending the CSP](https://shopify.dev/docs/custom-storefronts/hydrogen/content-security-policy). The call to `createContentSecurityPolicy` should now look like the following:
573
+
574
+ ```ts
575
+ const { nonce, header, NonceProvider } = createContentSecurityPolicy({
576
+ defaultSrc: [
577
+ "'self'",
578
+ "localhost:*",
579
+ "https://cdn.shopify.com",
580
+ "https://d3hw6dc1ow8pp2.cloudfront.net",
581
+ "https://d3g5hqndtiniji.cloudfront.net",
582
+ "https://cdn-static.okendo.io",
583
+ "https://surveys.okendo.io",
584
+ "data:",
585
+ ],
586
+ imgSrc: [
587
+ "'self'",
588
+ "https://cdn.shopify.com",
589
+ "data:",
590
+ "https://d3hw6dc1ow8pp2.cloudfront.net",
591
+ "https://d3g5hqndtiniji.cloudfront.net",
592
+ "https://cdn-static.okendo.io",
593
+ "https://surveys.okendo.io",
594
+ ],
595
+ mediaSrc: [
596
+ "'self'",
597
+ "https://d3hw6dc1ow8pp2.cloudfront.net",
598
+ "https://d3g5hqndtiniji.cloudfront.net",
599
+ "https://cdn-static.okendo.io",
600
+ ],
601
+ styleSrcElem: [
602
+ "'self'",
603
+ "'unsafe-inline'",
604
+ "https://cdn.shopify.com",
605
+ "https://fonts.googleapis.com",
606
+ "https://fonts.gstatic.com",
607
+ "https://d3hw6dc1ow8pp2.cloudfront.net",
608
+ "https://cdn-static.okendo.io",
609
+ "https://surveys.okendo.io",
610
+ ],
611
+ scriptSrc: [
612
+ "'self'",
613
+ "https://cdn.shopify.com",
614
+ "https://d3hw6dc1ow8pp2.cloudfront.net",
615
+ "https://cdn-static.okendo.io",
616
+ "https://surveys.okendo.io",
617
+ ],
618
+ fontSrc: [
619
+ "'self'",
620
+ "https://fonts.gstatic.com",
621
+ "https://d3hw6dc1ow8pp2.cloudfront.net",
622
+ "https://cdn.shopify.com",
623
+ "https://cdn-static.okendo.io",
624
+ "https://surveys.okendo.io",
625
+ ],
626
+ connectSrc: [
627
+ "'self'",
628
+ "https://monorail-edge.shopifysvc.com",
629
+ "localhost:*",
630
+ "ws://localhost:*",
631
+ "ws://127.0.0.1:*",
632
+ "https://api.okendo.io",
633
+ "https://cdn-static.okendo.io",
634
+ "https://surveys.okendo.io",
635
+ ],
636
+ });
637
+ ```
638
+
639
+ ### `app/routes/_index.tsx`
640
+
641
+ Add the following imports:
642
+
643
+ ```ts
644
+ import {
645
+ OkendoStarRating,
646
+ type WithOkendoStarRatingSnippet,
647
+ } from "@okendo/shopify-hydrogen";
648
+ ```
649
+
650
+ Add the following block just before the `RECOMMENDED_PRODUCTS_QUERY` GraphQL query:
651
+
652
+ ```ts
653
+ const OKENDO_PRODUCT_STAR_RATING_FRAGMENT = `#graphql
654
+ fragment OkendoStarRatingSnippet on Product {
655
+ okendoStarRatingSnippet: metafield(
656
+ namespace: "okendo"
657
+ key: "StarRatingSnippet"
658
+ ) {
659
+ value
660
+ }
661
+ }
662
+ ` as const;
663
+ ```
664
+
665
+ Then append `${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}` and `...OkendoStarRatingSnippet` to `RECOMMENDED_PRODUCTS_QUERY`:
666
+
667
+ ```ts
668
+ const RECOMMENDED_PRODUCTS_QUERY = `#graphql
669
+ ${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}
670
+ fragment RecommendedProduct on Product {
671
+ id
672
+ title
673
+ handle
674
+ priceRange {
675
+ minVariantPrice {
676
+ amount
677
+ currencyCode
678
+ }
679
+ }
680
+ images(first: 1) {
681
+ nodes {
682
+ id
683
+ url
684
+ altText
685
+ width
686
+ height
687
+ }
688
+ }
689
+ ...OkendoStarRatingSnippet
690
+ }
691
+ query RecommendedProducts ($country: CountryCode, $language: LanguageCode)
692
+ @inContext(country: $country, language: $language) {
693
+ products(first: 4, sortKey: UPDATED_AT, reverse: true) {
694
+ nodes {
695
+ ...RecommendedProduct
696
+ }
697
+ }
698
+ }
699
+ ` as const;
700
+ ```
701
+
702
+ Tweak the type of the `products` prop of `RecommendedProducts`:
703
+
704
+ ```ts
705
+ products: Promise<{
706
+ products: {
707
+ nodes: (RecommendedProductsQuery["products"]["nodes"][0] &
708
+ WithOkendoStarRatingSnippet)[];
709
+ };
710
+ }>;
711
+ ```
712
+
713
+ Add `OkendoStarRating` to `RecommendedProducts`:
714
+
715
+ ```tsx
716
+ <OkendoStarRating
717
+ productId={product.id}
718
+ okendoStarRatingSnippet={product.okendoStarRatingSnippet}
719
+ />
720
+ ```
721
+
722
+ For instance, we can add it below the product title, like this:
723
+
724
+ ```tsx
725
+ <Image
726
+ data={product.images.nodes[0]}
727
+ aspectRatio="1/1"
728
+ sizes="(min-width: 45em) 20vw, 50vw"
729
+ />
730
+ <h4>{product.title}</h4>
731
+ <OkendoStarRating
732
+ productId={product.id}
733
+ okendoStarRatingSnippet={product.okendoStarRatingSnippet}
734
+ />
735
+ <small>
736
+ <Money data={product.priceRange.minVariantPrice} />
737
+ </small>
738
+ ```
739
+
740
+ We now have the Okendo Star Rating widget visible on our page:
741
+
742
+ ![Okendo's Star Rating widget](./okendo-star-rating-widget.png)
743
+
744
+ ### `app/routes/products.$handle.tsx`
745
+
746
+ Add the following imports:
747
+
748
+ ```ts
749
+ import {
750
+ OKENDO_PRODUCT_REVIEWS_FRAGMENT,
751
+ OKENDO_PRODUCT_STAR_RATING_FRAGMENT,
752
+ OkendoReviews,
753
+ OkendoStarRating,
754
+ type WithOkendoReviewsSnippet,
755
+ type WithOkendoStarRatingSnippet,
756
+ } from "@okendo/shopify-hydrogen";
757
+ ```
758
+
759
+ Add the following block just before the `RECOMMENDED_PRODUCTS_QUERY` GraphQL query:
760
+
761
+ ```ts
762
+ const OKENDO_PRODUCT_STAR_RATING_FRAGMENT = `#graphql
763
+ fragment OkendoStarRatingSnippet on Product {
764
+ okendoStarRatingSnippet: metafield(
765
+ namespace: "okendo"
766
+ key: "StarRatingSnippet"
767
+ ) {
768
+ value
769
+ }
770
+ }
771
+ ` as const;
772
+
773
+ const OKENDO_PRODUCT_REVIEWS_FRAGMENT = `#graphql
774
+ fragment OkendoReviewsSnippet on Product {
775
+ okendoReviewsSnippet: metafield(
776
+ namespace: "okendo"
777
+ key: "ReviewsWidgetSnippet"
778
+ ) {
779
+ value
780
+ }
781
+ }
782
+ ` as const;
783
+ ```
784
+
785
+ Then append `${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}`, `${OKENDO_PRODUCT_REVIEWS_FRAGMENT}`, `...OkendoStarRatingSnippet`, and `...OkendoReviewsSnippet` to `PRODUCT_FRAGMENT`:
786
+
787
+ ```ts
788
+ const PRODUCT_FRAGMENT = `#graphql
789
+ ${OKENDO_PRODUCT_STAR_RATING_FRAGMENT}
790
+ ${OKENDO_PRODUCT_REVIEWS_FRAGMENT}
791
+ fragment Product on Product {
792
+ id
793
+ title
794
+ vendor
795
+ handle
796
+ descriptionHtml
797
+ description
798
+ options {
799
+ name
800
+ values
801
+ }
802
+ selectedVariant: variantBySelectedOptions(selectedOptions: $selectedOptions) {
803
+ ...ProductVariant
804
+ }
805
+ variants(first: 1) {
806
+ nodes {
807
+ ...ProductVariant
808
+ }
809
+ }
810
+ seo {
811
+ description
812
+ title
813
+ }
814
+ ...OkendoStarRatingSnippet
815
+ ...OkendoReviewsSnippet
816
+ }
817
+ ${PRODUCT_VARIANT_FRAGMENT}
818
+ ` as const;
819
+ ```
820
+
821
+ Add `OkendoReviews` to `Product`:
822
+
823
+ ```tsx
824
+ <OkendoReviews
825
+ productId={product.id}
826
+ okendoReviewsSnippet={product.okendoReviewsSnippet}
827
+ />
828
+ ```
829
+
830
+ For instance, we can add it below the product section, like this:
831
+
832
+ ```tsx
833
+ <>
834
+ <div className="product">
835
+ <ProductImage image={selectedVariant?.image} />
836
+ <ProductMain
837
+ selectedVariant={selectedVariant}
838
+ product={product}
839
+ variants={variants}
840
+ />
841
+ </div>
842
+
843
+ <OkendoReviews
844
+ productId={product.id}
845
+ okendoReviewsSnippet={product.okendoReviewsSnippet}
846
+ />
847
+ </>
848
+ ```
849
+
850
+ Tweak the type of the `product` prop of `ProductMain`:
851
+
852
+ ```ts
853
+ product: ProductFragment &
854
+ WithOkendoStarRatingSnippet &
855
+ WithOkendoReviewsSnippet;
856
+ ```
857
+
858
+ Add `OkendoStarRating` to `ProductMain`:
859
+
860
+ ```tsx
861
+ <OkendoStarRating
862
+ productId={product.id}
863
+ okendoStarRatingSnippet={product.okendoStarRatingSnippet}
864
+ />
865
+ ```
866
+
867
+ For instance, we can add it below the product title, like this:
868
+
869
+ ```tsx
870
+ <div className="product-main">
871
+ <h1>{title}</h1>
872
+ <OkendoStarRating
873
+ productId={product.id}
874
+ okendoStarRatingSnippet={product.okendoStarRatingSnippet}
875
+ />
876
+ <ProductPrice selectedVariant={selectedVariant} />
877
+ ```
878
+
879
+ We now have the Okendo Star Rating and Reviews widgets visible on our product page:
880
+
881
+ ![Okendo's Star Rating and Reviews widgets](./okendo-star-rating-and-reviews-widgets.png)