# 🏷️ Metafields & Tags

div
h3
Shopify Metafields & Tags Reference
p
Appstle Loyalty uses Shopify metafields and tags to store loyalty program data, power storefront widgets, sync product reviews, and enable automation workflows. This reference covers every metafield and tag — what it contains, when it's set, and how to use it in your integration.
## 📦 Metafields Overview

Appstle Loyalty uses **three namespaces** across multiple resource types:

| Namespace | Resources | Visibility | Purpose |
|  --- | --- | --- | --- |
| `appstle_loyalty` | Shop, Customer | Public (readable by any app/theme) | Loyalty program configuration, customer loyalty profiles |
| `$app:appstle_loyalty` | Customer | **Private** (only readable by this app) | Customer reward discount codes for checkout |
| `appstle_review` | Product, Shop | Public | Product reviews, ratings, and carousel data |


> **Private namespace note:** The `$app:appstle_loyalty` namespace uses Shopify's app-owned metafield protection. Metafields in this namespace can only be read and written by the Appstle Loyalty app itself — not by themes, Liquid templates, or other apps.


## 🏪 Shop Metafields — Loyalty Program (`appstle_loyalty`)

These metafields store the complete loyalty program configuration and are used by the storefront widget to render the loyalty experience. They are written by `SubscribeItScriptUtils.writeShopMetafield()` whenever a merchant saves settings.

**When updated:** On every settings save in the Appstle admin, and when point rules or VIP tiers are modified. Updates are **synchronous** (immediate).

### Core Configuration

| Key | Type | Purpose |
|  --- | --- | --- |
| `app_url` | `string` | App base URL for widget communication |
| `proxy_path_prefix` | `string` | Custom proxy path prefix (default: `apps/loyalty`) |
| `public_domain` | `string` | Store's public domain name |
| `shop_name` | `string` | Store name from Shopify |
| `currency` | `string` | ISO currency code (e.g., `USD`, `EUR`) |
| `store_front_access_token` | `string` | Shopify Storefront API access token |


### Widget Configuration

| Key | Type | Purpose |
|  --- | --- | --- |
| `bundle_js_path` | `url` | CDN URL for widget JS bundle (with version params) |
| `bundle_css_path` | `url` | CDN URL for widget CSS bundle (with version params) |
| `widget_setting` | `json` | Full widget configuration object (colors, layout, positioning, etc.) |
| `shop_labels` | `json` | Widget label translations (i18n support) |


### Points Configuration

| Key | Type | Purpose |
|  --- | --- | --- |
| `pointRoundType` | `string` | Point rounding strategy: `NO_ROUND`, `ROUND_DOWN`, or `ROUND_UP` |
| `point_earn_rules` | `json` | Array of all active point earn rules |
| `point_redeem_rules` | `json` | Array of all active point redeem rules |


#### Example: `point_earn_rules`

```json
[
  {
    "id": 123,
    "type": "PURCHASE",
    "pointsPerDollar": 5,
    "minimumOrderAmount": 0,
    "enabled": true
  },
  {
    "id": 124,
    "type": "SIGNUP",
    "fixedPoints": 100,
    "enabled": true
  },
  {
    "id": 125,
    "type": "BIRTHDAY",
    "fixedPoints": 200,
    "enabled": true
  }
]
```

#### Example: `point_redeem_rules`

```json
[
  {
    "id": 456,
    "type": "FIXED_AMOUNT",
    "pointsCost": 500,
    "discountValue": 5.00,
    "discountType": "FIXED_AMOUNT",
    "enabled": true
  },
  {
    "id": 457,
    "type": "PERCENTAGE",
    "pointsCost": 1000,
    "discountValue": 10,
    "discountType": "PERCENTAGE",
    "maxDiscountAmount": 50.00,
    "enabled": true
  }
]
```

### Feature Flags

