# 🏷️ Metafields & Tags

div
h3
Shopify Metafields & Tags Reference
p
Appstle Subscriptions uses Shopify metafields and tags to store subscription data, power storefront widgets, 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

All metafields use the namespace **`appstle_subscription`** and are managed through the Shopify GraphQL Admin API (`MetafieldsSetMutation`).

Metafields are set on four Shopify resource types:

| Resource | Count | Visibility | Description |
|  --- | --- | --- | --- |
| **Shop** | 15+ keys | Public (readable by any app/theme) | App configuration, widget settings, selling plans, build-a-box data |
| **Selling Plan** | 1 key | Public | Individual selling plan metadata |
| **Order** | 1 key | Public | Subscription context for each order |
| **Customer** | 1 key | Public | All subscription contracts for the customer |


> **Namespace visibility:** All subscription metafields use the `appstle_subscription` namespace (no `$app:` prefix), which means they are **readable by other apps, themes, and Liquid templates**.


## 🏪 Shop Metafields

Shop metafields store the app's configuration and are used by the storefront widget, checkout extensions, and build-a-box features. They are written by `SubscribeItScriptUtils.updateShopMetafieldsForSettings()` whenever a merchant saves settings.

**When updated:** On every settings save in the Appstle admin, and on selling plan or bundle configuration changes. Updates are **synchronous** (immediate).

### Core Configuration

#### `appstle_subscription` / `setting`

| Property | Value |
|  --- | --- |
| **Type** | `json` |
| **Resource** | Shop |
| **Purpose** | Core widget and app configuration — UI settings, asset paths, selling plans, labels, and validation rules |
| **Updated when** | Merchant saves settings in the Appstle admin |


```json
{
  "widgetEnabled": true,
  "sellingPlans": [...],
  "assetPaths": {
    "js": "https://cdn.appstle.com/...",
    "css": "https://cdn.appstle.com/..."
  },
  "labels": {...},
  "validationRules": {...}
}
```

#### `appstle_subscription` / `labels`

| Property | Value |
|  --- | --- |
| **Type** | `multi_line_text_field` |
| **Resource** | Shop |
| **Purpose** | Localized UI labels and translations for the subscription widget |
| **Updated when** | Merchant saves label translations |
| **Also set by** | `LabelTranslationsServiceImpl` |


#### `appstle_subscription` / `shop_info`

| Property | Value |
|  --- | --- |
| **Type** | `json` |
| **Resource** | Shop |
| **Purpose** | Shop metadata including money format, feature flags, and API tokens |


### Selling Plan Metafields

#### `appstle_subscription` / `selling_plans`

| Property | Value |
|  --- | --- |
| **Type** | `json` |
| **Resource** | Shop |
| **Purpose** | Free-product selling plans only |
| **Also set by** | `SubscriptionGroupServiceImpl` |


#### `appstle_subscription` / `all_Selling_Plans`

| Property | Value |
|  --- | --- |
| **Type** | `json` |
| **Resource** | Shop |
| **Purpose** | ALL selling plans (including paid plans) |
| **Also set by** | `SubscriptionGroupServiceImpl` |


> **Note:** The key uses camelCase (`all_Selling_Plans`) — this is intentional and must not be changed.


### Checkout Validation

#### `appstle_subscription` / `checkout_validation`

| Property | Value |
|  --- | --- |
| **Type** | `json` |
| **Resource** | Shop |
| **Purpose** | Checkout validation rules including duplicate subscription restrictions and per-customer limits |


```json
{
  "preventDuplicateSubscriptions": true,
  "maxSubscriptionsPerCustomer": 5,
  "rules": [...]
}
```

### Widget Templates

#### `appstle_subscription` / `widget_template_html`

| Property | Value |
|  --- | --- |
| **Type** | `multi_line_text_field` |
| **Resource** | Shop |
| **Purpose** | Custom widget template HTML (only set if merchant has configured a custom template) |


#### `appstle_subscription` / `all_widget_template_html`

| Property | Value |
|  --- | --- |
| **Type** | `json` |
| **Resource** | Shop |
| **Purpose** | Map of all available widget templates |


### Build-a-Box Metafields

Build-a-box bundles use multiple metafield keys for configuration, styling, and validation.

| Key | Type | Purpose |
|  --- | --- | --- |
| `bundle` | `json` | Bundle/build-a-box configuration |
| `bab_subscription_css` | `json` | Build-a-box subscription CSS styles |
| `bab_customization_css` | `json` | Build-a-box customization CSS |
| `bab_validation_info` | `json` | Build-a-box validation rules |
| `bab_setting_info` | `json` | Build-a-box settings |
| `bab_info_0`, `bab_info_1`, ... | `json` | Individual build-a-box bundle details (one per enabled bundle, dynamically indexed) |
| `total_bab` | `integer` | Total count of enabled build-a-box bundles |


