feed-common 1.48.2 → 1.49.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,1012 +1,1033 @@
1
- // /* eslint-disable quotes */
2
- // /* eslint-disable @typescript-eslint/no-explicit-any */
3
- // /* eslint-disable max-len */
4
- // import { dateRangeSchema, measureUnitsSchema, priceSchema, validateIfNoMacro } from './index.js';
5
- // import { SOURCE_LANGUAGE, XmlFeedFormat } from '../../constants/profile.constants.js';
6
- // import { ProductUploadMapSource, XmlFeedTemplateType } from '../../types/profile.types.js';
7
- // import { z } from 'zod';
8
- // import { ulid } from 'ulid';
1
+ /* eslint-disable quotes */
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+ /* eslint-disable max-len */
4
+ import { dateRangeSchema, measureUnitsSchema, priceSchema, validateIfNoMacro } from './index.js';
5
+ import { SOURCE_COUNTRY, SOURCE_LANGUAGE, XmlFeedFormat } from '../../constants/profile.constants.js';
6
+ import { ProductUploadMapSource, XmlFeedTemplateType } from '../../types/profile.types.js';
7
+ import { z } from 'zod';
8
+ import { ulid } from 'ulid';
9
9
 
10
- // const availabilityMapping: ProductUploadMapSource = {
11
- // attribute: 'availability',
12
- // label: 'Availability',
13
- // description:
14
- // "Your product's availability. Accurately submit the product's availability and match the availability from your landing page and checkout pages",
15
- // required: true,
16
- // type: 'macro-input' as any,
17
- // validator: (v: string | number) =>
18
- // validateIfNoMacro(v, z.enum(['in_stock', 'out_of_stock', 'preorder', 'backorder'])),
19
- // rules: { sections: [] },
20
- // baseMode: true,
21
- // };
10
+ const availabilityMapping: ProductUploadMapSource = {
11
+ attribute: 'availability',
12
+ label: 'Availability',
13
+ description:
14
+ "Your product's availability. Accurately submit the product's availability and match the availability from your landing page and checkout pages",
15
+ required: true,
16
+ type: 'macro-input' as any,
17
+ validator: (v: string | number) =>
18
+ validateIfNoMacro(v, z.enum(['in_stock', 'out_of_stock', 'preorder', 'backorder'])),
19
+ rules: { sections: [] },
20
+ baseMode: true,
21
+ };
22
22
 
23
- // const customLabelMapping: ProductUploadMapSource = {
24
- // attribute: '',
25
- // label: '',
26
- // description:
27
- // 'Label that you assign to a product to help organize bidding and reporting in Shopping campaigns. Max 100 characters. Example: Seasonal',
28
- // required: false,
29
- // type: 'macro-input' as any,
30
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().max(100)),
31
- // rules: { sections: [] },
32
- // };
23
+ const customLabelMapping: ProductUploadMapSource = {
24
+ attribute: '',
25
+ label: '',
26
+ description:
27
+ 'Label that you assign to a product to help organize bidding and reporting in Shopping campaigns. Max 100 characters. Example: Seasonal',
28
+ required: false,
29
+ type: 'macro-input' as any,
30
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().max(100)),
31
+ rules: { sections: [] },
32
+ };
33
33
 