| Key | Type | Purpose |
|  --- | --- | --- |
| `enable_inactive_customer` | `boolean` | Allow tracking inactive customers in the program |
| `has_dedicated_page_access` | `boolean` | Access to dedicated loyalty page feature |
| `allow_customer_opt_in` | `boolean` | Allow customer self-opt-in to the loyalty program |
| `enable_discount_to_apply_automatically` | `boolean` | Auto-apply reward discounts at checkout |
| `show_store_credit_rewards` | `boolean` | Show store credit rewards in the widget |
| `birthdate_format` | `string` | Expected birthdate format (`MM/DD`, `DD/MM`, etc.) |


### Referral Configuration

| Key | Type | Purpose |
|  --- | --- | --- |
| `referral_enabled` | `boolean` | Referral program enabled flag |
| `referral_loyalty` | `json` | Referral config (discount type, tags, amounts) |


#### Example: `referral_loyalty`

```json
{
  "referrerDiscountType": "PERCENTAGE",
  "referrerDiscountValue": 10,
  "referredDiscountType": "FIXED_AMOUNT",
  "referredDiscountValue": 5.00,
  "referrerPoints": 500,
  "referredPoints": 200
}
```

### VIP Tier Configuration

| Key | Type | Purpose |
|  --- | --- | --- |
| `vip_tier_enabled` | `boolean` | VIP tier program enabled flag |
| `vip_tiers` | `json` | Array of VIP tier definitions |
| `vip_rewards` | `json` | VIP tier-specific point redeem rules |
| `vip_point_rewards` | `json` | VIP tier-specific point earn rules |
| `vip_tier_setting` | `json` | VIP tier configuration object |


#### Example: `vip_tiers`

```json
[
  {
    "id": 1,
    "name": "Silver",
    "threshold": 0,
    "icon": "🥈",
    "additionalTags": "silver-member"
  },
  {
    "id": 2,
    "name": "Gold",
    "threshold": 1000,
    "icon": "🥇",
    "additionalTags": "gold-member, premium-support"
  },
  {
    "id": 3,
    "name": "Platinum",
    "threshold": 5000,
    "icon": "💎",
    "additionalTags": "platinum-member, priority-shipping, premium-support"
  }
]
```

### Points Expiration

| Key | Type | Purpose |
|  --- | --- | --- |
| `points_expiration_setting` | `json` | Point expiration config (interval and type) |


```json
{
  "enabled": true,
  "interval": 12,
  "intervalType": "MONTH"
}
```

### Metafield Deletion

Shop metafields can be removed via `MiscellaneousResource.removeShopMetafield()` — this is used when resetting or disabling features.

## 👤 Customer Metafields — Loyalty Profile (`appstle_loyalty`)

#### `appstle_loyalty` / `customer_loyalty`

| Property | Value |
|  --- | --- |
| **Type** | `json` |
| **Resource** | Customer |
| **Visibility** | Public (readable by themes and other apps) |
| **Set by** | `SubscribeItScriptUtils.writeCustomerMetafield()` (loyalty-l / Lambda) |
| **Updated when** | After every loyalty event: point earn, point redeem, VIP tier change, referral, social engagement, etc. |
| **Update mechanism** | Processed by AWS Lambda (loyalty-l), updates are **near real-time** |


> **Important:** This metafield is set **only** by the Lambda processor (loyalty-l), not by the main loyalty app. It's updated after every loyalty event.


```json
{
  "availablePoints": 1250,
  "pendingPoints": 100,
  "redeemedPoints": 500,
  "creditedPoints": 1850,
  "vipTier": {
    "id": 2,
    "name": "Gold"
  },
  "achievableTierId": 3,
  "activeRewards": [
    {
      "discountCode": "REWARD-XYZ789",
      "discountType": "PERCENTAGE",
      "discountValue": 10
    }
  ],
  "socialMediaEngagement": {
    "facebook": true,
    "twitter": false,
    "instagram": true,
    "youtube": false,
    "tiktok": false,
    "pinterest": false,
    "newsletter": true,
    "sms": false,
    "accountCreation": true,
    "sharing": false
  },
  "referralInfo": {
    "referralLink": "https://store.myshopify.com/?ref=abc123",
    "completedReferrals": 3
  },
  "customerStatus": "ACTIVE",
  "dateOfBirth": "1990-05-15",
  "storeCreditBalance": 25.00,
  "lastActivityDate": "2025-03-20T14:30:00Z",
  "spentAmount": 450.75
}
```