> Build-a-box metafields are set by `SubscriptionBundlingServiceImpl.handleMetafieldInputBuildABoxInfo()`. The `bab_info_*` keys are zero-indexed — if a merchant has 3 bundles, the keys will be `bab_info_0`, `bab_info_1`, and `bab_info_2`.


## 📋 Selling Plan Metafields

#### `appstle_subscription` / `selling_plan`

| Property | Value |
|  --- | --- |
| **Type** | `json` |
| **Resource** | Selling Plan |
| **Purpose** | Individual selling plan metadata (frequency, billing policy, discounts) |
| **Set by** | `SubscriptionGroupServiceImpl.createOrUpdateSellingPlans()` |
| **Updated when** | Selling plan is created or modified |


```json
{
  "frequencyCount": 1,
  "frequencyInterval": "MONTH",
  "billingPolicy": {
    "interval": "MONTH",
    "intervalCount": 1
  },
  "discountType": "PERCENTAGE",
  "discountValue": 10.0
}
```

## 📦 Order Metafields

#### `appstle_subscription` / `details`

| Property | Value |
|  --- | --- |
| **Type** | `json` |
| **Resource** | Order |
| **Purpose** | Full subscription contract context for this order |
| **Set by** | `AbstractSubscriptionService.updateOrderMetafields()` (subscription-async) |
| **Updated when** | Order is created via subscription (initial or recurring billing) |
| **Update mechanism** | Queued via SQS, processed asynchronously |


This metafield contains a complete snapshot of the subscription context at the time the order was created:

```json
{
  "customer": {
    "id": "gid://shopify/Customer/1234567890"
  },
  "subscriptionContract": {
    "id": "gid://shopify/SubscriptionContract/9876543210",
    "status": "ACTIVE",
    "sellingPlanIds": ["gid://shopify/SellingPlan/111"],
    "sellingPlanNames": ["Monthly Subscription - 10% off"],
    "variantIds": ["gid://shopify/ProductVariant/222"],
    "variantNames": ["Default Title"],
    "currentCycle": 3,
    "groupPlanNames": ["Subscribe & Save"],
    "cancellationReason": null
  },
  "lineItems": [
    {
      "variantId": "gid://shopify/ProductVariant/222",
      "title": "Premium Coffee Beans",
      "productId": "gid://shopify/Product/333",
      "sellingPlanId": "gid://shopify/SellingPlan/111",
      "sellingPlanName": "Monthly Subscription - 10% off",
      "sku": "COFFEE-PREMIUM-1KG"
    }
  ],
  "firstOrder": {
    "id": "gid://shopify/Order/444",
    "createdAt": "2025-01-15T10:30:00Z"
  }
}
```

## 👤 Customer Metafields

#### `appstle_subscription` / `subscriptions`

| Property | Value |
|  --- | --- |
| **Type** | `json` |
| **Resource** | Customer |
| **Purpose** | All subscription contracts for this customer with full details |
| **Set by** | `AbstractSubscriptionService.setCustomerMetaFields()` (subscription-async) |
| **Updated when** | Any subscription contract changes (created, updated, paused, cancelled, billing attempt) |
| **Update mechanism** | Queued via SQS (`subscription-update-customer-metafields.fifo`), processed asynchronously by subscription-async |


```json
[
  {
    "id": "gid://shopify/SubscriptionContract/9876543210",
    "status": "ACTIVE",
    "sellingPlanNames": ["Monthly Subscription - 10% off"],
    "nextBillingDate": "2025-04-15T10:30:00Z",
    "lineItems": [
      {
        "title": "Premium Coffee Beans",
        "variantId": "gid://shopify/ProductVariant/222",
        "sku": "COFFEE-PREMIUM-1KG"
      }
    ]
  }
]
```

> **Important:** Customer metafield updates are **asynchronous**. The main app queues the update via SQS, and subscription-async processes it. There may be a delay of a few seconds between a contract change and the metafield reflecting that change.


## 🏷️ Tags Overview

Appstle Subscriptions applies tags to **Orders** and **Customers**. All tags are **merchant-configurable** through the Appstle admin under **Settings**.

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

## 📦 Order Tags

Order tags are static strings (no Liquid template support) that identify whether an order is the initial subscription order or a recurring billing order.

### First-Time Order Tag

| Property | Value |
|  --- | --- |
| **Config field** | `firstTimeOrderTag` |
| **Default value** | `appstle_subscription_first_order` |
| **Applied when** | Initial subscription order is created |
| **Applied by** | `SubscriptionCreateService` and `AbstractSubscriptionService.updateShopifyOrderDetails()` (subscription-async) |
| **Removed** | Never (order tags are permanent) |


### Recurring Order Tag

