@veyralabs/skills 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,298 @@
1
+ # GraphQL Queries — shopify-dev Reference
2
+
3
+ Ready-to-use queries for Storefront API and Admin API.
4
+ Check API version in your app config before using — Shopify deprecates quarterly.
5
+
6
+ ---
7
+
8
+ ## Storefront API
9
+
10
+ Use for: public-facing data, headless storefronts, Hydrogen.
11
+ Auth: public Storefront API token (safe for frontend).
12
+
13
+ ### Products
14
+
15
+ ```graphql
16
+ query GetProduct($handle: String!) {
17
+ product(handle: $handle) {
18
+ id
19
+ title
20
+ handle
21
+ description
22
+ descriptionHtml
23
+ vendor
24
+ productType
25
+ tags
26
+ priceRange {
27
+ minVariantPrice { amount currencyCode }
28
+ maxVariantPrice { amount currencyCode }
29
+ }
30
+ compareAtPriceRange {
31
+ minVariantPrice { amount currencyCode }
32
+ }
33
+ featuredImage { url altText width height }
34
+ images(first: 20) {
35
+ nodes { id url altText width height }
36
+ }
37
+ variants(first: 100) {
38
+ nodes {
39
+ id
40
+ title
41
+ availableForSale
42
+ quantityAvailable
43
+ price { amount currencyCode }
44
+ compareAtPrice { amount currencyCode }
45
+ selectedOptions { name value }
46
+ image { url altText }
47
+ }
48
+ }
49
+ seo { title description }
50
+ }
51
+ }
52
+ ```
53
+
54
+ ### Collections
55
+
56
+ ```graphql
57
+ query GetCollection($handle: String!, $cursor: String) {
58
+ collection(handle: $handle) {
59
+ id
60
+ title
61
+ handle
62
+ description
63
+ image { url altText }
64
+ products(first: 24, after: $cursor, sortKey: BEST_SELLING) {
65
+ pageInfo { hasNextPage endCursor }
66
+ nodes {
67
+ id
68
+ title
69
+ handle
70
+ featuredImage { url altText }
71
+ priceRange {
72
+ minVariantPrice { amount currencyCode }
73
+ }
74
+ availableForSale
75
+ }
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
81
+ ### Cart
82
+
83
+ ```graphql
84
+ mutation CartCreate($lines: [CartLineInput!], $buyerIdentity: CartBuyerIdentityInput) {
85
+ cartCreate(input: { lines: $lines, buyerIdentity: $buyerIdentity }) {
86
+ cart {
87
+ id
88
+ checkoutUrl
89
+ totalQuantity
90
+ cost {
91
+ totalAmount { amount currencyCode }
92
+ subtotalAmount { amount currencyCode }
93
+ }
94
+ lines(first: 100) {
95
+ nodes {
96
+ id
97
+ quantity
98
+ merchandise {
99
+ ... on ProductVariant {
100
+ id
101
+ title
102
+ price { amount currencyCode }
103
+ product { title handle featuredImage { url } }
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }
109
+ userErrors { field message }
110
+ }
111
+ }
112
+ ```
113
+
114
+ ```graphql
115
+ mutation CartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
116
+ cartLinesAdd(cartId: $cartId, lines: $lines) {
117
+ cart { id totalQuantity cost { totalAmount { amount currencyCode } } }
118
+ userErrors { field message }
119
+ }
120
+ }
121
+ ```
122
+
123
+ ```graphql
124
+ mutation CartLinesUpdate($cartId: ID!, $lines: [CartLineUpdateInput!]!) {
125
+ cartLinesUpdate(cartId: $cartId, lines: $lines) {
126
+ cart { id totalQuantity }
127
+ userErrors { field message }
128
+ }
129
+ }
130
+ ```
131
+
132
+ ### Search (Predictive)
133
+
134
+ ```graphql
135
+ query PredictiveSearch($query: String!) {
136
+ predictiveSearch(query: $query, limit: 5) {
137
+ products {
138
+ id
139
+ title
140
+ handle
141
+ featuredImage { url altText }
142
+ priceRange { minVariantPrice { amount currencyCode } }
143
+ }
144
+ collections { id title handle }
145
+ pages { id title handle }
146
+ }
147
+ }
148
+ ```
149
+
150
+ ### Customer
151
+
152
+ ```graphql
153
+ query GetCustomer($customerAccessToken: String!) {
154
+ customer(customerAccessToken: $customerAccessToken) {
155
+ id
156
+ firstName
157
+ lastName
158
+ email
159
+ orders(first: 10, sortKey: PROCESSED_AT, reverse: true) {
160
+ nodes {
161
+ id
162
+ orderNumber
163
+ processedAt
164
+ financialStatus
165
+ fulfillmentStatus
166
+ totalPrice { amount currencyCode }
167
+ lineItems(first: 5) {
168
+ nodes {
169
+ title
170
+ quantity
171
+ variant { image { url } price { amount currencyCode } }
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
178
+ ```
179
+
180
+ ---
181
+
182
+ ## Admin API
183
+
184
+ Use for: app backends, webhooks, bulk operations.
185
+ Auth: Admin API access token (server-side only, never expose to client).
186
+
187
+ ### Product Create
188
+
189
+ ```graphql
190
+ mutation ProductCreate($input: ProductInput!) {
191
+ productCreate(input: $input) {
192
+ product {
193
+ id
194
+ title
195
+ handle
196
+ status
197
+ }
198
+ userErrors { field message }
199
+ }
200
+ }
201
+ ```
202
+
203
+ Variables:
204
+ ```json
205
+ {
206
+ "input": {
207
+ "title": "Product Title",
208
+ "descriptionHtml": "<p>Description</p>",
209
+ "vendor": "Brand Name",
210
+ "productType": "Category",
211
+ "status": "DRAFT",
212
+ "variants": [
213
+ {
214
+ "price": "29.99",
215
+ "sku": "SKU-001",
216
+ "inventoryQuantities": [{
217
+ "availableQuantity": 100,
218
+ "locationId": "gid://shopify/Location/YOUR_LOCATION_ID"
219
+ }]
220
+ }
221
+ ]
222
+ }
223
+ }
224
+ ```
225
+
226
+ ### Metafields Set (bulk)
227
+
228
+ ```graphql
229
+ mutation MetafieldsSet($metafields: [MetafieldsSetInput!]!) {
230
+ metafieldsSet(metafields: $metafields) {
231
+ metafields { key value namespace }
232
+ userErrors { field message }
233
+ }
234
+ }
235
+ ```
236
+
237
+ ### Bulk Operations (for large datasets)
238
+
239
+ ```graphql
240
+ mutation BulkOperationRunQuery($query: String!) {
241
+ bulkOperationRunQuery(query: $query) {
242
+ bulkOperation { id status }
243
+ userErrors { field message }
244
+ }
245
+ }
246
+ ```
247
+
248
+ Poll for completion:
249
+ ```graphql
250
+ query BulkOperationStatus {
251
+ currentBulkOperation {
252
+ id
253
+ status
254
+ errorCode
255
+ objectCount
256
+ fileSize
257
+ url
258
+ partialDataUrl
259
+ }
260
+ }
261
+ ```
262
+
263
+ ---
264
+
265
+ ## Common Patterns
266
+
267
+ ### Cursor-based pagination
268
+
269
+ ```typescript
270
+ async function fetchAllProducts(storefront) {
271
+ let allProducts = [];
272
+ let cursor = null;
273
+ let hasNextPage = true;
274
+
275
+ while (hasNextPage) {
276
+ const { products } = await storefront.query(PRODUCTS_QUERY, {
277
+ variables: { cursor },
278
+ });
279
+ allProducts.push(...products.nodes);
280
+ hasNextPage = products.pageInfo.hasNextPage;
281
+ cursor = products.pageInfo.endCursor;
282
+ }
283
+
284
+ return allProducts;
285
+ }
286
+ ```
287
+
288
+ ### Error handling for mutations
289
+
290
+ ```typescript
291
+ const { data } = await admin.graphql(MUTATION, { variables });
292
+
293
+ if (data.productCreate.userErrors.length > 0) {
294
+ throw new Error(
295
+ data.productCreate.userErrors.map(e => `${e.field}: ${e.message}`).join(', ')
296
+ );
297
+ }
298
+ ```
@@ -0,0 +1,286 @@
1
+ # Liquid Patterns — shopify-dev Reference
2
+
3
+ Common Liquid patterns, filters, and gotchas for Shopify theme development.
4
+
5
+ ---
6
+
7
+ ## Objects
8
+
9
+ ### Global objects (available everywhere)
10
+
11
+ | Object | What it is |
12
+ |--------|-----------|
13
+ | `shop` | Store settings, name, currency, policies |
14
+ | `cart` | Current cart (items, total, count) |
15
+ | `customer` | Logged-in customer (nil if guest) |
16
+ | `request` | Current request info (page_type, host) |
17
+ | `settings` | Theme settings from settings_data.json |
18
+ | `content_for_header` | Required in `<head>` — Shopify scripts injection |
19
+ | `content_for_layout` | Required in layout — renders template content |
20
+
21
+ ### Template-specific objects
22
+
23
+ | Template | Objects available |
24
+ |----------|------------------|
25
+ | product | `product`, `current_variant` |
26
+ | collection | `collection`, `current_tags`, `current_vendor` |
27
+ | article | `article`, `blog` |
28
+ | page | `page` |
29
+ | cart | `cart` |
30
+ | order (customer) | `order` |
31
+
32
+ ---
33
+
34
+ ## Filters
35
+
36
+ ### String filters
37
+
38
+ ```liquid
39
+ {{ product.title | upcase }}
40
+ {{ product.title | downcase }}
41
+ {{ product.title | capitalize }}
42
+ {{ product.description | strip_html }}
43
+ {{ product.description | truncate: 150 }}
44
+ {{ product.description | truncate_words: 30 }}
45
+ {{ " padded " | strip }}
46
+ {{ product.handle | replace: "-", " " }}
47
+ ```
48
+
49
+ ### Number and money filters
50
+
51
+ ```liquid
52
+ {{ product.price | money }} → "€29,99"
53
+ {{ product.price | money_without_currency }} → "29,99"
54
+ {{ product.price | money_with_currency }} → "29,99 EUR"
55
+ {{ product.price | divided_by: 100.0 }} → raw float
56
+ {{ 1.5 | round }} → 2
57
+ {{ 1.5 | floor }} → 1
58
+ {{ 1.5 | ceil }} → 2
59
+ ```
60
+
61
+ Note: Shopify stores prices in cents. `product.price` = 2999 = €29.99. Always use `| money` for display.
62
+
63
+ ### URL filters
64
+
65
+ ```liquid
66
+ {{ product.featured_image | img_url: '800x' }}
67
+ {{ product.featured_image | img_url: '800x600', crop: 'center' }}
68
+ {{ product.featured_image | image_url: width: 800 }} ← new Liquid syntax
69
+ {{ 'theme.css' | asset_url }}
70
+ {{ 'custom.js' | asset_url | script_tag }}
71
+ {{ product.url | within: collection }} ← collection-scoped URL
72
+ ```
73
+
74
+ ### Array filters
75
+
76
+ ```liquid
77
+ {{ product.tags | join: ", " }}
78
+ {{ collection.products | size }}
79
+ {{ product.images | first }}
80
+ {{ product.images | last }}
81
+ {{ product.variants | map: "title" | join: ", " }}
82
+ {{ collection.products | where: "available", true }}
83
+ {{ collection.products | sort: "price" }}
84
+ {{ collection.products | reverse }}
85
+ ```
86
+
87
+ ### Date filters
88
+
89
+ ```liquid
90
+ {{ article.created_at | date: "%B %d, %Y" }} → "January 15, 2025"
91
+ {{ article.created_at | date: "%Y-%m-%d" }} → "2025-01-15"
92
+ {{ "now" | date: "%s" | plus: 0 }} → Unix timestamp
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Tags
98
+
99
+ ### Conditionals
100
+
101
+ ```liquid
102
+ {% if customer %}
103
+ Hello, {{ customer.first_name }}
104
+ {% elsif request.page_type == "product" %}
105
+ Product page
106
+ {% else %}
107
+ Guest
108
+ {% endif %}
109
+
110
+ {% unless product.available %}
111
+ Sold out
112
+ {% endunless %}
113
+
114
+ {% case product.type %}
115
+ {% when "Shirt" %}
116
+ ...
117
+ {% when "Pants", "Trousers" %}
118
+ ...
119
+ {% else %}
120
+ ...
121
+ {% endcase %}
122
+ ```
123
+
124
+ ### Loops
125
+
126
+ ```liquid
127
+ {% for product in collection.products %}
128
+ {{ product.title }}
129
+ {% endfor %}
130
+
131
+ {% for i in (1..5) %}
132
+ {{ i }}
133
+ {% endfor %}
134
+
135
+ {% for tag in product.tags limit: 3 offset: 1 %}
136
+ {{ tag }}
137
+ {% endfor %}
138
+
139
+ {% for product in collection.products %}
140
+ {% if forloop.first %}First{% endif %}
141
+ {% if forloop.last %}Last{% endif %}
142
+ {{ forloop.index }} ← 1-based
143
+ {{ forloop.index0 }} ← 0-based
144
+ {% endfor %}
145
+ ```
146
+
147
+ ### Content inclusion
148
+
149
+ ```liquid
150
+ {% render 'card-product', product: product %}
151
+ {% render 'icon', name: 'cart' %}
152
+
153
+ {% section 'header' %}
154
+
155
+ {% include 'snippet' %} ← deprecated, use render
156
+ ```
157
+
158
+ `render` is sandboxed — parent variables not accessible unless passed explicitly.
159
+
160
+ ### Pagination
161
+
162
+ ```liquid
163
+ {% paginate collection.products by 24 %}
164
+ {% for product in collection.products %}
165
+ ...
166
+ {% endfor %}
167
+
168
+ {{ paginate | default_pagination }}
169
+ {% endpaginate %}
170
+ ```
171
+
172
+ Custom pagination:
173
+ ```liquid
174
+ {% paginate collection.products by 24 %}
175
+ {% if paginate.previous %}
176
+ <a href="{{ paginate.previous.url }}">Previous</a>
177
+ {% endif %}
178
+
179
+ {% for part in paginate.parts %}
180
+ {% if part.is_link %}
181
+ <a href="{{ part.url }}">{{ part.title }}</a>
182
+ {% else %}
183
+ <span>{{ part.title }}</span>
184
+ {% endif %}
185
+ {% endfor %}
186
+
187
+ {% if paginate.next %}
188
+ <a href="{{ paginate.next.url }}">Next</a>
189
+ {% endif %}
190
+ {% endpaginate %}
191
+ ```
192
+
193
+ ---
194
+
195
+ ## Gotchas
196
+
197
+ ### Blank vs nil vs false
198
+
199
+ ```liquid
200
+ {% if product.metafields.custom.badge %} ← true if value exists AND not empty
201
+ {% if product.metafields.custom.badge != blank %} ← safer
202
+ {% if product.metafields.custom.badge == nil %} ← only nil check
203
+ ```
204
+
205
+ ### Price display edge case
206
+
207
+ Always check for variant availability before displaying compare_at_price:
208
+ ```liquid
209
+ {% if product.compare_at_price_max > product.price_min %}
210
+ <s>{{ product.compare_at_price_max | money }}</s>
211
+ {% endif %}
212
+ ```
213
+
214
+ ### render scope
215
+
216
+ ```liquid
217
+ {% render 'card', product: product %}
218
+ ```
219
+
220
+ Inside `card.liquid`, only `product` is accessible — not `collection`, `shop`, `settings`, etc. Pass each required variable explicitly.
221
+
222
+ `settings` is an exception — globally accessible in rendered snippets.
223
+
224
+ ### Forloop and break
225
+
226
+ Liquid has no `break` in for loops. Use `limit` to cap iterations, or build logic with conditionals and `continue`:
227
+
228
+ ```liquid
229
+ {% for product in collection.products limit: 10 %}
230
+ {% if product.available == false %}{% continue %}{% endif %}
231
+ {{ product.title }}
232
+ {% endfor %}
233
+ ```
234
+
235
+ ### Section settings output in JS
236
+
237
+ ```liquid
238
+ <script>
239
+ const threshold = {{ section.settings.countdown_hours | times: 3600 }};
240
+ const color = {{ section.settings.text_color | json }};
241
+ </script>
242
+ ```
243
+
244
+ `| json` escapes strings correctly. Never interpolate raw strings into JS.
245
+
246
+ ### Metafield access
247
+
248
+ ```liquid
249
+ {{ product.metafields.custom.size_guide }}
250
+ {{ product.metafields["reviews"]["rating"] }}
251
+
252
+ {% assign guide = product.metafields.custom.size_guide %}
253
+ {% if guide %}
254
+ {{ guide.value }}
255
+ {% endif %}
256
+ ```
257
+
258
+ Metafield types in Liquid: `value` for single values, `.value.items` for list types.
259
+
260
+ ### Translation (i18n)
261
+
262
+ ```liquid
263
+ {{ 'products.product.add_to_cart' | t }}
264
+ {{ 'cart.items_count' | t: count: cart.item_count }}
265
+ ```
266
+
267
+ Keys defined in `locales/en.default.json`. Always use `| t` for user-facing strings.
268
+
269
+ ---
270
+
271
+ ## Liquid Anti-Patterns
272
+
273
+ **Using `include` instead of `render`:** `include` shares scope (can read parent variables), which creates hidden coupling. Use `render`.
274
+
275
+ **Doing math with price in Liquid:** `product.price | times: 1.21` for VAT display — Liquid math on integers (cents) then format. Don't format first, then calculate.
276
+
277
+ **Nested loops for lookups:** O(n²) in Liquid. Build a hash map instead:
278
+ ```liquid
279
+ {% assign variant_map = "" %}
280
+ {% for variant in product.variants %}
281
+ {% assign variant_map = variant_map | append: variant.id | append: ":" | append: variant.title | append: "," %}
282
+ {% endfor %}
283
+ ```
284
+ Or pass data to JS and handle there.
285
+
286
+ **Long conditional chains in Liquid:** Move logic to a snippet with `render`, or handle in JS.