## 🔒 Customer Metafields — Reward Codes (`$app:appstle_loyalty`)

#### `$app:appstle_loyalty` / `customer_rewards`

| Property | Value |
|  --- | --- |
| **Type** | `json` |
| **Resource** | Customer |
| **Visibility** | **Private** — only readable by the Appstle Loyalty app |
| **Set by** | `CommonUtils.syncCustomerRewardMetafield()` (loyalty) and `SubscribeItScriptUtils.syncCustomerRewardMetafield()` (loyalty-l) |
| **Updated when** | Customer redeems points for a reward, reward expires, or reward is used |
| **Purpose** | Stores active discount codes for Shopify checkout discount functions |


```json
{
  "rewards": [
    { "discountCode": "REWARD-ABC123" },
    { "discountCode": "REWARD-DEF456" }
  ]
}
```

> **Why private?** This metafield uses the `$app:` prefix to prevent external access. It contains discount codes that should only be validated by the Shopify discount function, not exposed to themes or third-party apps.


## ⭐ Product Metafields — Reviews (`appstle_review`)

Product review metafields are set on individual **Product** resources and on the **Shop** resource (for carousel displays).

**Set by:** `SubscribeItScriptUtils.createOrUpdateProductMetafield()` in both loyalty and loyalty-l repos.

**Updated when:** A review is approved, updated, or deleted. Updates are **synchronous** in the main app and **near real-time** from Lambda.

### Per-Product Metafields

| Key | Type | Purpose |
|  --- | --- | --- |
| `product_reviews` | `json` | Array of approved reviews for the product detail page (paginated, max 5 per page) |
| `total_reviews` | `integer` | Total review count for the product |
| `rating` | `rating` | Overall product rating (Shopify native rating type: value, scale_min=1, scale_max=5) |
| `product_rating_details` | `json` | Breakdown by star rating (map of rating → count) |


#### Example: `product_reviews`

```json
[
  {
    "id": 789,
    "author": "John D.",
    "rating": 5,
    "title": "Amazing product!",
    "body": "This is the best coffee I've ever had.",
    "createdAt": "2025-03-15T10:00:00Z",
    "verified": true
  }
]
```

#### Example: `rating` (Shopify native rating type)

```json
{
  "value": "4.5",
  "scale_min": "1",
  "scale_max": "5"
}
```

#### Example: `product_rating_details`

```json
{
  "5": 42,
  "4": 18,
  "3": 7,
  "2": 2,
  "1": 1
}
```

### Shop-Level Carousel Metafields (`appstle_review` on Shop)

| Key | Type | Purpose |
|  --- | --- | --- |
| `carousel_product_reviews` | `json` | Recent/featured reviews for the homepage carousel widget (max 15 reviews) |
| `carousel_total_reviews` | `integer` | Total count of reviews in the carousel |
| `carousel_rating` | `rating` | Overall rating for the carousel display (Shopify native rating type) |


## 🏷️ Tags Overview

Appstle Loyalty applies tags to **Customer** resources only. No tags are applied to orders or products.

All tags are applied using the Shopify GraphQL Admin API (`TagsAddMutation` / `TagsRemoveMutation`).

## 👤 Customer Tags

### VIP Tier Name Tags

| Property | Value |
|  --- | --- |
| **Resource** | Customer |
| **Format** | Dynamic — the VIP tier's name (e.g., `Silver`, `Gold`, `Platinum`) |
| **Applied when** | Customer achieves or is promoted to a VIP tier (automatic calculation or manual assignment) |
| **Removed when** | Customer's tier changes (upgrade, downgrade, or removal) |
| **Mutual exclusivity** | Only the current tier's name tag is active — previous tier name tags are removed |