| Property | Value |
|  --- | --- |
| **Config field** | `recurringOrderTag` |
| **Default value** | `appstle_subscription_recurring_order` |
| **Applied when** | Each subsequent billing attempt creates an order |
| **Applied by** | `SubscriptionBillingAttemptService` and `AbstractSubscriptionService.updateShopifyOrderDetails()` (subscription-async) |
| **Removed** | Never (order tags are permanent) |


### Configuring Order Tags

1. Go to **Appstle Admin** → **Settings**
2. Find the **Order Tags** section
3. Enter your desired tag values (or keep the defaults)
4. Save settings


> **Backfill endpoint:** If you need to apply tags to orders that were created before tags were configured, use the `MiscellaneousResource.applyMissedOrderTags()` API endpoint to backfill missed tags.


## 👤 Customer Tags

Customer tags are **dynamic** — they change as subscription status changes. They support **Liquid template syntax** with access to subscription data variables, enabling highly customized tags.

### Tag Lifecycle & Mutual Exclusivity

Customer tags follow a strict **priority hierarchy** — only one status tag is active at a time:

```
ACTIVE > PAUSED > INACTIVE
```

| Status | Condition | Config Field | Default Value |
|  --- | --- | --- | --- |
| **Active** | Customer has 1+ ACTIVE subscription contracts | `customerActiveSubscriptionTag` | `appstle_subscription_active_customer` |
| **Paused** | Customer has PAUSED contracts but no active ones | `customerPausedSubscriptionTag` | `appstle_subscription_paused_customer` |
| **Inactive** | Customer has no active or paused contracts (all cancelled) | `customerInActiveSubscriptionTag` | `appstle_subscription_inactive_customer` |


**When status changes:**

1. The previous status tag is **removed** (`TagsRemoveMutation`)
2. The new status tag is **added** (`TagsAddMutation`)
3. The applied tag is tracked in the database (`SubscriptionContractDetails.customerTag` column)


### When Customer Tags Are Applied

| Event | Action |
|  --- | --- |
| Subscription contract created | Active tag applied |
| Subscription paused | Active tag removed → Paused tag applied (if no other active contracts) |
| Subscription resumed | Paused tag removed → Active tag applied |
| Subscription cancelled | Tag recalculated based on remaining contracts |
| Billing attempt succeeds/fails | Tag recalculated |
| Subscription event processed | Tag recalculated |


**Processing locations:**

- **subscription app:** `SubscriptionContractDetailsServiceImpl.updateCustomerTags()`
- **subscription-async:** `AbstractSubscriptionService.updateCustomerTags()` — triggered from contract creation, status changes, and subscription events
- **Deferred updates:** Via AWS Step Functions state machine for batched/delayed processing


### Liquid Template Variables

Customer tags support **Liquid template syntax**, allowing you to create dynamic, data-driven tags. Wrap variables in double curly braces: `{{variable}}`.

#### Available Variables

| Variable | Type | Description | Example Value |
|  --- | --- | --- | --- |
| `{{customer.id}}` | String | Shopify customer GID | `gid://shopify/Customer/1234567890` |
| `{{contract.id}}` | String | Subscription contract GID | `gid://shopify/SubscriptionContract/9876` |
| `{{contract.sellingPlanIds}}` | String | Comma-separated selling plan IDs | `gid://shopify/SellingPlan/111` |
| `{{contract.sellingPlanNames}}` | String | Comma-separated selling plan names | `Monthly Subscription - 10% off` |
| `{{contract.variantIds}}` | String | Comma-separated variant IDs | `gid://shopify/ProductVariant/222` |
| `{{contract.variantNames}}` | String | Comma-separated variant names | `Default Title` |
| `{{contract.currentCycle}}` | Number | Current billing cycle number | `3` |
| `{{contract.cancellationReason}}` | String | Cancellation reason (if cancelled) | `Too expensive` |
| `{{order.id}}` | String | First order GID | `gid://shopify/Order/444` |
| `{{order.createdAt}}` | String | First order creation date (ISO 8601) | `2025-01-15T10:30:00Z` |


**Line item variables** (available within the contract context):

| Variable | Type | Description |
|  --- | --- | --- |
| `sellingPlanId` | String | Selling plan ID for this line item |
| `variantId` | String | Variant ID |
| `variantTitle` | String | Variant title |
| `title` | String | Product title |
| `productId` | String | Product ID |
| `sku` | String | SKU |
| `sellingPlanName` | String | Selling plan name |


#### Example Liquid Templates

**Static tag (default behavior):**

```
appstle_subscription_active_customer
```

**Dynamic tag with selling plan name:**

```
active_subscriber_{{contract.sellingPlanNames}}
```

Result: `active_subscriber_Monthly Subscription - 10% off`

**Dynamic tag with cycle count:**