34
- // export const googleApiTemplate: XmlFeedTemplateType = {
35
- // formats: [XmlFeedFormat.XML],
36
- // documentation: 'https://support.google.com/merchants/answer/7052112',
37
- // mapping: [
38
- // {
39
- // attribute: 'title',
40
- // label: 'Title',
41
- // description: 'Your product’s name. Plain text.Max 150 characters. Example: Mens Pique Polo Shirt',
42
- // required: true,
43
- // type: 'macro-input',
44
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().max(150).min(1)),
45
- // defaultValue: '{{shopify.title}}',
46
- // rules: { sections: [] },
47
- // baseMode: true,
48
- // },
49
- // {
50
- // attribute: 'description',
51
- // label: 'Description',
52
- // description:
53
- // 'Your product’s description. Plain Text. Max 5000 characters. Example: Made from 100% organic cotton, this classic red men’s polo has a slim fit and signature logo embroidered on the left chest. Machine wash cold; imported.',
54
- // required: true,
55
- // type: 'macro-input',
56
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().max(5000).min(1)),
57
- // defaultValue: '{{shopify.description}}',
58
- // rules: { sections: [] },
59
- // baseMode: true,
60
- // },
61
- // {
62
- // attribute: 'link',
63
- // label: 'Link',
64
- // description:
65
- // 'Your product’s landing page. Links must begin with http:// or https://. http://www.example.com/asp/sp.asp?cat=12&id=1030',
66
- // required: true,
67
- // type: 'macro-input',
68
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().url()),
69
- // defaultValue: 'https://{{shopify.shop_domain}}/products/{{shopify.handle}}',
70
- // rules: { sections: [] },
71
- // baseMode: true,
72
- // },
73
- // {
74
- // attribute: 'imageLink',
75
- // label: 'Image link',
76
- // description: 'The URL of your product’s main image. Example: http:// www.example.com/image1.jpg',
77
- // required: true,
78
- // type: 'macro-input',
79
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().url()),
80
- // defaultValue: '{{shopify.image}}',
81
- // rules: { sections: [] },
82
- // baseMode: true,
83
- // },
84
- // {
85
- // attribute: 'additionalImageLinks',
86
- // label: 'Additional image links',
87
- // description:
88
- // 'The URL of an additional image for your product. Submit up to 10 additional product images, separate them with comma. Max 2000 characters. Example: http:// www.example.com/image1.jpg, http:// www.example.com/image2.jpg',
89
- // required: false,
90
- // type: 'macro-input' as any,
91
- // validator: (v: string | number) =>
92
- // validateIfNoMacro(
93
- // v,
94
- // z
95
- // .string()
96
- // .max(2000)
97
- // .refine(
98
- // v =>
99
- // !v ||
100
- // v
101
- // .split(',')
102
- // .map(i => i.trim())
103
- // .every(i => z.string().url().safeParse(i).success),
104
- // {
105
- // message: 'invalid URL',
106
- // }
107
- // )
108
- // ),
109
- // defaultValue: '{{shopify.images}}',
110
- // rules: { sections: [] },
111
- // allowDuplicates: true,
112
- // },
113
- // {
114
- // attribute: 'virtualModelLink',
115
- // label: '3D model link',
116
- // description:
117
- // 'Additional link to show a 3D model of your product. Available only in the US. This attribute is only available in the classic experience of Merchant Center. Limitation: valid URL, no more than 2000 characters long. Example: https://www.google.com/products/xyz.glb',
118
- // required: false,
119
- // type: 'macro-input',
120
- // validator: (v: string | number) =>
121
- // validateIfNoMacro(
122
- // v,
123
- // z
124
- // .string()
125
- // .max(2000)
126
- // .refine(v => !v || z.string().url().safeParse(v).success, { message: 'invalid URL' })
127
- // ),
128
- // defaultValue: '',
129
- // rules: { sections: [] },
130
- // },
131
- // {
132
- // attribute: 'mobileLink',
133
- // label: 'Mobile link',
134
- // description:
135
- // 'Your product’s mobile-optimized landing page when you have a different URL for mobile and desktop traffic. Limitation: valid URL, no more than 2000 characters long. Example: http://www.m.example.com/asp/ sp.asp?cat=12 id=1030',
136
- // required: false,
137
- // type: 'macro-input',
138
- // validator: (v: string | number) =>
139
- // validateIfNoMacro(
140
- // v,
141
- // z
142
- // .string()
143
- // .max(2000)
144
- // .refine(v => !v || z.string().url().safeParse(v).success, { message: 'invalid URL' })
145
- // ),
146
- // defaultValue: '',
147
- // rules: { sections: [] },
148
- // },
149
- // {
150
- // ...availabilityMapping,
151
- // defaultValue: 'in_stock',
152
- // rules: { sections: [] },
153
- // },
154
- // {
155
- // ...availabilityMapping,
156
- // defaultValue: 'out_of_stock',
157
- // rules: {
158
- // sections: [
159
- // {
160
- // id: ulid(),
161
- // ruleItems: [
162
- // {
163
- // id: ulid(),
164
- // attribute: 'inventory_quantity',
165
- // operator: 'equals',
166
- // value: 0,
167
- // },
168
- // ],
169
- // },
170
- // {
171
- // id: ulid(),
172
- // ruleItems: [
173
- // {
174
- // id: ulid(),
175
- // attribute: 'inventory_policy',
176
- // operator: 'equals',
177
- // value: 'deny',
178
- // },
179
- // ],
180
- // },
181
- // ],
182
- // },
183
- // },
184
- // {
185
- // attribute: 'availabilityDate',
186
- // label: 'Availability date',
187
- // description:
188
- // 'Required if product availability is set to preorder. The date a preordered product becomes available for delivery. Provide a value up to one year in the future. The availability date should also be added to the product’s landing page and be clear to your customers (for example, “May 6, 2023”). Example: 2016-02-24T11:07+0100',
189
- // required: false,
190
- // type: 'macro-input',
191
- // validator: (v: string | number) =>
192
- // // TODO: make required if availability is preorder
193
- // validateIfNoMacro(
194
- // v,
195
- // z
196
- // .string()
197
- // .refine(v => !v || z.string().date().safeParse(v).success, { message: 'invalid date' })
198
- // .refine(v => !v || new Date(v).getTime() + 1000 * 60 * 60 * 24 * 365 > new Date().getTime(), {
199
- // message: 'The date should be up to one year in the future',
200
- // })
201
- // ),
202
- // defaultValue: '',
203
- // rules: { sections: [] },
204
- // },
205
- // {
206
- // attribute: 'costOfGoodsSold',
207
- // label: 'Cost of goods sold',
208
- // description:
209
- // 'The costs associated with the sale of a particular product as defined by the accounting convention you set up. These costs may include material, labor, freight, or other overhead expenses. By submitting the COGS for your products, you gain insights about other metrics, such as your gross margin and the amount of revenue generated by your ads and free listings. The currency must be in the ISO 4217 format. For example, USD for US dollars. The decimal point must be a period (.). Example, 10.00 USD',
210
- // required: false,
211
- // type: 'macro-input',
212
- // defaultValue: '',
213
- // validator: (v: string | number) => validateIfNoMacro(v, priceSchema()),
214
- // rules: { sections: [] },
215
- // },
216
- // {
217
- // attribute: 'expirationDate',
218
- // label: 'Expiration date',
219
- // description:
220
- // 'The date that your product should stop showing. Use a date less than 30 days in the future. Example: 2016-07-11T11:07+0100',
221
- // required: false,
222
- // type: 'macro-input',
223
- // validator: (v: string | number) =>
224
- // validateIfNoMacro(
225
- // v,
226
- // z
227
- // .string()
228
- // .refine(v => !v || z.string().datetime().safeParse(v).success, { message: 'invalid date' })
229
- // .refine(v => !v || new Date(v).getTime() + 1000 * 60 * 60 * 24 * 30 > new Date().getTime(), {
230
- // message: 'The date should be less than 30 days in the future',
231
- // })
232
- // ),
233
- // defaultValue: '',
234
- // rules: { sections: [] },
235
- // },
236
- // // TODO: add condition, checking if sale price is set
237
- // {
238
- // attribute: 'price',
239
- // label: 'Price',
240
- // description:
241
- // "Your products price. Accurately submit the product's price and currency, and match with the price from your landing page and at checkout. Example: 15.00 USD",
242
- // required: true,
243
- // type: 'macro-input',
244
- // validator: (v: string | number) => validateIfNoMacro(v, priceSchema()),
245
- // defaultValue: '{{shopify.price}} {{shopify.shop_currency}}',
246
- // rules: { sections: [] },
247
- // baseMode: true,
248
- // },
249
- // {
250
- // attribute: 'salePrice',
251
- // label: 'Sale price',
252
- // description:
253
- // "Your product's sale price. Accurately submit the product's sale price, and match the sale price with your landing page and the checkout pages. Example: 15.00 USD",
254
- // required: false,
255
- // type: 'macro-input',
256
- // validator: (v: string | number) => validateIfNoMacro(v, priceSchema()),
257
- // defaultValue: '',
258
- // rules: { sections: [] },
259
- // },
260
- // {
261
- // attribute: 'salePriceEffectiveDate',
262
- // label: 'Sale price effective date',
263
- // description:
264
- // "The date range during which the sale price applies. Use a date less than 30 days in the future. Use together with the sale price. If you don't submit this attribute (sale price effective date), the sale price always applies. Separate start date and end date with /. Use a start date before the end date. Example: 2016-07-11T11:07+0100/2016-08-11T11:07+0100",
265
- // required: false,
266
- // type: 'macro-input',
267
- // validator: (v: string | number) => validateIfNoMacro(v, dateRangeSchema),
268
- // defaultValue: '',
269
- // rules: { sections: [] },
270
- // },
271
- // {
272
- // attribute: 'unitPricingMeasure',
273
- // label: 'Unit pricing measure',
274
- // description:
275
- // 'The measure and dimension of your product as it is sold. Optional (except when required by local laws or regulations). Use the measure or dimension of the product without packaging. Example: 1.5kg',
276
- // required: false,
277
- // type: 'macro-input',
278
- // validator: (v: string | number) =>
279
- // validateIfNoMacro(
280
- // v,
281
- // measureUnitsSchema([
282
- // 'oz',
283
- // 'lb',
284
- // 'mg',
285
- // 'g',
286
- // 'kg',
287
- // 'floz',
288
- // 'pt',
289
- // 'qt',
290
- // 'gal',
291
- // 'ml',
292
- // 'cl',
293
- // 'l',
294
- // 'cbm',
295
- // 'in',
296
- // 'ft',
297
- // 'yd',
298
- // 'cm',
299
- // 'm',
300
- // 'sqft',
301
- // 'sqm',
302
- // 'ct',
303
- // ])
304
- // ),
305
- // defaultValue: '',
306
- // rules: { sections: [] },
307
- // },
308
- // {
309
- // attribute: 'unitPricingBaseMeasure',
310
- // label: 'Unit pricing base measure',
311
- // description:
312
- // 'The product’s base measure for pricing (for example, 100ml means the price is calculated based on a 100ml units). Optional (except when required by local laws or regulations). Example: 100g',
313
- // required: false,
314
- // type: 'macro-input',
315
- // validator: (v: string | number) =>
316
- // validateIfNoMacro(
317
- // v,
318
- // measureUnitsSchema([
319
- // 'oz',
320
- // 'lb',
321
- // 'mg',
322
- // 'g',
323
- // 'kg',
324
- // 'floz',
325
- // 'pt',
326
- // 'qt',
327
- // 'gal',
328
- // 'ml',
329
- // 'cl',
330
- // 'l',
331
- // 'cbm',
332
- // 'in',
333
- // 'ft',
334
- // 'yd',
335
- // 'cm',
336
- // 'm',
337
- // 'sqft',
338
- // 'sqm',
339
- // 'ct',
340
- // ])
341
- // ),
342
- // defaultValue: '',
343
- // rules: { sections: [] },
344
- // },
345
- // {
346
- // attribute: 'installment',
347
- // label: 'Installment',
348
- // description:
349
- // 'Details of an installment payment plan. This attribute uses 2 sub-attributes: Months: the number of installments the buyer has to pay. Amount: ISO 4217, the amount the buyer has to pay per month. Example: 6:30 EUR',
350
- // required: false,
351
- // type: 'macro-input',
352
- // defaultValue: '',
353
- // validator: (v: string | number) =>
354
- // validateIfNoMacro(
355
- // v,
356
- // z.string().superRefine((v, ctx) => {
357
- // if (!v) {
358
- // return true;
359
- // }
34
+ export const googleApiTemplate: XmlFeedTemplateType = {
35
+ formats: [XmlFeedFormat.XML],
36
+ documentation: 'https://support.google.com/merchants/answer/7052112',
37
+ mapping: [
38
+ {
39
+ attribute: 'offerId',
40
+ label: 'ID',
41
+ description:
42
+ 'A unique identifier for the item. Leading and trailing whitespaces are stripped and multiple whitespaces are replaced by a single whitespace upon submission. Only valid unicode characters are accepted. Max length: 50 characters. Example: 123456',
43
+ required: true,
44
+ type: 'macro-input',
45
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().max(150).min(1)),
46
+ defaultValue: '{{shopify.id}}_{{shopify.variant_id}}',
47
+ rules: { sections: [] },
48
+ baseMode: true,
49
+ },
50
+ {
51
+ attribute: 'contentLanguage',
52
+ label: 'Content language',
53
+ description: 'The two-letter ISO 639-1 language code for the item',
54
+ required: true,
55
+ type: 'select',
56
+ choices: [SOURCE_LANGUAGE],
57
+ rules: { sections: [] },
58
+ baseMode: true,
59
+ },
60
+ {
61
+ attribute: 'targetCountry',
62
+ label: 'Target country',
63
+ description: "The CLDR territory code for the item's country of sale",
64
+ required: true,
65
+ type: 'select',
66
+ choices: [SOURCE_COUNTRY],
67
+ rules: { sections: [] },
68
+ baseMode: true,
69
+ },
70
+ {
71
+ attribute: 'title',
72
+ label: 'Title',
73
+ description: 'Your product’s name. Plain text.Max 150 characters. Example: Mens Pique Polo Shirt',
74
+ required: true,
75
+ type: 'macro-input',
76
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().max(150).min(1)),
77
+ defaultValue: '{{shopify.title}}',
78
+ rules: { sections: [] },
79
+ baseMode: true,
80
+ },
81
+ {
82
+ attribute: 'description',
83
+ label: 'Description',
84
+ description:
85
+ 'Your product’s description. Plain Text. Max 5000 characters. Example: Made from 100% organic cotton, this classic red men’s polo has a slim fit and signature logo embroidered on the left chest. Machine wash cold; imported.',
86
+ required: true,
87
+ type: 'macro-input',
88
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().max(5000).min(1)),
89
+ defaultValue: '{{shopify.description}}',
90
+ rules: { sections: [] },
91
+ baseMode: true,
92
+ },
93
+ {
94
+ attribute: 'link',
95
+ label: 'Link',
96
+ description:
97
+ 'Your product’s landing page. Links must begin with http:// or https://. http://www.example.com/asp/sp.asp?cat=12&id=1030',
98
+ required: true,
99
+ type: 'macro-input',
100
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().url()),
101
+ defaultValue: 'https://{{shopify.shop_domain}}/products/{{shopify.handle}}',
102
+ rules: { sections: [] },
103
+ baseMode: true,
104
+ },
105
+ {
106
+ attribute: 'imageLink',
107
+ label: 'Image link',
108
+ description: 'The URL of your product’s main image. Example: http:// www.example.com/image1.jpg',
109
+ required: true,
110
+ type: 'macro-input',
111
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().url()),
112
+ defaultValue: '{{shopify.image}}',
113
+ rules: { sections: [] },
114
+ baseMode: true,
115
+ },
116
+ {
117
+ attribute: 'additionalImageLinks',
118
+ label: 'Additional image links',
119
+ description:
120
+ 'The URL of an additional image for your product. Submit up to 10 additional product images, separate them with comma. Max 2000 characters. Example: http:// www.example.com/image1.jpg, http:// www.example.com/image2.jpg',
121
+ required: false,
122
+ type: 'macro-input' as any,
123
+ validator: (v: string | number) =>
124
+ validateIfNoMacro(
125
+ v,
126
+ z
127
+ .string()
128
+ .max(2000)
129
+ .refine(
130
+ v =>
131
+ !v ||
132
+ v
133
+ .split(',')
134
+ .map(i => i.trim())
135
+ .every(i => z.string().url().safeParse(i).success),
136
+ {
137
+ message: 'invalid URL',
138
+ }
139
+ )
140
+ ),
141
+ defaultValue: '{{shopify.images}}',
142
+ rules: { sections: [] },
143
+ allowDuplicates: true,
144
+ },
145
+ {
146
+ attribute: 'virtualModelLink',
147
+ label: '3D model link',
148
+ description:
149
+ 'Additional link to show a 3D model of your product. Available only in the US. This attribute is only available in the classic experience of Merchant Center. Limitation: valid URL, no more than 2000 characters long. Example: https://www.google.com/products/xyz.glb',
150
+ required: false,
151
+ type: 'macro-input',
152
+ validator: (v: string | number) =>
153
+ validateIfNoMacro(
154
+ v,
155
+ z
156
+ .string()
157
+ .max(2000)
158
+ .refine(v => !v || z.string().url().safeParse(v).success, { message: 'invalid URL' })
159
+ ),
160
+ defaultValue: '',
161
+ rules: { sections: [] },
162
+ },
163
+ {
164
+ attribute: 'mobileLink',
165
+ label: 'Mobile link',
166
+ description:
167
+ 'Your product’s mobile-optimized landing page when you have a different URL for mobile and desktop traffic. Limitation: valid URL, no more than 2000 characters long. Example: http://www.m.example.com/asp/ sp.asp?cat=12 id=1030',
168
+ required: false,
169
+ type: 'macro-input',
170
+ validator: (v: string | number) =>
171
+ validateIfNoMacro(
172
+ v,
173
+ z
174
+ .string()
175
+ .max(2000)
176
+ .refine(v => !v || z.string().url().safeParse(v).success, { message: 'invalid URL' })
177
+ ),
178
+ defaultValue: '',
179
+ rules: { sections: [] },
180
+ },
181
+ {
182
+ ...availabilityMapping,
183
+ defaultValue: 'in_stock',
184
+ rules: { sections: [] },
185
+ },
186
+ {
187
+ ...availabilityMapping,
188
+ defaultValue: 'out_of_stock',
189
+ rules: {
190
+ sections: [
191
+ {
192
+ id: ulid(),
193
+ ruleItems: [
194
+ {
195
+ id: ulid(),
196
+ attribute: 'inventory_quantity',
197
+ operator: 'equals',
198
+ value: 0,
199
+ },
200
+ ],
201
+ },
202
+ {
203
+ id: ulid(),
204
+ ruleItems: [
205
+ {
206
+ id: ulid(),
207
+ attribute: 'inventory_policy',
208
+ operator: 'equals',
209
+ value: 'deny',
210
+ },
211
+ ],
212
+ },
213
+ ],
214
+ },
215
+ },
216
+ {
217
+ attribute: 'availabilityDate',
218
+ label: 'Availability date',
219
+ description:
220
+ 'Required if product availability is set to preorder. The date a preordered product becomes available for delivery. Provide a value up to one year in the future. The availability date should also be added to the product’s landing page and be clear to your customers (for example, “May 6, 2023”). Example: 2016-02-24T11:07+0100',
221
+ required: false,
222
+ type: 'macro-input',
223
+ validator: (v: string | number) =>
224
+ // TODO: make required if availability is preorder
225
+ validateIfNoMacro(
226
+ v,
227
+ z
228
+ .string()
229
+ .refine(v => !v || z.string().date().safeParse(v).success, { message: 'invalid date' })
230
+ .refine(v => !v || new Date(v).getTime() + 1000 * 60 * 60 * 24 * 365 > new Date().getTime(), {
231
+ message: 'The date should be up to one year in the future',
232
+ })
233
+ ),
234
+ defaultValue: '',
235
+ rules: { sections: [] },
236
+ },
237
+ {
238
+ attribute: 'costOfGoodsSold',
239
+ label: 'Cost of goods sold',
240
+ description:
241
+ 'The costs associated with the sale of a particular product as defined by the accounting convention you set up. These costs may include material, labor, freight, or other overhead expenses. By submitting the COGS for your products, you gain insights about other metrics, such as your gross margin and the amount of revenue generated by your ads and free listings. The currency must be in the ISO 4217 format. For example, USD for US dollars. The decimal point must be a period (.). Example, 10.00 USD',
242
+ required: false,
243
+ type: 'macro-input',
244
+ defaultValue: '',
245
+ validator: (v: string | number) => validateIfNoMacro(v, priceSchema()),
246
+ rules: { sections: [] },
247
+ },
248
+ {
249
+ attribute: 'expirationDate',
250
+ label: 'Expiration date',
251
+ description:
252
+ 'The date that your product should stop showing. Use a date less than 30 days in the future. Example: 2016-07-11T11:07+0100',
253
+ required: false,
254
+ type: 'macro-input',
255
+ validator: (v: string | number) =>
256
+ validateIfNoMacro(
257
+ v,
258
+ z
259
+ .string()
260
+ .refine(v => !v || z.string().datetime().safeParse(v).success, { message: 'invalid date' })
261
+ .refine(v => !v || new Date(v).getTime() + 1000 * 60 * 60 * 24 * 30 > new Date().getTime(), {
262
+ message: 'The date should be less than 30 days in the future',
263
+ })
264
+ ),
265
+ defaultValue: '',
266
+ rules: { sections: [] },
267
+ },
268
+ // TODO: add condition, checking if sale price is set
269
+ {
270
+ attribute: 'price',
271
+ label: 'Price',
272
+ description:
273
+ "Your products price. Accurately submit the product's price and currency, and match with the price from your landing page and at checkout. Example: 15.00 USD",
274
+ required: true,
275
+ type: 'macro-input',
276
+ validator: (v: string | number) => validateIfNoMacro(v, priceSchema()),
277
+ defaultValue: '{{shopify.price}} {{shopify.shop_currency}}',
278
+ rules: { sections: [] },
279
+ baseMode: true,
280
+ },
281
+ {
282
+ attribute: 'salePrice',
283
+ label: 'Sale price',
284
+ description:
285
+ "Your product's sale price. Accurately submit the product's sale price, and match the sale price with your landing page and the checkout pages. Example: 15.00 USD",
286
+ required: false,
287
+ type: 'macro-input',
288
+ validator: (v: string | number) => validateIfNoMacro(v, priceSchema()),
289
+ defaultValue: '',
290
+ rules: { sections: [] },
291
+ },
292
+ {
293
+ attribute: 'salePriceEffectiveDate',
294
+ label: 'Sale price effective date',
295
+ description:
296
+ "The date range during which the sale price applies. Use a date less than 30 days in the future. Use together with the sale price. If you don't submit this attribute (sale price effective date), the sale price always applies. Separate start date and end date with /. Use a start date before the end date. Example: 2016-07-11T11:07+0100/2016-08-11T11:07+0100",
297
+ required: false,
298
+ type: 'macro-input',
299
+ validator: (v: string | number) => validateIfNoMacro(v, dateRangeSchema),
300
+ defaultValue: '',
301
+ rules: { sections: [] },
302
+ },
303
+ {
304
+ attribute: 'unitPricingMeasure',
305
+ label: 'Unit pricing measure',
306
+ description:
307
+ 'The measure and dimension of your product as it is sold. Optional (except when required by local laws or regulations). Use the measure or dimension of the product without packaging. Example: 1.5kg',
308
+ required: false,
309
+ type: 'macro-input',
310
+ validator: (v: string | number) =>
311
+ validateIfNoMacro(
312
+ v,
313
+ measureUnitsSchema([
314
+ 'oz',
315
+ 'lb',
316
+ 'mg',
317
+ 'g',
318
+ 'kg',
319
+ 'floz',
320
+ 'pt',
321
+ 'qt',
322
+ 'gal',
323
+ 'ml',
324
+ 'cl',
325
+ 'l',
326
+ 'cbm',
327
+ 'in',
328
+ 'ft',
329
+ 'yd',
330
+ 'cm',
331
+ 'm',
332
+ 'sqft',
333
+ 'sqm',
334
+ 'ct',
335
+ ])
336
+ ),
337
+ defaultValue: '',
338
+ rules: { sections: [] },
339
+ },
340
+ {
341
+ attribute: 'unitPricingBaseMeasure',
342
+ label: 'Unit pricing base measure',
343
+ description:
344
+ 'The product’s base measure for pricing (for example, 100ml means the price is calculated based on a 100ml units). Optional (except when required by local laws or regulations). Example: 100g',
345
+ required: false,
346
+ type: 'macro-input',
347
+ validator: (v: string | number) =>
348
+ validateIfNoMacro(
349
+ v,
350
+ measureUnitsSchema([
351
+ 'oz',
352
+ 'lb',
353
+ 'mg',
354
+ 'g',
355
+ 'kg',
356
+ 'floz',
357
+ 'pt',
358
+ 'qt',
359
+ 'gal',
360
+ 'ml',
361
+ 'cl',
362
+ 'l',
363
+ 'cbm',
364
+ 'in',
365
+ 'ft',
366
+ 'yd',
367
+ 'cm',
368
+ 'm',
369
+ 'sqft',
370
+ 'sqm',
371
+ 'ct',
372
+ ])
373
+ ),
374
+ defaultValue: '',
375
+ rules: { sections: [] },
376
+ },
377
+ {
378
+ attribute: 'installment',
379
+ label: 'Installment',
380
+ description:
381
+ 'Details of an installment payment plan. This attribute uses 2 sub-attributes: Months: the number of installments the buyer has to pay. Amount: ISO 4217, the amount the buyer has to pay per month. Example: 6:30 EUR',
382
+ required: false,
383
+ type: 'macro-input',
384
+ defaultValue: '',
385
+ validator: (v: string | number) =>
386
+ validateIfNoMacro(
387
+ v,
388
+ z.string().superRefine((v, ctx) => {
389
+ if (!v) {
390
+ return true;
391
+ }
360
392
 
361
- // const [months, amount] = v.split(':');
393
+ const [months, amount] = v.split(':');
362
394
 
363
- // if (!z.coerce.number().min(1).int().safeParse(months).success) {
364
- // ctx.addIssue({
365
- // code: z.ZodIssueCode.custom,
366
- // message: 'Months should be a positive whole number',
367
- // });
368
- // }
395
+ if (!z.coerce.number().min(1).int().safeParse(months).success) {
396
+ ctx.addIssue({
397
+ code: z.ZodIssueCode.custom,
398
+ message: 'Months should be a positive whole number',
399
+ });
400
+ }
369
401
 
370
- // const result = priceSchema().safeParse(amount);
371
- // if (!result.success) {
372
- // ctx.addIssue({
373
- // code: z.ZodIssueCode.custom,
374
- // message: result.error.issues[0].message,
375
- // });
376
- // }
377
- // })
378
- // ),
379
- // rules: { sections: [] },
380
- // },
381
- // {
382
- // attribute: 'subscriptionCost',
383
- // label: 'Subscription cost',
384
- // description:
385
- // 'Details a monthly or annual payment plan that bundles a communications service contract with a wireless product. Optional (available in certain countries for showing wireless products and services only). Syntax: Period [period] (Required), The duration of a single subscription period. This sub-attribute uses the following supported values: Month [month] Year [year]. Period length [period_length] (Required), Integer, the number of subscription periods (months or years) that the buyer must pay. Amount [amount] (Required), ISO 4217, the amount the buyer has to pay per subscription period. Example: month:12:30.00EUR',
386
- // required: false,
387
- // type: 'macro-input',
388
- // validator: (v: string | number) =>
389
- // validateIfNoMacro(
390
- // v,
391
- // z.string().superRefine((v, ctx) => {
392
- // if (!v) {
393
- // return true;
394
- // }
402
+ const result = priceSchema().safeParse(amount);
403
+ if (!result.success) {
404
+ ctx.addIssue({
405
+ code: z.ZodIssueCode.custom,
406
+ message: result.error.issues[0].message,
407
+ });
408
+ }
409
+ })
410
+ ),
411
+ rules: { sections: [] },
412
+ },
413
+ {
414
+ attribute: 'subscriptionCost',
415
+ label: 'Subscription cost',
416
+ description:
417
+ 'Details a monthly or annual payment plan that bundles a communications service contract with a wireless product. Optional (available in certain countries for showing wireless products and services only). Syntax: Period [period] (Required), The duration of a single subscription period. This sub-attribute uses the following supported values: Month [month] Year [year]. Period length [period_length] (Required), Integer, the number of subscription periods (months or years) that the buyer must pay. Amount [amount] (Required), ISO 4217, the amount the buyer has to pay per subscription period. Example: month:12:30.00EUR',
418
+ required: false,
419
+ type: 'macro-input',
420
+ validator: (v: string | number) =>
421
+ validateIfNoMacro(
422
+ v,
423
+ z.string().superRefine((v, ctx) => {
424
+ if (!v) {
425
+ return true;
426
+ }
395
427
 
396
- // const [period, periodLength, amount] = v.split(':');
428
+ const [period, periodLength, amount] = v.split(':');
397
429
 
398
- // if (!['year', 'month'].includes(period)) {
399
- // ctx.addIssue({
400
- // code: z.ZodIssueCode.custom,
401
- // message: 'Period should be either year or month',
402
- // });
403
- // }
430
+ if (!['year', 'month'].includes(period)) {
431
+ ctx.addIssue({
432
+ code: z.ZodIssueCode.custom,
433
+ message: 'Period should be either year or month',
434
+ });
435
+ }
404
436
 
405
- // if (!z.coerce.number().min(1).int().safeParse(periodLength).success) {
406
- // ctx.addIssue({
407
- // code: z.ZodIssueCode.custom,
408
- // message: 'Period length should be a positive whole number',
409
- // });
410
- // }
437
+ if (!z.coerce.number().min(1).int().safeParse(periodLength).success) {
438
+ ctx.addIssue({
439
+ code: z.ZodIssueCode.custom,
440
+ message: 'Period length should be a positive whole number',
441
+ });
442
+ }
411
443
 
412
- // const result = priceSchema().safeParse(amount);
413
- // if (!result.success) {
414
- // ctx.addIssue({
415
- // code: z.ZodIssueCode.custom,
416
- // message: result.error.issues[0].message,
417
- // });
418
- // }
419
- // })
420
- // ),
421
- // defaultValue: '',
422
- // rules: { sections: [] },
423
- // },
424
- // {
425
- // attribute: 'googleProductCategory',
426
- // label: 'Google product category',
427
- // description:
428
- // 'Google-defined product category for your product. Example: Apparel & Accessories > Clothing > Outerwear > Coats & Jackets or 371',
429
- // required: false,
430
- // type: 'macro-input',
431
- // defaultValue: '',
432
- // rules: { sections: [] },
433
- // },
434
- // {
435
- // attribute: 'product_type',
436
- // label: 'Product type',
437
- // description:
438
- // 'Product category that you define for your product. Max 750 alphanumeric character. Example: Home > Women > Dresses > Maxi Dresses',
439
- // required: false,
440
- // type: 'macro-input',
441
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().max(750)),
442
- // defaultValue: '{{shopify.category}}',
443
- // rules: { sections: [] },
444
- // },
445
- // {
446
- // attribute: 'brand',
447
- // label: 'Brand',
448
- // description: 'Your product’s brand name. Plain text. Max 70 characters. Example: Google',
449
- // required: true,
450
- // type: 'macro-input',
451
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().max(70).min(1)),
452
- // defaultValue: '{{shopify.vendor}}',
453
- // rules: { sections: [] },
454
- // baseMode: true,
455
- // },
456
- // {
457
- // attribute: 'gtin',
458
- // label: 'GTIN',
459
- // description:
460
- // 'Your product’s Global Trade Item Number (GTIN). Required (For all products with a known GTIN to enable full offer performance). Max 50 numeric characters. Example: 3234567890126',
461
- // required: false,
462
- // type: 'macro-input',
463
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().max(50)),
464
- // defaultValue: '{{shopify.barcode}}',
465
- // rules: { sections: [] },
466
- // },
467
- // {
468
- // attribute: 'mpn',
469
- // label: 'MPN',
470
- // description:
471
- // 'Your product’s Manufacturer Part Number (MPN). Required (Only if your product does not have a manufacturer assigned GTIN). Only submit MPNs assigned by a manufacturer. Max 70 alphanumeric characters. Example: GO12345OOGLE',
472
- // required: false,
473
- // type: 'macro-input',
474
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().max(70)),
475
- // defaultValue: '{{shopify.sku}}',
476
- // rules: { sections: [] },
477
- // },
478
- // {
479
- // attribute: 'identifier_exists',
480
- // label: 'Identifier exists',
481
- // description:
482
- // 'Use to indicate whether or not the unique product identifiers (UPIs) GTIN, MPN, and brand are available for your product. Submit "no" if: Your product is a media item and the GTIN is unavailable (Note: ISBN and SBN codes are accepted as GTINs or Your product is an apparel (clothing) item and the brand is unavailable or In all other categories, your product doesn’t have a GTIN, or a combination of MPN and brand. If a product does have unique product identifiers, don’t submit this attribute with a value of “no” or the product may be disapproved. Example: no',
483
- // required: false,
484
- // type: 'macro-input',
485
- // validator: (v: string | number) => validateIfNoMacro(v, z.enum(['yes', 'no', ''])),
486
- // defaultValue: 'yes',
487
- // rules: { sections: [] },
488
- // },
489
- // {
490
- // attribute: 'condition',
491
- // label: 'Condition',
492
- // description:
493
- // 'The condition of your product at time of sale. Required if your product is used or refurbished. Supported values: new, refurbished, used. Example: new',
494
- // required: false,
495
- // type: 'macro-input',
496
- // validator: (v: string | number) => validateIfNoMacro(v, z.enum(['new', 'refurbished', 'used', ''])),
497
- // defaultValue: '',
498
- // rules: { sections: [] },
499
- // },
500
- // {
501
- // attribute: 'adult',
502
- // label: 'Adult',
503
- // description:
504
- // 'Indicate a product includes sexually suggestive content. Required (If a product contains adult content). Supported values: yes, no. Example: yes',
505
- // required: false,
506
- // type: 'macro-input',
507
- // validator: (v: string | number) => validateIfNoMacro(v, z.enum(['yes', 'no', ''])),
508
- // defaultValue: '',
509
- // rules: { sections: [] },
510
- // },
511
- // {
512
- // attribute: 'multipack',
513
- // label: 'Multipack',
514
- // description:
515
- // "The number of identical products sold within a merchant-defined multipack. Required (For multipack products in Australia, Brazil, Czechia, France, Germany, Italy, Japan, Netherlands, Spain, Switzerland, the UK and the US). Required for free listings on Google if you’ve created a multipack. Submit this attribute if you defined a custom group of identical products and are selling them as a single unit of sale (for example, you're selling 6 bars of soap together). If the product's manufacturer assembled the multipack instead of you, don't submit this attribute. Example: 6",
516
- // required: false,
517
- // type: 'macro-input',
518
- // validator: (v: string | number) => validateIfNoMacro(v, z.coerce.number().min(0)),
519
- // defaultValue: '',
520
- // rules: { sections: [] },
521
- // },
522
- // {
523
- // attribute: 'is_bundle',
524
- // label: 'Bundle',
525
- // description:
526
- // 'Indicates a product is a merchant-defined custom group of different products featuring one main product. Required (For bundles in Australia, Brazil, Czechia, France, Germany, Italy, Japan, Netherlands, Spain, Switzerland, the UK and the US). Required for free listings on Google if you’ve created a bundle containing a main product. Supported values: yes, no. Example: yes',
527
- // required: false,
528
- // type: 'macro-input',
529
- // validator: (v: string | number) => validateIfNoMacro(v, z.enum(['yes', 'no', ''])),
530
- // defaultValue: '',
531
- // rules: { sections: [] },
532
- // },
533
- // {
534
- // attribute: 'certification',
535
- // label: 'Certification',
536
- // description:
537
- // 'Certifications, such as energy efficiency ratings, associated with your product. Available for the EU and EFTA countries and the UK. Required for products that require certain certification information to be shown in your Shopping ads or free listings, for example due to local energy efficiency labeling regulations. Example: EC:EPREL:123456',
538
- // required: false,
539
- // type: 'macro-input',
540
- // defaultValue: '',
541
- // rules: { sections: [] },
542
- // },
543
- // {
544
- // attribute: 'energy_efficiency_class',
545
- // label: 'Energy efficiency class',
546
- // description:
547
- // 'Your product’s energy label. Available for the EU and EFTA countries and the UK. Note: This attribute is being deprecated. Please use the certification attribute instead to show the EU energy efficiency class. Supported values: A, A+, A++, A+++, B, C, D, E, F, G. Example: A+',
548
- // required: false,
549
- // type: 'macro-input',
550
- // validator: (v: string | number) =>
551
- // validateIfNoMacro(v, z.enum(['A', 'A+', 'A++', 'A+++', 'B', 'C', 'D', 'E', 'F', 'G', ''])),
552
- // defaultValue: '',
553
- // rules: { sections: [] },
554
- // },
555
- // {
556
- // attribute: 'age_group',
557
- // label: 'Age group',
558
- // description:
559
- // 'The demographic for which your product is intended. Required (For all apparel products that are targeted to people in Brazil, France, Germany, Japan, the UK, and the US as well as all products with assigned age groups). Required for free listings for all Apparel & Accessories (ID: 166) products. Supported values: newborn, infant, toddler, kids, adult. Example: adult',
560
- // required: false,
561
- // type: 'macro-input',
562
- // // TODO: add to check if product is apparel
563
- // validator: (v: string | number) =>
564
- // validateIfNoMacro(v, z.enum(['newborn', 'infant', 'toddler', 'kids', 'adult', ''])),
565
- // defaultValue: '',
566
- // rules: { sections: [] },
567
- // },
568
- // {
569
- // attribute: 'color',
570
- // label: 'Color',
571
- // description:
572
- // 'Your product’s color(s). Required (For all apparel products that are targeted to people in Brazil, France, Germany, Japan, the UK, and the US). Required for free listings for all Apparel & Accessories (ID: 166) products. Max 100 alphanumeric characters (max 40 characters per color). Example: Black',
573
- // required: false,
574
- // type: 'macro-input',
575
- // // TODO: add to check if product is apparel
576
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().max(100)),
577
- // rules: { sections: [] },
578
- // },
579
- // {
580
- // attribute: 'gender',
581
- // label: 'Gender',
582
- // description:
583
- // 'The gender for which your product is intended. Required (Required for all apparel items that are targeted to people in Brazil, France, Germany, Japan, the UK, and the US as well as all gender-specific products). Required for free listings for all Google Apparel & Accessories (ID: 166) products. Supported values: male, female, unisex. Example: unisex',
584
- // required: false,
585
- // type: 'macro-input',
586
- // // TODO: add to check if product is apparel
587
- // validator: (v: string | number) => validateIfNoMacro(v, z.enum(['male', 'female', 'unisex', ''])),
588
- // rules: { sections: [] },
589
- // },
590
- // {
591
- // attribute: 'material',
592
- // label: 'Material',
593
- // description:
594
- // 'Your product’s fabric or material. Required (if relevant for distinguishing different products in a set of variants). Max 200 characters. Example: leather',
595
- // required: false,
596
- // type: 'macro-input',
597
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().max(200)),
598
- // rules: { sections: [] },
599
- // },
600
- // {
601
- // attribute: 'pattern',
602
- // label: 'Pattern',
603
- // description:
604
- // 'Your product’s pattern or graphic print. Required (if relevant for distinguishing different products in a set of variants). Max 100 characters. Example: polka dot',
605
- // required: false,
606
- // type: 'macro-input',
607
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().max(100)),
608
- // rules: { sections: [] },
609
- // },
610
- // {
611
- // attribute: 'size',
612
- // label: 'Size',
613
- // description:
614
- // 'Your product’s size. Required (Required for all apparel products in Apparel & Accessories > Clothing (ID:1604) and Apparel & Accessories > Shoes (ID:187) categories targeted to people in Brazil, France, Germany, Japan, the UK, and the US as well as all products available in different sizes). Required for free listings for all Apparel & Accessories > Clothing (ID:1604) and Apparel & Accessories > Shoes (ID:187) products. Max 100 characters. Example: XL',
615
- // required: false,
616
- // type: 'macro-input',
617
- // // TODO: add to check if product is apparel
618
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().max(100)),
619
- // rules: { sections: [] },
620
- // },
621
- // {
622
- // attribute: 'size_type',
623
- // label: 'Size type',
624
- // description:
625
- // 'Your apparel product’s cut. Available for apparel products only. Submit up to 2 values. Supported values: regular, petite, maternity, big, tall, plus. Example: maternity',
626
- // required: false,
627
- // type: 'macro-input',
628
- // validator: (v: string | number) =>
629
- // validateIfNoMacro(
630
- // v,
631
- // z
632
- // .string()
633
- // .refine(
634
- // v =>
635
- // !v ||
636
- // (v.split(/\s+/).length < 3 &&
637
- // v
638
- // .split(/\s+/)
639
- // .every(st =>
640
- // ['regular', 'petite', 'maternity', 'big', 'tall', 'plus'].includes(st)
641
- // )),
642
- // {
643
- // message: `Only 'regular', 'petite', 'maternity', 'big', 'tall', 'plus' supported. Max 2 values`,
644
- // }
645
- // )
646
- // ),
647
- // rules: { sections: [] },
648
- // },
649
- // {
650
- // attribute: 'size_system',
651
- // label: 'Size system',
652
- // description:
653
- // "The country of the size system used by your product. If you don't submit the attribute, the default value is your target country. Supported values: US, EU, UK, DE, FR, JP, CN, IT, BR, MEX, AU. Example: US",
654
- // required: false,
655
- // type: 'macro-input',
656
- // validator: (v: string | number) =>
657
- // validateIfNoMacro(v, z.enum(['US', 'EU', 'UK', 'DE', 'FR', 'JP', 'CN', 'IT', 'BR', 'MEX', 'AU', ''])),
658
- // rules: { sections: [] },
659
- // },
660
- // {
661
- // attribute: 'item_group_id',
662
- // label: 'Item group ID',
663
- // description:
664
- // 'ID for a group of products that come in different versions (variants). Required (Brazil, France, Germany, Japan, the United Kingdom, and the US if the product is a variant). Required for free listings for all product variants. Max 50 alphanumeric characters. Example: AB12345',
665
- // required: false,
666
- // type: 'macro-input',
667
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().max(50)),
668
- // defaultValue: '{{shopify.id}}',
669
- // rules: { sections: [] },
670
- // },
671
- // {
672
- // attribute: 'product_length',
673
- // label: 'Product length',
674
- // description:
675
- // "Your product's length. Supported values: 0-3000. Syntax: Number + unit. Supported units: cm, in. Example: 20 in",
676
- // required: false,
677
- // type: 'macro-input',
678
- // validator: (v: string | number) => validateIfNoMacro(v, measureUnitsSchema(['cm', 'in'])),
679
- // defaultValue: '',
680
- // rules: { sections: [] },
681
- // },
682
- // {
683
- // attribute: 'product_width',
684
- // label: 'Product width',
685
- // description:
686
- // "Your product's width. Supported values: 0-3000. Syntax: Number + unit. Supported units: cm, in. Example: 20 in",
687
- // required: false,
688
- // type: 'macro-input',
689
- // validator: (v: string | number) => validateIfNoMacro(v, measureUnitsSchema(['cm', 'in'])),
690
- // defaultValue: '',
691
- // rules: { sections: [] },
692
- // },
693
- // {
694
- // attribute: 'product_height',
695
- // label: 'Product height',
696
- // description:
697
- // "Your product's height. Supported values: 0-3000. Syntax: Number + unit. Supported units: cm, in. Example: 20 in",
698
- // required: false,
699
- // type: 'macro-input',
700
- // validator: (v: string | number) => validateIfNoMacro(v, measureUnitsSchema(['cm', 'in'])),
701
- // defaultValue: '',
702
- // rules: { sections: [] },
703
- // },
704
- // {
705
- // attribute: 'product_weight',
706
- // label: 'Product weight',
707
- // description:
708
- // "Your product's weight. Supported values: 0-2000. Syntax: Number + unit. Supported units: lb, oz, g, kb. Example: 3.5 lb",
709
- // required: false,
710
- // type: 'macro-input',
711
- // validator: (v: string | number) => validateIfNoMacro(v, measureUnitsSchema(['lb', 'oz', 'g', 'kg'])),
712
- // defaultValue: '{{shopify.weight}} {{shopify.weight_unit}}',
713
- // rules: { sections: [] },
714
- // },
715
- // {
716
- // attribute: 'product_detail',
717
- // label: 'Product detail',
718
- // description:
719
- // "Technical specifications or additional details of your product. This attribute uses 3 sub-attributes: Section name: Max 140 characters. Attribute name: Max 140 characters. Attribute value: Max 1000 characters. Don't add information covered in other attributes, all capital letters, gimmicky foreign characters, promotion text, or list keywords or search terms. Example: General:Product Type:Digital player",
720
- // required: false,
721
- // type: 'macro-input',
722
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().max(1280)),
723
- // defaultValue: '',
724
- // rules: { sections: [] },
725
- // },
726
- // {
727
- // attribute: 'product_highlight',
728
- // label: 'Product highlight',
729
- // description:
730
- // 'The most relevant highlights of your products. Max 150 characters. Example: Supports thousands of apps, including Netflix, YouTube, and HBO Max',
731
- // required: false,
732
- // type: 'macro-input',
733
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().max(150)),
734
- // defaultValue: '',
735
- // rules: { sections: [] },
736
- // },
737
- // {
738
- // attribute: 'ads_redirect',
739
- // label: 'Ads redirect',
740
- // description:
741
- // 'A URL used to specify additional parameters for your product page. Customers will be sent to this URL rather than the value that you submit for the link or mobile link attributes. Submit the same registered domain as for the link attribute (and the mobile link attribute, if present). Max 2000 characters. Example: http://www.example.com/product.html',
742
- // required: false,
743
- // type: 'macro-input',
744
- // // TODO: check for the same domain with link and mobile link
745
- // validator: (v: string | number) =>
746
- // validateIfNoMacro(
747
- // v,
748
- // z
749
- // .string()
750
- // .max(2000)
751
- // .refine(val => !val || z.string().url().safeParse(val).success, { message: 'invalid URL' })
752
- // ),
753
- // defaultValue: '',
754
- // rules: { sections: [] },
755
- // },
756
- // ...[0, 1, 2, 3, 4].map(i => ({
757
- // ...customLabelMapping,
758
- // attribute: `custom_label_${i}`,
759
- // label: `Custom Label ${i + 1}`,
760
- // })),
761
- // {
762
- // attribute: 'promotion_id',
763
- // label: 'Promotion ID',
764
- // description:
765
- // 'An identifier that allows you to match products to promotions. Required for promotions in Australia, France, Germany, India, the UK and the US. Max 50 characters. Example: ABC123',
766
- // required: false,
767
- // type: 'macro-input',
768
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().max(15)),
769
- // defaultValue: '',
770
- // rules: { sections: [] },
771
- // },
772
- // {
773
- // attribute: 'lifestyle_image_link',
774
- // label: 'Lifestyle image link',
775
- // description:
776
- // 'Attribute used to include the URL for a lifestyle image for your product. Only available for browsy surfaces. Max 2000 characters. Example: https://www.example.com/image1.jpg',
777
- // required: false,
778
- // type: 'macro-input',
779
- // validator: (v: string | number) =>
780
- // validateIfNoMacro(
781
- // v,
782
- // z
783
- // .string()
784
- // .max(2000)
785
- // .refine(val => !val || z.string().url().safeParse(val).success, { message: 'invalid URL' })
786
- // ),
787
- // defaultValue: '',
788
- // rules: { sections: [] },
789
- // },
790
- // {
791
- // attribute: 'external_seller_id',
792
- // label: 'External seller ID',
793
- // description:
794
- // 'Note: Marketplaces is currently only available in the classic version of Merchant Center.Required for multi-seller account. Used by a marketplace to externally identify a seller. (For example, on a website). 1 - 50 characters. Example: SellerPublicName1991',
795
- // required: false,
796
- // type: 'macro-input',
797
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().max(50)),
798
- // defaultValue: '',
799
- // rules: { sections: [] },
800
- // },
801
- // {
802
- // attribute: 'exclude_destination',
803
- // label: 'Exclude destination',
804
- // description:
805
- // 'A setting that you can use to exclude a product from participating in a specific type of advertising campaign. Supported values: Shopping_ads, Buy_on_Google_listings, Display_ads, Local_inventory_ads, Free_listings, Free_local_listings, YouTube_Shopping. Example: Shopping_ads',
806
- // required: false,
807
- // type: 'macro-input',
808
- // validator: (v: string | number) =>
809
- // validateIfNoMacro(
810
- // v,
811
- // z.enum([
812
- // 'Shopping_ads',
813
- // 'Buy_on_Google_listings',
814
- // 'Display_ads',
815
- // 'Local_inventory_ads',
816
- // 'Free_listings',
817
- // 'Free_local_listings',
818
- // 'YouTube_Shopping',
819
- // '',
820
- // ])
821
- // ),
822
- // defaultValue: '',
823
- // rules: { sections: [] },
824
- // },
825
- // {
826
- // attribute: 'included_destination',
827
- // label: 'Included destination',
828
- // description:
829
- // 'A setting that you can use to include a product in a specific type of advertising campaign. Supported values: Shopping_ads, Buy_on_Google_listings, Display_ads, Local_inventory_ads, Free_listings, Free_local_listings, YouTube_Shopping. Example: Shopping_ads',
830
- // required: false,
831
- // type: 'macro-input',
832
- // validator: (v: string | number) =>
833
- // validateIfNoMacro(
834
- // v,
835
- // z.enum([
836
- // 'Shopping_ads',
837
- // 'Buy_on_Google_listings',
838
- // 'Display_ads',
839
- // 'Local_inventory_ads',
840
- // 'Free_listings',
841
- // 'Free_local_listings',
842
- // 'YouTube_Shopping',
843
- // '',
844
- // ])
845
- // ),
846
- // defaultValue: '',
847
- // rules: { sections: [] },
848
- // },
849
- // {
850
- // attribute: 'shopping_ads_excluded_country',
851
- // label: 'Shopping ads excluded country',
852
- // description:
853
- // 'A setting that allows you to exclude countries where your products are advertised on Shopping ads. 2 characters. Must be an ISO_3166-1_alpha-2 country code. Only available for Shopping ads. Example: DE',
854
- // required: false,
855
- // type: 'select',
856
- // choices: [SOURCE_LANGUAGE],
857
- // defaultValue: '',
858
- // rules: { sections: [] },
859
- // },
860
- // {
861
- // attribute: 'pause',
862
- // label: 'Pause',
863
- // description:
864
- // 'A setting you can use to pause and quickly reactivate a product for all ads (including Shopping ads, Display ads, and local inventory ads). A product can be paused for up to 14 days. If a product is paused for more than 14 days it will be disapproved. To re-approve, remove the attribute.. Supported values: ads, no. Example: ads',
865
- // required: false,
866
- // type: 'macro-input',
867
- // validator: (v: string | number) => validateIfNoMacro(v, z.enum(['ads', ''])),
868
- // defaultValue: '',
869
- // rules: { sections: [] },
870
- // },
871
- // {
872
- // attribute: 'shipping',
873
- // label: 'Shipping',
874
- // description:
875
- // "Your product's shipping cost, shipping speeds, and the locations your product ships to. Shipping costs are required for Shopping ads and free listings for the following countries: Australia, Austria, Belgium, Canada, Czechia, France, Germany, India, Ireland, Israel, Italy, New Zealand, Japan, the Netherlands, Poland, Romania, South Korea, Spain, Switzerland, the UK, and the US. This attribute uses the following sub-attributes: Country (Required) ISO 3166 country code. Region (Optional). Postal code (Optional). Location ID (Optional). Location group name (Optional). Service (Optional) Service class or shipping speed. Price (Optional) Fixed shipping cost, including VAT if required. Minimum handling time and maximum handling time (Optional) To specify handling time. Minimum transit time and maximum transit time (Optional) To specify transit time. Supported prices: 0-1000 USD. Example: US:CA:Overnight:16.00 USD:1:1:2:3",
876
- // required: false,
877
- // type: 'macro-input',
878
- // defaultValue: '',
879
- // rules: { sections: [] },
880
- // },
881
- // {
882
- // attribute: 'shipping_label',
883
- // label: 'Shipping label',
884
- // description:
885
- // 'Label that you assign to a product to help assign correct shipping costs in Merchant Center account settings. Max 100 characters. Example: perishable',
886
- // type: 'macro-input',
887
- // defaultValue: '',
888
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().max(100)),
889
- // rules: { sections: [] },
890
- // },
891
- // {
892
- // attribute: 'shipping_weight',
893
- // label: 'Shipping weight',
894
- // description:
895
- // 'The weight of the product used to calculate the shipping cost. Required for carrier-calculated rates in your account shipping settings. Supported weights: 9-2000 lbs for imperial, 0-1000 kg from metric. Supported units: lb, oz, g, kg. Syntax: Number + unit. Example: 3 kg',
896
- // required: false,
897
- // type: 'macro-input',
898
- // validator: (v: string | number) => validateIfNoMacro(v, measureUnitsSchema(['lb', 'oz', 'g', 'kg'])),
899
- // defaultValue: '{{shopify.weight}} {{shopify.weight_unit}}',
900
- // rules: { sections: [] },
901
- // },
902
- // {
903
- // attribute: 'shipping_length',
904
- // label: 'Shipping length',
905
- // description:
906
- // 'The length of the product used to calculate the shipping cost by dimensional weight. Supported values: 0-150 for inches, 1-400 for cm. Syntax: Number + unit. Supported units: cm, in. Example: 20 in',
907
- // required: false,
908
- // type: 'macro-input',
909
- // validator: (v: string | number) => validateIfNoMacro(v, measureUnitsSchema(['cm', 'in'])),
910
- // defaultValue: '',
911
- // rules: { sections: [] },
912
- // },
913
- // {
914
- // attribute: 'shipping_width',
915
- // label: 'Shipping width',
916
- // description:
917
- // 'The width of the product used to calculate the shipping cost by dimensional weight. Required for carrier-calculated rates in your account shipping settings. Supported values: 0-3000. Syntax: Number + unit. Supported units: cm, in. Example: 20 in',
918
- // required: false,
919
- // type: 'macro-input',
920
- // validator: (v: string | number) => validateIfNoMacro(v, measureUnitsSchema(['cm', 'in'])),
921
- // defaultValue: '',
922
- // rules: { sections: [] },
923
- // },
924
- // {
925
- // attribute: 'shipping_height',
926
- // label: 'Shipping height',
927
- // description:
928
- // 'The height of the product used to calculate the shipping cost by dimensional weight. Required for carrier-calculated rates in your account shipping settings. Supported values: 0-3000. Syntax: Number + unit. Supported units: cm, in. Example: 20 in',
929
- // required: false,
930
- // type: 'macro-input',
931
- // validator: (v: string | number) => validateIfNoMacro(v, measureUnitsSchema(['cm', 'in'])),
932
- // defaultValue: '',
933
- // rules: { sections: [] },
934
- // },
935
- // {
936
- // attribute: 'ships_from_country',
937
- // label: 'Ships from country',
938
- // description:
939
- // 'A setting that allows you to provide the country from which your product will typically ship. 2 characters. Must be an ISO_3166-1_alpha-2 country code. Example: DE',
940
- // required: false,
941
- // type: 'select',
942
- // choices: [SOURCE_LANGUAGE],
943
- // defaultValue: '',
944
- // rules: { sections: [] },
945
- // },
946
- // {
947
- // attribute: 'max_handling_time',
948
- // label: 'Max handling time',
949
- // description:
950
- // 'The longest amount of time between when an order is placed for a product and when the product ships. Submit the number of business days. For products ready to be shipped the same day, submit 0. Integer, greater than or equal to 0. Example: 3',
951
- // required: false,
952
- // type: 'macro-input',
953
- // validator: (v: string | number) => validateIfNoMacro(v, z.coerce.number().min(0).int()),
954
- // defaultValue: '',
955
- // rules: { sections: [] },
956
- // },
957
- // {
958
- // attribute: 'transit_time_label',
959
- // label: 'Transit time label',
960
- // description:
961
- // 'Label that you assign to a product to help assign different transit times in Merchant Center account settings. Max 100 characters. Example: From Seattle',
962
- // required: false,
963
- // type: 'macro-input',
964
- // defaultValue: '',
965
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().max(100)),
966
- // rules: { sections: [] },
967
- // },
968
- // {
969
- // attribute: 'min_handling_time',
970
- // label: 'Min handling time',
971
- // description:
972
- // 'The shortest amount of time between when an order is placed for a product and when the product ships. Submit the number of business days. For products ready to be shipped the same day, submit 0. Integer, greater than or equal to 0. Example: 1',
973
- // required: false,
974
- // type: 'macro-input',
975
- // validator: (v: string | number) => validateIfNoMacro(v, z.coerce.number().min(0).int()),
976
- // defaultValue: '',
977
- // rules: { sections: [] },
978
- // },
979
- // {
980
- // attribute: 'free_shipping_threshold',
981
- // label: 'Free shipping threshold',
982
- // description:
983
- // 'Order cost above which shipping is free. This attribute uses the following sub-attributes: Country (Required) ISO 3166 country code. Price threshold (Required) Order cost above which shipping is free. Example: US:16.00 USD',
984
- // required: false,
985
- // type: 'macro-input',
986
- // defaultValue: '',
987
- // rules: { sections: [] },
988
- // },
989
- // {
990
- // attribute: 'tax',
991
- // label: 'Tax',
992
- // description:
993
- // 'Your product’s sales tax rate in percent. Required (Available for the US only). This attribute uses 4 sub-attributes: Country (optional) ISO 3166 country code. Region or postal code or location ID (optional). Rate (required) Tax rate as a percentage. Shipping tax (optional) Specify if you charge tax on shipping, supported values: yes, no. Example: US:CA:5.00:y',
994
- // required: false,
995
- // type: 'macro-input',
996
- // defaultValue: '',
997
- // // TODO: add validator to check if US
998
- // rules: { sections: [] },
999
- // },
1000
- // {
1001
- // attribute: 'tax_category',
1002
- // label: 'Tax category',
1003
- // description:
1004
- // 'A category that classifies your product by specific tax rules. Recommended for custom tax rates at the account level. Max 100 characters. Example: Apparel',
1005
- // required: false,
1006
- // type: 'macro-input',
1007
- // defaultValue: '',
1008
- // validator: (v: string | number) => validateIfNoMacro(v, z.string().max(100)),
1009
- // rules: { sections: [] },
1010
- // },
1011
- // ],
1012
- // };
444
+ const result = priceSchema().safeParse(amount);
445
+ if (!result.success) {
446
+ ctx.addIssue({
447
+ code: z.ZodIssueCode.custom,
448
+ message: result.error.issues[0].message,
449
+ });
450
+ }
451
+ })
452
+ ),
453
+ defaultValue: '',
454
+ rules: { sections: [] },
455
+ },
456
+ {
457
+ attribute: 'googleProductCategory',
458
+ label: 'Google product category',
459
+ description:
460
+ 'Google-defined product category for your product. Example: Apparel & Accessories > Clothing > Outerwear > Coats & Jackets or 371',
461
+ required: false,
462
+ type: 'macro-input',
463
+ defaultValue: '',
464
+ rules: { sections: [] },
465
+ },
466
+ {
467
+ attribute: 'productTypes',
468
+ label: 'Product types',
469
+ description:
470
+ 'Product category that you define for your product. Max 750 alphanumeric character. Comma separated list. Example: Home > Women > Dresses > Maxi Dresses, Home > Men > T-Shirts',
471
+ required: false,
472
+ type: 'macro-input',
473
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().max(750)),
474
+ defaultValue: '{{shopify.category}}',
475
+ rules: { sections: [] },
476
+ },
477
+ {
478
+ attribute: 'brand',
479
+ label: 'Brand',
480
+ description: 'Your product’s brand name. Plain text. Max 70 characters. Example: Google',
481
+ required: true,
482
+ type: 'macro-input',
483
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().max(70).min(1)),
484
+ defaultValue: '{{shopify.vendor}}',
485
+ rules: { sections: [] },
486
+ baseMode: true,
487
+ },
488
+ {
489
+ attribute: 'gtin',
490
+ label: 'GTIN',
491
+ description:
492
+ 'Your product’s Global Trade Item Number (GTIN). Required (For all products with a known GTIN to enable full offer performance). Max 50 numeric characters. Example: 3234567890126',
493
+ required: false,
494
+ type: 'macro-input',
495
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().max(50)),
496
+ defaultValue: '{{shopify.barcode}}',
497
+ rules: { sections: [] },
498
+ },
499
+ {
500
+ attribute: 'mpn',
501
+ label: 'MPN',
502
+ description:
503
+ 'Your product’s Manufacturer Part Number (MPN). Required (Only if your product does not have a manufacturer assigned GTIN). Only submit MPNs assigned by a manufacturer. Max 70 alphanumeric characters. Example: GO12345OOGLE',
504
+ required: false,
505
+ type: 'macro-input',
506
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().max(70)),
507
+ defaultValue: '{{shopify.sku}}',
508
+ rules: { sections: [] },
509
+ },
510
+ {
511
+ attribute: 'identifierExists',
512
+ label: 'Identifier exists',
513
+ description:
514
+ 'Use to indicate whether or not the unique product identifiers (UPIs) GTIN, MPN, and brand are available for your product. Submit "no" if: Your product is a media item and the GTIN is unavailable (Note: ISBN and SBN codes are accepted as GTINs or Your product is an apparel (clothing) item and the brand is unavailable or In all other categories, your product doesn’t have a GTIN, or a combination of MPN and brand. If a product does have unique product identifiers, don’t submit this attribute with a value of “no” or the product may be disapproved. Example: no',
515
+ required: false,
516
+ type: 'macro-input',
517
+ validator: (v: string | number) => validateIfNoMacro(v, z.enum(['yes', 'no', ''])),
518
+ defaultValue: 'yes',
519
+ rules: { sections: [] },
520
+ },
521
+ {
522
+ attribute: 'condition',
523
+ label: 'Condition',
524
+ description:
525
+ 'The condition of your product at time of sale. Required if your product is used or refurbished. Supported values: new, refurbished, used. Example: new',
526
+ required: false,
527
+ type: 'macro-input',
528
+ validator: (v: string | number) => validateIfNoMacro(v, z.enum(['new', 'refurbished', 'used', ''])),
529
+ defaultValue: '',
530
+ rules: { sections: [] },
531
+ },
532
+ {
533
+ attribute: 'adult',
534
+ label: 'Adult',
535
+ description:
536
+ 'Indicate a product includes sexually suggestive content. Required (If a product contains adult content). Supported values: yes, no. Example: yes',
537
+ required: false,
538
+ type: 'macro-input',
539
+ validator: (v: string | number) => validateIfNoMacro(v, z.enum(['yes', 'no', ''])),
540
+ defaultValue: '',
541
+ rules: { sections: [] },
542
+ },
543
+ {
544
+ attribute: 'multipack',
545
+ label: 'Multipack',
546
+ description:
547
+ "The number of identical products sold within a merchant-defined multipack. Required (For multipack products in Australia, Brazil, Czechia, France, Germany, Italy, Japan, Netherlands, Spain, Switzerland, the UK and the US). Required for free listings on Google if you’ve created a multipack. Submit this attribute if you defined a custom group of identical products and are selling them as a single unit of sale (for example, you're selling 6 bars of soap together). If the product's manufacturer assembled the multipack instead of you, don't submit this attribute. Example: 6",
548
+ required: false,
549
+ type: 'macro-input',
550
+ validator: (v: string | number) => validateIfNoMacro(v, z.coerce.number().min(0)),
551
+ defaultValue: '',
552
+ rules: { sections: [] },
553
+ },
554
+ {
555
+ attribute: 'isBundle',
556
+ label: 'Bundle',
557
+ description:
558
+ 'Indicates a product is a merchant-defined custom group of different products featuring one main product. Required (For bundles in Australia, Brazil, Czechia, France, Germany, Italy, Japan, Netherlands, Spain, Switzerland, the UK and the US). Required for free listings on Google if you’ve created a bundle containing a main product. Supported values: yes, no. Example: yes',
559
+ required: false,
560
+ type: 'macro-input',
561
+ validator: (v: string | number) => validateIfNoMacro(v, z.enum(['yes', 'no', ''])),
562
+ defaultValue: '',
563
+ rules: { sections: [] },
564
+ },
565
+ // {
566
+ // attribute: 'certification',
567
+ // label: 'Certification',
568
+ // description:
569
+ // 'Certifications, such as energy efficiency ratings, associated with your product. Available for the EU and EFTA countries and the UK. Required for products that require certain certification information to be shown in your Shopping ads or free listings, for example due to local energy efficiency labeling regulations. Example: EC:EPREL:123456',
570
+ // required: false,
571
+ // type: 'macro-input',
572
+ // defaultValue: '',
573
+ // rules: { sections: [] },
574
+ // },
575
+ {
576
+ attribute: 'maxEnergyEfficiencyClass',
577
+ label: 'Energy efficiency class',
578
+ description:
579
+ 'Your product’s energy label. Available for the EU and EFTA countries and the UK. Note: This attribute is being deprecated. Please use the certification attribute instead to show the EU energy efficiency class. Supported values: A, A+, A++, A+++, B, C, D, E, F, G. Example: A+',
580
+ required: false,
581
+ type: 'macro-input',
582
+ validator: (v: string | number) =>
583
+ validateIfNoMacro(v, z.enum(['A', 'A+', 'A++', 'A+++', 'B', 'C', 'D', 'E', 'F', 'G', ''])),
584
+ defaultValue: '',
585
+ rules: { sections: [] },
586
+ },
587
+ {
588
+ attribute: 'ageGroup',
589
+ label: 'Age group',
590
+ description:
591
+ 'The demographic for which your product is intended. Required (For all apparel products that are targeted to people in Brazil, France, Germany, Japan, the UK, and the US as well as all products with assigned age groups). Required for free listings for all Apparel & Accessories (ID: 166) products. Supported values: newborn, infant, toddler, kids, adult. Example: adult',
592
+ required: false,
593
+ type: 'macro-input',
594
+ // TODO: add to check if product is apparel
595
+ validator: (v: string | number) =>
596
+ validateIfNoMacro(v, z.enum(['newborn', 'infant', 'toddler', 'kids', 'adult', ''])),
597
+ defaultValue: '',
598
+ rules: { sections: [] },
599
+ },
600
+ {
601
+ attribute: 'color',
602
+ label: 'Color',
603
+ description:
604
+ 'Your product’s color(s). Required (For all apparel products that are targeted to people in Brazil, France, Germany, Japan, the UK, and the US). Required for free listings for all Apparel & Accessories (ID: 166) products. Max 100 alphanumeric characters (max 40 characters per color). Example: Black',
605
+ required: false,
606
+ type: 'macro-input',
607
+ // TODO: add to check if product is apparel
608
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().max(100)),
609
+ rules: { sections: [] },
610
+ },
611
+ {
612
+ attribute: 'gender',
613
+ label: 'Gender',
614
+ description:
615
+ 'The gender for which your product is intended. Required (Required for all apparel items that are targeted to people in Brazil, France, Germany, Japan, the UK, and the US as well as all gender-specific products). Required for free listings for all Google Apparel & Accessories (ID: 166) products. Supported values: male, female, unisex. Example: unisex',
616
+ required: false,
617
+ type: 'macro-input',
618
+ // TODO: add to check if product is apparel
619
+ validator: (v: string | number) => validateIfNoMacro(v, z.enum(['male', 'female', 'unisex', ''])),
620
+ rules: { sections: [] },
621
+ },
622
+ {
623
+ attribute: 'material',
624
+ label: 'Material',
625
+ description:
626
+ 'Your product’s fabric or material. Required (if relevant for distinguishing different products in a set of variants). Max 200 characters. Example: leather',
627
+ required: false,
628
+ type: 'macro-input',
629
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().max(200)),
630
+ rules: { sections: [] },
631
+ },
632
+ {
633
+ attribute: 'pattern',
634
+ label: 'Pattern',
635
+ description:
636
+ 'Your product’s pattern or graphic print. Required (if relevant for distinguishing different products in a set of variants). Max 100 characters. Example: polka dot',
637
+ required: false,
638
+ type: 'macro-input',
639
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().max(100)),
640
+ rules: { sections: [] },
641
+ },
642
+ {
643
+ attribute: 'sizes',
644
+ label: 'Size',
645
+ description:
646
+ 'Your product’s size. Required (Required for all apparel products in Apparel & Accessories > Clothing (ID:1604) and Apparel & Accessories > Shoes (ID:187) categories targeted to people in Brazil, France, Germany, Japan, the UK, and the US as well as all products available in different sizes). Required for free listings for all Apparel & Accessories > Clothing (ID:1604) and Apparel & Accessories > Shoes (ID:187) products. Max 100 characters. Example: XL',
647
+ required: false,
648
+ type: 'macro-input',
649
+ // TODO: add to check if product is apparel
650
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().max(100)),
651
+ rules: { sections: [] },
652
+ },
653
+ {
654
+ attribute: 'sizeType',
655
+ label: 'Size type',
656
+ description:
657
+ 'Your apparel product’s cut. Available for apparel products only. Supported values: regular, petite, maternity, big, tall, plus. Example: maternity',
658
+ required: false,
659
+ type: 'macro-input',
660
+ validator: (v: string | number) =>
661
+ validateIfNoMacro(v, z.enum(['regular', 'petite', 'maternity', 'big', 'tall', 'plus', ''])),
662
+ rules: { sections: [] },
663
+ },
664
+ {
665
+ attribute: 'sizeSystem',
666
+ label: 'Size system',
667
+ description:
668
+ "The country of the size system used by your product. If you don't submit the attribute, the default value is your target country. Supported values: US, EU, UK, DE, FR, JP, CN, IT, BR, MEX, AU. Example: US",
669
+ required: false,
670
+ type: 'macro-input',
671
+ validator: (v: string | number) =>
672
+ validateIfNoMacro(v, z.enum(['US', 'EU', 'UK', 'DE', 'FR', 'JP', 'CN', 'IT', 'BR', 'MEX', 'AU', ''])),
673
+ rules: { sections: [] },
674
+ },
675
+ {
676
+ attribute: 'itemGroupId',
677
+ label: 'Item group ID',
678
+ description:
679
+ 'ID for a group of products that come in different versions (variants). Required (Brazil, France, Germany, Japan, the United Kingdom, and the US if the product is a variant). Required for free listings for all product variants. Max 50 alphanumeric characters. Example: AB12345',
680
+ required: false,
681
+ type: 'macro-input',
682
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().max(50)),
683
+ defaultValue: '{{shopify.id}}',
684
+ rules: { sections: [] },
685
+ },
686
+ {
687
+ attribute: 'productLength',
688
+ label: 'Product length',
689
+ description:
690
+ "Your product's length. Supported values: 0-3000. Syntax: Number + unit. Supported units: cm, in. Example: 20 in",
691
+ required: false,
692
+ type: 'macro-input',
693
+ validator: (v: string | number) => validateIfNoMacro(v, measureUnitsSchema(['cm', 'in'])),
694
+ defaultValue: '',
695
+ rules: { sections: [] },
696
+ },
697
+ {
698
+ attribute: 'productWidth',
699
+ label: 'Product width',
700
+ description:
701
+ "Your product's width. Supported values: 0-3000. Syntax: Number + unit. Supported units: cm, in. Example: 20 in",
702
+ required: false,
703
+ type: 'macro-input',
704
+ validator: (v: string | number) => validateIfNoMacro(v, measureUnitsSchema(['cm', 'in'])),
705
+ defaultValue: '',
706
+ rules: { sections: [] },
707
+ },
708
+ {
709
+ attribute: 'productHeight',
710
+ label: 'Product height',
711
+ description:
712
+ "Your product's height. Supported values: 0-3000. Syntax: Number + unit. Supported units: cm, in. Example: 20 in",
713
+ required: false,
714
+ type: 'macro-input',
715
+ validator: (v: string | number) => validateIfNoMacro(v, measureUnitsSchema(['cm', 'in'])),
716
+ defaultValue: '',
717
+ rules: { sections: [] },
718
+ },
719
+ {
720
+ attribute: 'productWeight',
721
+ label: 'Product weight',
722
+ description:
723
+ "Your product's weight. Supported values: 0-2000. Syntax: Number + unit. Supported units: lb, oz, g, kb. Example: 3.5 lb",
724
+ required: false,
725
+ type: 'macro-input',
726
+ validator: (v: string | number) => validateIfNoMacro(v, measureUnitsSchema(['lb', 'oz', 'g', 'kg'])),
727
+ defaultValue: '{{shopify.weight}} {{shopify.weight_unit}}',
728
+ rules: { sections: [] },
729
+ },
730
+ {
731
+ attribute: 'productDetails',
732
+ label: 'Product detail',
733
+ description:
734
+ "Technical specifications or additional details of your product. This attribute uses 3 sub-attributes: Section name: Max 140 characters. Attribute name: Max 140 characters. Attribute value: Max 1000 characters. Don't add information covered in other attributes, all capital letters, gimmicky foreign characters, promotion text, or list keywords or search terms. Example: General:Product Type:Digital player",
735
+ required: false,
736
+ type: 'macro-input',
737
+ validator: (v: string | number) =>
738
+ validateIfNoMacro(
739
+ v,
740
+ z
741
+ .string()
742
+ .max(1280)
743
+ .refine(v => !v || v.split(':').length === 3, { message: 'Invalid format' })
744
+ ),
745
+ defaultValue: '',
746
+ rules: { sections: [] },
747
+ },
748
+ {
749
+ attribute: 'productHighlights',
750
+ label: 'Product highlight',
751
+ description:
752
+ 'The most relevant highlights of your products. Max 150 characters. Example: Supports thousands of apps, including Netflix, YouTube, and HBO Max',
753
+ required: false,
754
+ type: 'macro-input',
755
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().max(150)),
756
+ defaultValue: '',
757
+ rules: { sections: [] },
758
+ },
759
+ {
760
+ attribute: 'adsRedirect',
761
+ label: 'Ads redirect',
762
+ description:
763
+ 'A URL used to specify additional parameters for your product page. Customers will be sent to this URL rather than the value that you submit for the link or mobile link attributes. Submit the same registered domain as for the link attribute (and the mobile link attribute, if present). Max 2000 characters. Example: http://www.example.com/product.html',
764
+ required: false,
765
+ type: 'macro-input',
766
+ // TODO: check for the same domain with link and mobile link
767
+ validator: (v: string | number) =>
768
+ validateIfNoMacro(
769
+ v,
770
+ z
771
+ .string()
772
+ .max(2000)
773
+ .refine(val => !val || z.string().url().safeParse(val).success, { message: 'invalid URL' })
774
+ ),
775
+ defaultValue: '',
776
+ rules: { sections: [] },
777
+ },
778
+ ...([0, 1, 2, 3, 4].map(i => ({
779
+ ...customLabelMapping,
780
+ attribute: `customLabel${i}`,
781
+ label: `Custom Label ${i + 1}`,
782
+ })) as any),
783
+ {
784
+ attribute: 'promotionIds',
785
+ label: 'Promotion ID',
786
+ description:
787
+ 'An identifier that allows you to match products to promotions. Required for promotions in Australia, France, Germany, India, the UK and the US. Max 50 characters. Example: ABC123',
788
+ required: false,
789
+ type: 'macro-input',
790
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().max(15)),
791
+ defaultValue: '',
792
+ rules: { sections: [] },
793
+ },
794
+ {
795
+ attribute: 'lifestyleImageLinks',
796
+ label: 'Lifestyle image link',
797
+ description:
798
+ 'Attribute used to include the URL for a lifestyle image for your product. Only available for browsy surfaces. Max 2000 characters. Example: https://www.example.com/image1.jpg',
799
+ required: false,
800
+ type: 'macro-input',
801
+ validator: (v: string | number) =>
802
+ validateIfNoMacro(
803
+ v,
804
+ z
805
+ .string()
806
+ .max(2000)
807
+ .refine(val => !val || z.string().url().safeParse(val).success, { message: 'invalid URL' })
808
+ ),
809
+ defaultValue: '',
810
+ rules: { sections: [] },
811
+ },
812
+ {
813
+ attribute: 'externalSellerId',
814
+ label: 'External seller ID',
815
+ description:
816
+ 'Note: Marketplaces is currently only available in the classic version of Merchant Center.Required for multi-seller account. Used by a marketplace to externally identify a seller. (For example, on a website). 1 - 50 characters. Example: SellerPublicName1991',
817
+ required: false,
818
+ type: 'macro-input',
819
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().max(50)),
820
+ defaultValue: '',
821
+ rules: { sections: [] },
822
+ },
823
+ {
824
+ attribute: 'excludedDestinations',
825
+ label: 'Exclude destination',
826
+ description:
827
+ 'A setting that you can use to exclude a product from participating in a specific type of advertising campaign. Supported values: Shopping_ads, Buy_on_Google_listings, Display_ads, Local_inventory_ads, Free_listings, Free_local_listings, YouTube_Shopping. Comma separated list. Example: Shopping_ads',
828
+ required: false,
829
+ type: 'macro-input',
830
+ validator: (v: string | number) =>
831
+ validateIfNoMacro(
832
+ v,
833
+ z
834
+ .string()
835
+ .refine(
836
+ v =>
837
+ !v ||
838
+ v
839
+ .split(',')
840
+ .every((val: string) =>
841
+ [
842
+ 'Shopping_ads',
843
+ 'Buy_on_Google_listings',
844
+ 'Display_ads',
845
+ 'Local_inventory_ads',
846
+ 'Free_listings',
847
+ 'Free_local_listings',
848
+ 'YouTube_Shopping',
849
+ ].includes(val.trim())
850
+ ),
851
+ { message: 'Invalid value' }
852
+ )
853
+ ),
854
+ defaultValue: '',
855
+ rules: { sections: [] },
856
+ },
857
+ {
858
+ attribute: 'includedDestinations',
859
+ label: 'Included destination',
860
+ description:
861
+ 'A setting that you can use to include a product in a specific type of advertising campaign. Supported values: Shopping_ads, Buy_on_Google_listings, Display_ads, Local_inventory_ads, Free_listings, Free_local_listings, YouTube_Shopping. Example: Shopping_ads',
862
+ required: false,
863
+ type: 'macro-input',
864
+ validator: (v: string | number) =>
865
+ validateIfNoMacro(
866
+ v,
867
+ z
868
+ .string()
869
+ .refine(
870
+ v =>
871
+ !v ||
872
+ v
873
+ .split(',')
874
+ .every((val: string) =>
875
+ [
876
+ 'Shopping_ads',
877
+ 'Buy_on_Google_listings',
878
+ 'Display_ads',
879
+ 'Local_inventory_ads',
880
+ 'Free_listings',
881
+ 'Free_local_listings',
882
+ 'YouTube_Shopping',
883
+ ].includes(val.trim())
884
+ ),
885
+ { message: 'Invalid value' }
886
+ )
887
+ ),
888
+ defaultValue: '',
889
+ rules: { sections: [] },
890
+ },
891
+ {
892
+ attribute: 'shoppingAdsExcludedCountries',
893
+ label: 'Shopping ads excluded country',
894
+ description:
895
+ 'A setting that allows you to exclude countries where your products are advertised on Shopping ads. 2 characters. Must be an ISO_3166-1_alpha-2 country code. Only available for Shopping ads. Example: DE',
896
+ required: false,
897
+ type: 'multi-select',
898
+ choices: [SOURCE_LANGUAGE],
899
+ defaultValue: '',
900
+ rules: { sections: [] },
901
+ },
902
+ {
903
+ attribute: 'pause',
904
+ label: 'Pause',
905
+ description:
906
+ 'A setting you can use to pause and quickly reactivate a product for all ads (including Shopping ads, Display ads, and local inventory ads). A product can be paused for up to 14 days. If a product is paused for more than 14 days it will be disapproved. To re-approve, remove the attribute.. Supported values: ads, no. Example: ads',
907
+ required: false,
908
+ type: 'macro-input',
909
+ validator: (v: string | number) => validateIfNoMacro(v, z.enum(['ads', ''])),
910
+ defaultValue: '',
911
+ rules: { sections: [] },
912
+ },
913
+ {
914
+ attribute: 'shipping',
915
+ label: 'Shipping',
916
+ description:
917
+ "Your product's shipping cost, shipping speeds, and the locations your product ships to. Shipping costs are required for Shopping ads and free listings for the following countries: Australia, Austria, Belgium, Canada, Czechia, France, Germany, India, Ireland, Israel, Italy, New Zealand, Japan, the Netherlands, Poland, Romania, South Korea, Spain, Switzerland, the UK, and the US. This attribute uses the following sub-attributes: Country (Required) ISO 3166 country code. Region (Optional). Postal code (Optional). Location ID (Optional). Location group name (Optional). Service (Optional) Service class or shipping speed. Price (Optional) Fixed shipping cost, including VAT if required. Minimum handling time and maximum handling time (Optional) To specify handling time. Minimum transit time and maximum transit time (Optional) To specify transit time. Supported prices: 0-1000 USD. Example: US:CA:::Overnight:16.00 USD:1:1:2:3",
918
+ required: false,
919
+ type: 'macro-input',
920
+ defaultValue: '',
921
+ rules: { sections: [] },
922
+ },
923
+ {
924
+ attribute: 'shippingLabel',
925
+ label: 'Shipping label',
926
+ description:
927
+ 'Label that you assign to a product to help assign correct shipping costs in Merchant Center account settings. Max 100 characters. Example: perishable',
928
+ type: 'macro-input',
929
+ defaultValue: '',
930
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().max(100)),
931
+ rules: { sections: [] },
932
+ },
933
+ {
934
+ attribute: 'shippingWeight',
935
+ label: 'Shipping weight',
936
+ description:
937
+ 'The weight of the product used to calculate the shipping cost. Required for carrier-calculated rates in your account shipping settings. Supported weights: 9-2000 lbs for imperial, 0-1000 kg from metric. Supported units: lb, oz, g, kg. Syntax: Number + unit. Example: 3 kg',
938
+ required: false,
939
+ type: 'macro-input',
940
+ validator: (v: string | number) => validateIfNoMacro(v, measureUnitsSchema(['lb', 'oz', 'g', 'kg'])),
941
+ defaultValue: '{{shopify.weight}} {{shopify.weight_unit}}',
942
+ rules: { sections: [] },
943
+ },
944
+ {
945
+ attribute: 'shippingLength',
946
+ label: 'Shipping length',
947
+ description:
948
+ 'The length of the product used to calculate the shipping cost by dimensional weight. Supported values: 0-150 for inches, 1-400 for cm. Syntax: Number + unit. Supported units: cm, in. Example: 20 in',
949
+ required: false,
950
+ type: 'macro-input',
951
+ validator: (v: string | number) => validateIfNoMacro(v, measureUnitsSchema(['cm', 'in'])),
952
+ defaultValue: '',
953
+ rules: { sections: [] },
954
+ },
955
+ {
956
+ attribute: 'shippingWidth',
957
+ label: 'Shipping width',
958
+ description:
959
+ 'The width of the product used to calculate the shipping cost by dimensional weight. Required for carrier-calculated rates in your account shipping settings. Supported values: 0-3000. Syntax: Number + unit. Supported units: cm, in. Example: 20 in',
960
+ required: false,
961
+ type: 'macro-input',
962
+ validator: (v: string | number) => validateIfNoMacro(v, measureUnitsSchema(['cm', 'in'])),
963
+ defaultValue: '',
964
+ rules: { sections: [] },
965
+ },
966
+ {
967
+ attribute: 'shippingHeight',
968
+ label: 'Shipping height',
969
+ description:
970
+ 'The height of the product used to calculate the shipping cost by dimensional weight. Required for carrier-calculated rates in your account shipping settings. Supported values: 0-3000. Syntax: Number + unit. Supported units: cm, in. Example: 20 in',
971
+ required: false,
972
+ type: 'macro-input',
973
+ validator: (v: string | number) => validateIfNoMacro(v, measureUnitsSchema(['cm', 'in'])),
974
+ defaultValue: '',
975
+ rules: { sections: [] },
976
+ },
977
+ {
978
+ attribute: 'maxHandlingTime',
979
+ label: 'Max handling time',
980
+ description:
981
+ 'The longest amount of time between when an order is placed for a product and when the product ships. Submit the number of business days. For products ready to be shipped the same day, submit 0. Integer, greater than or equal to 0. Example: 3',
982
+ required: false,
983
+ type: 'macro-input',
984
+ validator: (v: string | number) => validateIfNoMacro(v, z.coerce.number().min(0).int()),
985
+ defaultValue: '',
986
+ rules: { sections: [] },
987
+ },
988
+ {
989
+ attribute: 'transitTimeLabel',
990
+ label: 'Transit time label',
991
+ description:
992
+ 'Label that you assign to a product to help assign different transit times in Merchant Center account settings. Max 100 characters. Example: From Seattle',
993
+ required: false,
994
+ type: 'macro-input',
995
+ defaultValue: '',
996
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().max(100)),
997
+ rules: { sections: [] },
998
+ },
999
+ {
1000
+ attribute: 'minHandlingTime',
1001
+ label: 'Min handling time',
1002
+ description:
1003
+ 'The shortest amount of time between when an order is placed for a product and when the product ships. Submit the number of business days. For products ready to be shipped the same day, submit 0. Integer, greater than or equal to 0. Example: 1',
1004
+ required: false,
1005
+ type: 'macro-input',
1006
+ validator: (v: string | number) => validateIfNoMacro(v, z.coerce.number().min(0).int()),
1007
+ defaultValue: '',
1008
+ rules: { sections: [] },
1009
+ },
1010
+ {
1011
+ attribute: 'taxes',
1012
+ label: 'Tax',
1013
+ description:
1014
+ 'Your product’s sales tax rate in percent. Required (Available for the US only). This attribute uses 4 sub-attributes: Country (optional) ISO 3166 country code. Region or postal code or location ID (optional). Rate (required) Tax rate as a percentage. Shipping tax (optional) Specify if you charge tax on shipping, supported values: yes, no. Example: US:CA:5.00:y',
1015
+ required: false,
1016
+ type: 'macro-input',
1017
+ defaultValue: '',
1018
+ // TODO: add validator to check if US
1019
+ rules: { sections: [] },
1020
+ },
1021
+ {
1022
+ attribute: 'taxCategory',
1023
+ label: 'Tax category',
1024
+ description:
1025
+ 'A category that classifies your product by specific tax rules. Recommended for custom tax rates at the account level. Max 100 characters. Example: Apparel',
1026
+ required: false,
1027
+ type: 'macro-input',
1028
+ defaultValue: '',
1029
+ validator: (v: string | number) => validateIfNoMacro(v, z.string().max(100)),
1030
+ rules: { sections: [] },
1031
+ },
1032
+ ],
1033
+ };