**When tier tags are applied:**

- Automatic VIP tier calculation (based on spend, points, or order count thresholds)
- Manual tier assignment by merchant
- Referral flow VIP assignment
- Shopify Flow trigger VIP assignment
- Bulk operations VIP assignment


### Additional VIP Tier Tags (Merchant-Configured)

| Property | Value |
|  --- | --- |
| **Resource** | Customer |
| **Format** | Comma-separated tags configured per tier in the `additionalTags` field |
| **Applied when** | Along with the tier name tag when a customer achieves a VIP tier |
| **Removed when** | Customer leaves the tier (upgrade, downgrade, or removal) |
| **Configuration** | Set per VIP tier in the Appstle admin under **VIP Tiers** |


#### Example

A "Gold" tier configured with `additionalTags = "premium-member, vip-support"` will apply three tags when a customer reaches Gold:

1. `Gold` (the tier name)
2. `premium-member` (additional tag)
3. `vip-support` (additional tag)


When the customer upgrades to Platinum, all three Gold tags are removed and replaced with Platinum's tags.

### Referral Tags

| Property | Value |
|  --- | --- |
| **Resource** | Customer |
| **Format** | Two tags per referral relationship |
| **Applied when** | A referral offer is accepted by both parties |
| **Removed** | Never (referral tags are permanent) |


| Tag Pattern | Applied To | Purpose |
|  --- | --- | --- |
| `referral:{referralCustomerId}` | The **referrer** | Identifies the referred customer |
| `referred:{originalCustomerId}` | The **referred customer** | Identifies who referred them |


#### Example

Customer A (ID: 1111) refers Customer B (ID: 2222):

- Customer A gets tag: `referral:2222`
- Customer B gets tag: `referred:1111`


### Product Review Tag