```
subscriber_cycle_{{contract.currentCycle}}
```

Result: `subscriber_cycle_3`

**Dynamic tag with product SKU:**

```
subscribed_to_{{contract.variantNames}}
```

Result: `subscribed_to_Default Title`

### Configuring Customer Tags

1. Go to **Appstle Admin** → **Settings**
2. Find the **Customer Tags** section
3. Enter your desired tag templates for each status (Active, Paused, Inactive)
4. Use Liquid variables for dynamic tags, or plain text for static tags
5. Save settings


## 🔗 Subscription Contract Custom Attributes

In addition to metafields and tags, Appstle supports Shopify's native `customAttributes` on subscription contracts. These are key-value pairs that can be transferred from the original order.

### Transfer Settings

These settings control whether data from the original order is copied to the subscription contract:

| Setting | Default | Description |
|  --- | --- | --- |
| `transferOrderNotesToSubscription` | `true` | Transfer order notes (text memo) to the subscription contract |
| `transferOrderNoteAttributesToSubscription` | `true` | Transfer order note attributes (key-value pairs) to the subscription contract |
| `transferOrderLineItemAttributesToSubscription` | `true` | Transfer line item attributes to the subscription contract |


Configure these in **Appstle Admin** → **Settings**.

## 📊 Summary Tables

### All Metafields at a Glance

| Resource | Namespace | Key | Type | Visibility |
|  --- | --- | --- | --- | --- |
| Shop | `appstle_subscription` | `setting` | json | Public |
| Shop | `appstle_subscription` | `widget_template_html` | multi_line_text_field | Public |
| Shop | `appstle_subscription` | `all_widget_template_html` | json | Public |
| Shop | `appstle_subscription` | `bundle` | json | Public |
| Shop | `appstle_subscription` | `labels` | multi_line_text_field | Public |
| Shop | `appstle_subscription` | `selling_plans` | json | Public |
| Shop | `appstle_subscription` | `checkout_validation` | json | Public |
| Shop | `appstle_subscription` | `bab_subscription_css` | json | Public |
| Shop | `appstle_subscription` | `bab_customization_css` | json | Public |
| Shop | `appstle_subscription` | `shop_info` | json | Public |
| Shop | `appstle_subscription` | `bab_validation_info` | json | Public |
| Shop | `appstle_subscription` | `bab_setting_info` | json | Public |
| Shop | `appstle_subscription` | `all_Selling_Plans` | json | Public |
| Shop | `appstle_subscription` | `bab_info_[0-N]` | json | Public |
| Shop | `appstle_subscription` | `total_bab` | integer | Public |
| Selling Plan | `appstle_subscription` | `selling_plan` | json | Public |
| Order | `appstle_subscription` | `details` | json | Public |
| Customer | `appstle_subscription` | `subscriptions` | json | Public |


### All Tags at a Glance

| Resource | Tag | Default Value | Liquid Support | Permanent? |
|  --- | --- | --- | --- | --- |
| Order | First-time order tag | `appstle_subscription_first_order` | No | Yes |
| Order | Recurring order tag | `appstle_subscription_recurring_order` | No | Yes |
| Customer | Active subscription tag | `appstle_subscription_active_customer` | Yes | No (swapped on status change) |
| Customer | Paused subscription tag | `appstle_subscription_paused_customer` | Yes | No (swapped on status change) |
| Customer | Inactive subscription tag | `appstle_subscription_inactive_customer` | Yes | No (swapped on status change) |


### GraphQL Mutations Used

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


## ❓ FAQ

details
summary
strong
Can I read subscription metafields from my Liquid theme?
Yes. All subscription metafields use the `appstle_subscription` namespace (no `$app:` prefix), so they are accessible in Liquid templates via `{{ shop.metafields.appstle_subscription.setting }}`, `{{ customer.metafields.appstle_subscription.subscriptions }}`, etc.

details
summary
strong
How quickly are customer metafields updated after a contract change?
Customer metafield updates are queued via SQS (`subscription-update-customer-metafields.fifo`) and processed asynchronously. Typical latency is a few seconds, but during high-traffic periods it may take longer.

details
summary
strong
Can I use the same tag template for Active and Paused?
Technically yes, but this defeats the purpose of mutual exclusivity. The system removes one and adds the other — if they're identical, the tag will be briefly removed and re-added, which could cause issues with automations that react to tag changes.

details
summary
strong
What happens to customer tags when a customer has multiple subscriptions?
Tags follow a priority hierarchy: Active > Paused > Inactive. If a customer has both active and paused subscriptions, the active tag takes precedence. The inactive tag is only applied when ALL contracts are cancelled.

details
summary
strong
Can I backfill order tags for existing orders?
Yes. Use the `applyMissedOrderTags` API endpoint to backfill tags on orders that were created before your tag configuration was set up.