@proveanything/smartlinks 1.2.4 → 1.3.2

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,484 @@
1
+ # Liquid Templates in SmartLinks
2
+
3
+ Liquid is a templating language that allows you to dynamically insert data into text content. SmartLinks uses Liquid Templates in various APIs—such as email templates, notification messages, and dynamic content—to personalize communications with real-time data from your collections, products, proofs, and users.
4
+
5
+ ---
6
+
7
+ ## What are Liquid Templates?
8
+
9
+ Liquid is an open-source template language created by Shopify. It uses a simple syntax with two main components:
10
+
11
+ - **Output tags** `{{ }}` — Insert dynamic values
12
+ - **Logic tags** `{% %}` — Control flow (if/else, loops, etc.)
13
+
14
+ ### Basic Example
15
+
16
+ ```liquid
17
+ Hello {{ contact.name }},
18
+
19
+ Thank you for registering your {{ product.name }}!
20
+ Your proof ID is: {{ proof.id }}
21
+
22
+ {% if proof.claimed %}
23
+ This item was claimed on {{ proof.claimedAt | date: "%B %d, %Y" }}.
24
+ {% endif %}
25
+ ```
26
+
27
+ ---
28
+
29
+ ## Core Data Objects
30
+
31
+ SmartLinks provides several core objects that can be accessed in Liquid Templates. The available objects depend on the context (e.g., a proof-level template has access to `proof`, `product`, and `collection`).
32
+
33
+ ---
34
+
35
+ ### Collection
36
+
37
+ A **Collection** represents a top-level business, brand, or organization. All products belong to a collection.
38
+
39
+ | Field | Type | Description |
40
+ |-------|------|-------------|
41
+ | `collection.id` | string | Unique identifier |
42
+ | `collection.name` | string | Display name of the collection |
43
+ | `collection.description` | string | Description text |
44
+ | `collection.slug` | string | URL-friendly identifier |
45
+ | `collection.logoUrl` | string | URL to the collection's logo image |
46
+ | `collection.websiteUrl` | string | Primary website URL |
47
+ | `collection.metadata` | object | Custom key-value metadata |
48
+ | `collection.createdAt` | datetime | When the collection was created |
49
+ | `collection.updatedAt` | datetime | When the collection was last updated |
50
+
51
+ #### Example Usage
52
+
53
+ ```liquid
54
+ Welcome to {{ collection.name }}!
55
+
56
+ {% if collection.websiteUrl %}
57
+ Visit us at {{ collection.websiteUrl }}
58
+ {% endif %}
59
+
60
+ <img src="{{ collection.logoUrl }}" alt="{{ collection.name }} logo" />
61
+ ```
62
+
63
+ ---
64
+
65
+ ### Product
66
+
67
+ A **Product** represents a type or definition of a physical or digital item. Products belong to a collection and can have many proofs (instances).
68
+
69
+ | Field | Type | Description |
70
+ |-------|------|-------------|
71
+ | `product.id` | string | Unique identifier |
72
+ | `product.name` | string | Product name |
73
+ | `product.description` | string | Product description |
74
+ | `product.sku` | string | Stock keeping unit |
75
+ | `product.slug` | string | URL-friendly identifier |
76
+ | `product.imageUrl` | string | Primary product image URL |
77
+ | `product.images` | array | Array of image URLs |
78
+ | `product.category` | string | Product category |
79
+ | `product.tags` | array | Array of tag strings |
80
+ | `product.metadata` | object | Custom key-value metadata |
81
+ | `product.createdAt` | datetime | When the product was created |
82
+ | `product.updatedAt` | datetime | When the product was last updated |
83
+
84
+ #### Example Usage
85
+
86
+ ```liquid
87
+ Your {{ product.name }} (SKU: {{ product.sku }})
88
+
89
+ {{ product.description }}
90
+
91
+ {% if product.tags.size > 0 %}
92
+ Tags: {{ product.tags | join: ", " }}
93
+ {% endif %}
94
+
95
+ {% for image in product.images %}
96
+ <img src="{{ image }}" alt="{{ product.name }}" />
97
+ {% endfor %}
98
+ ```
99
+
100
+ ---
101
+
102
+ ### Proof
103
+
104
+ A **Proof** is a specific instance of a product—think of it as a unique digital certificate for a physical item. Proofs can be claimed by users and carry ownership information.
105
+
106
+ | Field | Type | Description |
107
+ |-------|------|-------------|
108
+ | `proof.id` | string | Unique identifier |
109
+ | `proof.serialNumber` | string | Human-readable serial number |
110
+ | `proof.claimed` | boolean | Whether the proof has been claimed |
111
+ | `proof.claimedAt` | datetime | When the proof was claimed |
112
+ | `proof.claimedBy` | string | User ID of the claimer |
113
+ | `proof.status` | string | Current status (e.g., "active", "transferred") |
114
+ | `proof.nfcTagId` | string | Associated NFC tag ID (if applicable) |
115
+ | `proof.qrCode` | string | QR code identifier |
116
+ | `proof.shortCode` | string | Short code for easy lookup |
117
+ | `proof.metadata` | object | Custom key-value metadata |
118
+ | `proof.createdAt` | datetime | When the proof was created |
119
+ | `proof.updatedAt` | datetime | When the proof was last updated |
120
+
121
+ #### Example Usage
122
+
123
+ ```liquid
124
+ Proof of Authenticity
125
+
126
+ Serial Number: {{ proof.serialNumber }}
127
+ Status: {{ proof.status }}
128
+
129
+ {% if proof.claimed %}
130
+ Claimed on: {{ proof.claimedAt | date: "%B %d, %Y at %H:%M" }}
131
+ {% else %}
132
+ This item has not been claimed yet.
133
+ {% endif %}
134
+
135
+ {% if proof.metadata.warrantyExpiry %}
136
+ Warranty expires: {{ proof.metadata.warrantyExpiry | date: "%B %d, %Y" }}
137
+ {% endif %}
138
+ ```
139
+
140
+ ---
141
+
142
+ ### Contact
143
+
144
+ A **Contact** represents a customer or user in the system. Contacts are associated with a collection and can own multiple proofs.
145
+
146
+ | Field | Type | Description |
147
+ |-------|------|-------------|
148
+ | `contact.id` | string | Unique identifier |
149
+ | `contact.email` | string | Email address |
150
+ | `contact.name` | string | Full name |
151
+ | `contact.firstName` | string | First name |
152
+ | `contact.lastName` | string | Last name |
153
+ | `contact.phone` | string | Phone number |
154
+ | `contact.locale` | string | Preferred language/locale (e.g., "en", "de") |
155
+ | `contact.timezone` | string | Preferred timezone |
156
+ | `contact.avatarUrl` | string | Profile picture URL |
157
+ | `contact.metadata` | object | Custom key-value metadata |
158
+ | `contact.tags` | array | Array of tag strings for segmentation |
159
+ | `contact.createdAt` | datetime | When the contact was created |
160
+ | `contact.updatedAt` | datetime | When the contact was last updated |
161
+ | `contact.lastSeenAt` | datetime | Last activity timestamp |
162
+
163
+ #### Example Usage
164
+
165
+ ```liquid
166
+ Hi {{ contact.firstName | default: contact.name | default: "there" }},
167
+
168
+ {% if contact.locale == "de" %}
169
+ Willkommen!
170
+ {% elsif contact.locale == "fr" %}
171
+ Bienvenue!
172
+ {% else %}
173
+ Welcome!
174
+ {% endif %}
175
+
176
+ {% if contact.phone %}
177
+ We'll send updates to {{ contact.phone }}.
178
+ {% endif %}
179
+ ```
180
+
181
+ ---
182
+
183
+ ### User (Account)
184
+
185
+ A **User** represents an authenticated account in the system. This is typically the logged-in user performing an action.
186
+
187
+ | Field | Type | Description |
188
+ |-------|------|-------------|
189
+ | `user.id` | string | Unique identifier |
190
+ | `user.email` | string | Email address |
191
+ | `user.name` | string | Display name |
192
+ | `user.admin` | boolean | Whether user has admin privileges |
193
+ | `user.avatarUrl` | string | Profile picture URL |
194
+ | `user.createdAt` | datetime | Account creation date |
195
+
196
+ #### Example Usage
197
+
198
+ ```liquid
199
+ Logged in as: {{ user.name }} ({{ user.email }})
200
+
201
+ {% if user.admin %}
202
+ 🔐 You have administrator access.
203
+ {% endif %}
204
+ ```
205
+
206
+ ---
207
+
208
+ ### Attestation
209
+
210
+ An **Attestation** is flexible data attached to a specific proof. It's used to store additional information like warranty registrations, tasting notes, service records, etc.
211
+
212
+ | Field | Type | Description |
213
+ |-------|------|-------------|
214
+ | `attestation.id` | string | Unique identifier |
215
+ | `attestation.type` | string | Attestation type (app-defined) |
216
+ | `attestation.data` | object | The attestation payload (varies by type) |
217
+ | `attestation.createdBy` | string | User ID who created it |
218
+ | `attestation.createdAt` | datetime | When the attestation was created |
219
+ | `attestation.updatedAt` | datetime | When the attestation was last updated |
220
+
221
+ #### Example Usage
222
+
223
+ ```liquid
224
+ {% if attestation.type == "warranty_registration" %}
225
+ Warranty Registration Details:
226
+ - Registered: {{ attestation.createdAt | date: "%B %d, %Y" }}
227
+ - Purchase Date: {{ attestation.data.purchaseDate }}
228
+ - Store: {{ attestation.data.storeName }}
229
+ {% endif %}
230
+
231
+ {% if attestation.type == "tasting_note" %}
232
+ 🍷 Tasting Note by {{ attestation.data.author }}:
233
+ "{{ attestation.data.notes }}"
234
+ Rating: {{ attestation.data.rating }}/5
235
+ {% endif %}
236
+ ```
237
+
238
+ ---
239
+
240
+ ## Liquid Filters
241
+
242
+ Liquid provides built-in filters to transform data. Common filters include:
243
+
244
+ ### Text Filters
245
+
246
+ | Filter | Description | Example |
247
+ |--------|-------------|---------|
248
+ | `upcase` | Convert to uppercase | `{{ product.name \| upcase }}` |
249
+ | `downcase` | Convert to lowercase | `{{ product.name \| downcase }}` |
250
+ | `capitalize` | Capitalize first letter | `{{ contact.name \| capitalize }}` |
251
+ | `truncate` | Limit string length | `{{ product.description \| truncate: 100 }}` |
252
+ | `strip_html` | Remove HTML tags | `{{ content \| strip_html }}` |
253
+ | `escape` | HTML escape special chars | `{{ user_input \| escape }}` |
254
+ | `default` | Fallback value if empty | `{{ contact.name \| default: "Customer" }}` |
255
+
256
+ ### Date Filters
257
+
258
+ | Filter | Description | Example |
259
+ |--------|-------------|---------|
260
+ | `date` | Format a date | `{{ proof.claimedAt \| date: "%B %d, %Y" }}` |
261
+
262
+ Common date formats:
263
+ - `%B %d, %Y` → January 15, 2025
264
+ - `%Y-%m-%d` → 2025-01-15
265
+ - `%d/%m/%Y` → 15/01/2025
266
+ - `%H:%M` → 14:30
267
+
268
+ ### Array Filters
269
+
270
+ | Filter | Description | Example |
271
+ |--------|-------------|---------|
272
+ | `join` | Join array elements | `{{ product.tags \| join: ", " }}` |
273
+ | `first` | Get first element | `{{ product.images \| first }}` |
274
+ | `last` | Get last element | `{{ product.images \| last }}` |
275
+ | `size` | Get array length | `{{ product.tags.size }}` |
276
+ | `sort` | Sort array | `{{ items \| sort: "name" }}` |
277
+
278
+ ### Number Filters
279
+
280
+ | Filter | Description | Example |
281
+ |--------|-------------|---------|
282
+ | `plus` | Add | `{{ count \| plus: 1 }}` |
283
+ | `minus` | Subtract | `{{ total \| minus: discount }}` |
284
+ | `times` | Multiply | `{{ price \| times: quantity }}` |
285
+ | `divided_by` | Divide | `{{ total \| divided_by: 2 }}` |
286
+ | `round` | Round number | `{{ average \| round: 2 }}` |
287
+
288
+ ---
289
+
290
+ ## Control Flow
291
+
292
+ ### Conditionals
293
+
294
+ ```liquid
295
+ {% if proof.claimed %}
296
+ This item is claimed.
297
+ {% elsif proof.status == "pending" %}
298
+ Claim pending verification.
299
+ {% else %}
300
+ Available to claim.
301
+ {% endif %}
302
+
303
+ {% unless contact.email %}
304
+ No email on file.
305
+ {% endunless %}
306
+ ```
307
+
308
+ ### Operators
309
+
310
+ | Operator | Description |
311
+ |----------|-------------|
312
+ | `==` | Equals |
313
+ | `!=` | Not equals |
314
+ | `>` | Greater than |
315
+ | `<` | Less than |
316
+ | `>=` | Greater than or equal |
317
+ | `<=` | Less than or equal |
318
+ | `or` | Logical OR |
319
+ | `and` | Logical AND |
320
+ | `contains` | String/array contains |
321
+
322
+ ```liquid
323
+ {% if product.tags contains "premium" %}
324
+ 🌟 Premium Product
325
+ {% endif %}
326
+
327
+ {% if contact.email and proof.claimed %}
328
+ Send confirmation to {{ contact.email }}
329
+ {% endif %}
330
+ ```
331
+
332
+ ### Loops
333
+
334
+ ```liquid
335
+ {% for tag in product.tags %}
336
+ <span class="tag">{{ tag }}</span>
337
+ {% endfor %}
338
+
339
+ {% for image in product.images limit: 3 %}
340
+ <img src="{{ image }}" alt="{{ product.name }} image {{ forloop.index }}" />
341
+ {% endfor %}
342
+ ```
343
+
344
+ Loop variables:
345
+ - `forloop.index` — Current iteration (1-indexed)
346
+ - `forloop.index0` — Current iteration (0-indexed)
347
+ - `forloop.first` — Is this the first iteration?
348
+ - `forloop.last` — Is this the last iteration?
349
+ - `forloop.length` — Total number of iterations
350
+
351
+ ---
352
+
353
+ ## Common Use Cases
354
+
355
+ ### Email Templates
356
+
357
+ ```liquid
358
+ Subject: Your {{ product.name }} has been registered!
359
+
360
+ Hi {{ contact.firstName | default: "there" }},
361
+
362
+ Great news! Your {{ product.name }} (Serial: {{ proof.serialNumber }})
363
+ has been successfully registered to your account.
364
+
365
+ {% if product.metadata.warrantyYears %}
366
+ Your warranty is valid for {{ product.metadata.warrantyYears }} years
367
+ from the date of purchase.
368
+ {% endif %}
369
+
370
+ If you have any questions, please contact {{ collection.name }} support.
371
+
372
+ Best regards,
373
+ The {{ collection.name }} Team
374
+ ```
375
+
376
+ ### Notification Messages
377
+
378
+ ```liquid
379
+ 🎉 {{ contact.firstName }}, your {{ product.name }} is now verified!
380
+ Proof ID: {{ proof.shortCode }}
381
+ ```
382
+
383
+ ### Dynamic Content Blocks
384
+
385
+ ```liquid
386
+ {% if proof.metadata.tier == "gold" %}
387
+ <div class="gold-benefits">
388
+ As a Gold member, you get exclusive access to...
389
+ </div>
390
+ {% elsif proof.metadata.tier == "silver" %}
391
+ <div class="silver-benefits">
392
+ Your Silver membership includes...
393
+ </div>
394
+ {% endif %}
395
+ ```
396
+
397
+ ### Multilingual Content
398
+
399
+ ```liquid
400
+ {% case contact.locale %}
401
+ {% when "de" %}
402
+ Vielen Dank für Ihre Registrierung!
403
+ {% when "fr" %}
404
+ Merci pour votre inscription!
405
+ {% when "es" %}
406
+ ¡Gracias por registrarte!
407
+ {% else %}
408
+ Thank you for registering!
409
+ {% endcase %}
410
+ ```
411
+
412
+ ---
413
+
414
+ ## Accessing Nested Data
415
+
416
+ Use dot notation to access nested fields in metadata or data objects:
417
+
418
+ ```liquid
419
+ {{ product.metadata.manufacturer }}
420
+ {{ attestation.data.warranty.expiryDate }}
421
+ {{ collection.metadata.social.twitter }}
422
+ ```
423
+
424
+ For dynamic keys, you may need to use bracket notation (if supported):
425
+
426
+ ```liquid
427
+ {{ product.metadata["custom-field"] }}
428
+ ```
429
+
430
+ ---
431
+
432
+ ## Best Practices
433
+
434
+ 1. **Always use `default` filter** for optional fields to avoid blank output:
435
+ ```liquid
436
+ {{ contact.name | default: "Valued Customer" }}
437
+ ```
438
+
439
+ 2. **Escape user-generated content** when outputting as HTML:
440
+ ```liquid
441
+ {{ attestation.data.userNotes | escape }}
442
+ ```
443
+
444
+ 3. **Check for existence** before accessing nested data:
445
+ ```liquid
446
+ {% if proof.metadata.warranty %}
447
+ Warranty: {{ proof.metadata.warranty.type }}
448
+ {% endif %}
449
+ ```
450
+
451
+ 4. **Use meaningful fallbacks** for a better user experience:
452
+ ```liquid
453
+ Hi {{ contact.firstName | default: contact.name | default: "there" }},
454
+ ```
455
+
456
+ 5. **Format dates appropriately** for the user's locale:
457
+ ```liquid
458
+ {{ proof.claimedAt | date: "%d %B %Y" }}
459
+ ```
460
+
461
+ ---
462
+
463
+ ## API Context
464
+
465
+ Different APIs provide different objects in the Liquid context:
466
+
467
+ | API / Feature | Available Objects |
468
+ |---------------|-------------------|
469
+ | Email Templates | `collection`, `product`, `proof`, `contact`, `attestation` |
470
+ | Push Notifications | `collection`, `product`, `proof`, `contact` |
471
+ | SMS Messages | `collection`, `product`, `proof`, `contact` |
472
+ | Wallet Passes | `collection`, `product`, `proof`, `contact` |
473
+ | Journey Actions | `collection`, `product`, `proof`, `contact`, `event` |
474
+ | Broadcast Campaigns | `collection`, `contact`, `segment` |
475
+
476
+ Check the specific API documentation for the exact objects available in each context.
477
+
478
+ ---
479
+
480
+ ## Further Resources
481
+
482
+ - [Liquid Template Language Documentation](https://shopify.github.io/liquid/)
483
+ - [Liquid for Designers](https://github.com/Shopify/liquid/wiki/Liquid-for-Designers)
484
+ - SmartLinks Template API Reference