| Property | Value |
|  --- | --- |
| **Resource** | Customer |
| **Format** | `Wrote Appstle Web Review` (static string) |
| **Applied when** | A new Shopify customer is created via product review submission (only if the customer didn't previously exist in Shopify) |
| **Removed** | Never (permanent tag) |


## 📊 Summary Tables

### All Metafields at a Glance

| # | Resource | Namespace | Key | Type | Visibility |
|  --- | --- | --- | --- | --- | --- |
| 1 | Shop | `appstle_loyalty` | `app_url` | string | Public |
| 2 | Shop | `appstle_loyalty` | `proxy_path_prefix` | string | Public |
| 3 | Shop | `appstle_loyalty` | `public_domain` | string | Public |
| 4 | Shop | `appstle_loyalty` | `shop_name` | string | Public |
| 5 | Shop | `appstle_loyalty` | `currency` | string | Public |
| 6 | Shop | `appstle_loyalty` | `pointRoundType` | string | Public |
| 7 | Shop | `appstle_loyalty` | `point_earn_rules` | json | Public |
| 8 | Shop | `appstle_loyalty` | `point_redeem_rules` | json | Public |
| 9 | Shop | `appstle_loyalty` | `bundle_js_path` | url | Public |
| 10 | Shop | `appstle_loyalty` | `bundle_css_path` | url | Public |
| 11 | Shop | `appstle_loyalty` | `widget_setting` | json | Public |
| 12 | Shop | `appstle_loyalty` | `shop_labels` | json | Public |
| 13 | Shop | `appstle_loyalty` | `enable_inactive_customer` | boolean | Public |
| 14 | Shop | `appstle_loyalty` | `has_dedicated_page_access` | boolean | Public |
| 15 | Shop | `appstle_loyalty` | `allow_customer_opt_in` | boolean | Public |
| 16 | Shop | `appstle_loyalty` | `enable_discount_to_apply_automatically` | boolean | Public |
| 17 | Shop | `appstle_loyalty` | `store_front_access_token` | string | Public |
| 18 | Shop | `appstle_loyalty` | `birthdate_format` | string | Public |
| 19 | Shop | `appstle_loyalty` | `show_store_credit_rewards` | boolean | Public |
| 20 | Shop | `appstle_loyalty` | `referral_enabled` | boolean | Public |
| 21 | Shop | `appstle_loyalty` | `referral_loyalty` | json | Public |
| 22 | Shop | `appstle_loyalty` | `vip_tier_enabled` | boolean | Public |
| 23 | Shop | `appstle_loyalty` | `vip_tiers` | json | Public |
| 24 | Shop | `appstle_loyalty` | `vip_rewards` | json | Public |
| 25 | Shop | `appstle_loyalty` | `vip_point_rewards` | json | Public |
| 26 | Shop | `appstle_loyalty` | `vip_tier_setting` | json | Public |
| 27 | Shop | `appstle_loyalty` | `points_expiration_setting` | json | Public |
| 28 | Customer | `appstle_loyalty` | `customer_loyalty` | json | Public |
| 29 | Customer | `$app:appstle_loyalty` | `customer_rewards` | json | **Private** |
| 30 | Product | `appstle_review` | `product_reviews` | json | Public |
| 31 | Product | `appstle_review` | `total_reviews` | integer | Public |
| 32 | Product | `appstle_review` | `rating` | rating | Public |
| 33 | Product | `appstle_review` | `product_rating_details` | json | Public |
| 34 | Shop | `appstle_review` | `carousel_product_reviews` | json | Public |
| 35 | Shop | `appstle_review` | `carousel_total_reviews` | integer | Public |
| 36 | Shop | `appstle_review` | `carousel_rating` | rating | Public |


### All Tags at a Glance

| # | Resource | Tag Pattern | When Applied | Permanent? |
|  --- | --- | --- | --- | --- |
| 1 | Customer | `{VIP tier name}` (dynamic) | VIP tier assignment | No (swapped on tier change) |
| 2 | Customer | `{additionalTags}` (merchant-configured per tier) | VIP tier assignment | No (swapped on tier change) |
| 3 | Customer | `referral:{customerId}` / `referred:{customerId}` | Referral acceptance | Yes |
| 4 | Customer | `Wrote Appstle Web Review` | New customer created via review | Yes |


### GraphQL Mutations Used

| Mutation | Purpose |
|  --- | --- |
| `MetafieldsSetMutation` | Create or update metafields on any resource |
| `MetafieldsDeleteMutation` | Delete metafields |
| `TagsAddMutation` | Add tags to customers |
| `TagsRemoveMutation` | Remove tags from customers |


## ❓ FAQ

details
summary
strong
Can I read loyalty metafields from my Liquid theme?
Yes — metafields in the `appstle_loyalty` and `appstle_review` namespaces are public. Access them in Liquid via `{{ customer.metafields.appstle_loyalty.customer_loyalty }}` or `{{ product.metafields.appstle_review.rating }}`. However, `customer_rewards` in the `$app:appstle_loyalty` namespace is **private** and not accessible in Liquid.

details
summary
strong
How quickly are customer loyalty metafields updated?
Customer loyalty metafields are updated by the Lambda processor (loyalty-l) after every loyalty event. Updates are **near real-time** — typically within a few seconds.

details
summary
strong
What happens to VIP tier tags when a customer is upgraded?
When a customer's VIP tier changes, the old tier's name tag and all its `additionalTags` are **removed**, and the new tier's name tag and `additionalTags` are **added**. Only one tier's tags are active at a time.

details
summary
strong
Can other apps read the customer_rewards discount codes?
No. The `customer_rewards` metafield uses the `$app:appstle_loyalty` namespace, which is a private app-owned namespace. Only the Appstle Loyalty app can read and write it. This protects discount codes from being exposed to unauthorized apps.

details
summary
strong
How do product review ratings work with Shopify's native rating type?
The `rating` metafield uses Shopify's native `rating` type (not JSON), which integrates directly with Shopify's built-in rating display, Google rich results, and other SEO features. The value is on a scale of 1 to 5.