Skip to content
Last updated

🏷️ Metafields & Tags

Shopify Metafields & Tags Reference

Appstle Memberships uses Shopify metafields and tags to store membership data, control access via customer tags, and enable storefront gating logic. 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_membership and are managed through the Shopify GraphQL Admin API (MetafieldsSetMutation).

Metafields are set on three Shopify resource types:

ResourceCountVisibilityDescription
Shop6 keysPublic (readable by any app/theme)Membership settings, selling plans, access rules, checkout validation, widget labels
Customer2 keysPublicMembership contracts, trial/dunning state
Order1 keyPublicSubscription context for each order

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


🏪 Shop Metafields

Shop metafields store the membership program configuration and are used by the storefront for access gating, checkout validation, and widget rendering.

When updated: On every settings save in the Appstle admin. Updates are synchronous (immediate).

Core Settings

appstle_membership / setting

PropertyValue
Typejson
ResourceShop
PurposeShop-level membership settings snapshot for storefront access
Set bySubscribeItScriptUtils.updateShopMetafieldsForSettings()

appstle_membership / all_selling_plans

PropertyValue
Typejson
ResourceShop
PurposeAll selling plans configured for this shop's membership program
Set bySubscribeItScriptUtils.updateShopMetafieldsForSettings()
[
  {
    "id": "gid://shopify/SellingPlan/111",
    "name": "Basic Monthly Membership",
    "billingPolicy": {
      "interval": "MONTH",
      "intervalCount": 1
    },
    "customerTag": "basic-member",
    "orderTag": "membership-order"
  },
  {
    "id": "gid://shopify/SellingPlan/222",
    "name": "Premium Annual Membership",
    "billingPolicy": {
      "interval": "YEAR",
      "intervalCount": 1
    },
    "customerTag": "premium-member",
    "orderTag": "premium-membership-order"
  }
]

Access Control

appstle_membership / rules_by_customer_tag

PropertyValue
Typejson
ResourceShop
PurposeRules keyed by customer tag — used for storefront gating logic (controlling which products, collections, or pages are accessible to which membership tiers)
Set bySubscribeItScriptUtils.updateShopMetafieldsForSettings()
{
  "basic-member": {
    "accessibleCollections": ["gid://shopify/Collection/111"],
    "accessibleProducts": [],
    "gatingType": "COLLECTION"
  },
  "premium-member": {
    "accessibleCollections": ["gid://shopify/Collection/111", "gid://shopify/Collection/222"],
    "accessibleProducts": ["gid://shopify/Product/333"],
    "gatingType": "COLLECTION_AND_PRODUCT"
  }
}

How gating works: The storefront reads this metafield and the customer's tags to determine what content is visible. If a customer has the premium-member tag, they see all products and collections mapped to that tag. Non-members or lower tiers see gated content as locked/hidden.

appstle_membership / checkout_validation

PropertyValue
Typejson
ResourceShop
PurposeCheckout validation configuration — enforces member-only product purchase rules at checkout
Set bySubscribeItScriptUtils.updateShopMetafieldsForSettings()

Widget Labels

appstle_membership / widget_label

PropertyValue
Typejson
ResourceShop
PurposeStorefront widget label translations (default locale)
Set byLabelTranslationsServiceImpl.syncTranslationToShopify()

appstle_membership / customer_portal_label

PropertyValue
Typejson
ResourceShop
PurposeCustomer portal label translations (default locale)
Set byLabelTranslationsServiceImpl.syncTranslationToShopify()

Multi-language support: For non-default locales, labels are NOT stored as separate metafields. Instead, the app fetches the existing default-locale metafield ID and registers a Shopify Translation on it (using TranslationsRegisterMutation). This leverages Shopify's built-in translation system.


📦 Order Metafields

appstle_membership / details

PropertyValue
Typejson
ResourceOrder
PurposeFull membership contract context for this order
Set byMiscellaneousResource.updateOrderMetafields() (membership) and AbstractSubscriptionService.updateOrderMetafieldsFromQueue() (membership-async)
Updated whenOrder is created via membership (initial purchase or recurring billing)
Update mechanismQueued via SQS (membership-update-order-metafields.fifo), processed asynchronously

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

{
  "customer": {
    "id": "gid://shopify/Customer/1234567890",
    "name": "Jane Smith",
    "email": "jane@example.com"
  },
  "subscriptionContract": {
    "id": "gid://shopify/SubscriptionContract/9876543210",
    "status": "ACTIVE",
    "sellingPlanIds": ["gid://shopify/SellingPlan/111"],
    "sellingPlanNames": ["Premium Monthly Membership"],
    "variantIds": ["gid://shopify/ProductVariant/222"],
    "variantNames": ["Premium Membership"]
  },
  "firstOrder": {
    "id": "gid://shopify/Order/444",
    "createdAt": "2025-01-15T10:30:00Z"
  }
}

👤 Customer Metafields

Membership Contracts

appstle_membership / subscriptions

PropertyValue
Typejson
ResourceCustomer
PurposeAll membership contracts for this customer with full details
Set bySubscriptionContractDetailsServiceImpl.maybeUpdateCustomerMetaFields() (membership) and AbstractSubscriptionService.maybeUpdateCustomerMetaFieldsFromQueue() (membership-async)
Updated whenAny membership contract changes (created, updated, paused, cancelled, billing attempt)
Update mechanismQueued via SQS (membership-update-customer-metafields.fifo), processed asynchronously
[
  {
    "id": "gid://shopify/SubscriptionContract/9876543210",
    "status": "ACTIVE",
    "sellingPlanIds": ["gid://shopify/SellingPlan/111"],
    "sellingPlanNames": ["Premium Monthly Membership"],
    "variantIds": ["gid://shopify/ProductVariant/222"],
    "variantNames": ["Premium Membership"],
    "nextBillingDate": "2025-04-15T10:30:00Z"
  }
]

Trial & Dunning State

appstle_membership / setting

PropertyValue
Typejson
ResourceCustomer
PurposeCustomer-level membership settings: tracks trial tags and dunning tags for this customer's active plans
Set bySame as subscriptions metafield (updated together)
Updated whenMembership state changes (trial start/end, billing failure/success)
{
  "trialTags": "basic-member,premium-member",
  "dunningTags": "premium-member"
}
FieldPurpose
trialTagsComma-separated list of plan customer tags where the customer is currently in a free trial period (no successful billing yet, within trial window)
dunningTagsComma-separated list of plan customer tags where the customer has a recent failed billing attempt requiring retry

Note: The same key setting is used for both Shop-level and Customer-level metafields, but they contain different data structures. The resource type (Shop vs Customer) distinguishes them.


🏷️ Tags Overview

Appstle Memberships applies tags to both Customer and Order resources. Customer tags are the primary mechanism for membership access control — they determine which content, products, and collections a member can access.

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


👤 Customer Tags

Plan-Based Customer Tags (Primary Access Control)

This is the core tag mechanism for Appstle Memberships. Each selling plan (membership tier) has a merchant-configured customerTag that controls access.

PropertyValue
ResourceCustomer
FormatFree-form string configured per plan (e.g., basic-member, premium-member, vip-member)
ConfigurationSet per selling plan in the Appstle admin under each membership plan's settings
Source fieldSubscriptionGroupPlan.customerTag

Tag Lifecycle

EventTag Action
Membership contract ACTIVEPlan's customerTag is added to the customer
Membership contract CANCELLEDPlan's customerTag is removed (immediately if immediateTagRemoveOnCancel=true, otherwise at nextBillingDate)
Membership contract PAUSEDPlan's customerTag is removed (immediately if immediateTagRemoveOnPause=true, otherwise at nextBillingDate)
Membership contract in DUNNING (failed payment)Plan's customerTag is removed
Membership contract RESUMEDPlan's customerTag is re-added

Delayed vs Immediate Tag Removal

Merchants can configure when tags are removed on cancellation or pause:

SettingDefaultBehavior
immediateTagRemoveOnCancelfalseIf false, tag stays until nextBillingDate (member keeps access until their paid period ends). If true, tag is removed immediately.
immediateTagRemoveOnPausefalseSame behavior for paused memberships.

Best practice: Keep the defaults (false) to ensure members retain access for the period they've already paid for. Only set to true if your membership grants access to ongoing services (not prepaid periods).

Cross-Membership Protection

When removing a customer tag on cancel/pause, the app checks all other active memberships for the same customer. If another active contract uses the same customerTag, the tag is not removed.

Method: getCustomerTagsForOtherActiveMembership() — ensures a customer who holds the same membership tag from two different contracts doesn't lose access when one is cancelled.

Example Scenario

A customer has two contracts:

  1. "Basic Monthly" → customerTag: basic-member (ACTIVE)
  2. "Basic Annual" → customerTag: basic-member (ACTIVE)

If the customer cancels "Basic Monthly", the basic-member tag is not removed because "Basic Annual" is still active.

Trial-State Tags

PropertyValue
ResourceCustomer
FormatSame customerTag as the plan — no separate trial tag
Active duringFree trial period (no successful billing yet, within freeTrialCount + freeTrialInterval window)

During a free trial:

  • The plan's customerTag is applied (member gets access during trial)
  • The tag is tracked in the customer's setting metafield under trialTags
  • When the trial ends (expires without payment), the tag is removed

Key point: Trial members get the same tag as paying members. There is no separate "trial" tag — access is identical. The trialTags field in the customer metafield is for internal tracking only.

Dunning-State Tags

PropertyValue
ResourceCustomer
FormatSame customerTag as the plan
Active duringPeriod when a billing attempt has failed and retries are pending

When a billing attempt fails:

  • The plan's customerTag may be removed (member loses access during dunning)
  • The tag is tracked in the customer's setting metafield under dunningTags
  • If payment succeeds on retry, the tag is re-added

Plan Upgrade/Downgrade Tag Swap

When a member changes plans (variant swap), tags are swapped in a single operation:

StepAction
1Old plan's customerTag is removed
2New plan's customerTag is added
3If the upgrade requires payment and that payment fails, rollback: new tag removed, old tag restored

Rollback method: SubscriptionBillingAttemptService.rollbackCustomerTags() (membership-async)


📦 Order Tags

Plan-Based Order Tags

PropertyValue
ResourceOrder
FormatFree-form string configured per plan
ConfigurationSet per selling plan in the Appstle admin
Source fieldSubscriptionGroupPlan.orderTag
Applied whenOn initial membership purchase and on each renewal billing (unless skipRecurringOrderTag is enabled)
RemovedNever (order tags are permanent)

First-Time Order Tag (Liquid Template)

PropertyValue
ResourceOrder
Config fieldShopInfo.firstTimeOrderTag
FormatLiquid template string
Applied whenInitial membership contract creation only (not on renewals)
ConditionOnly applied if firstTimeOrderTag is non-empty AND the event is SUBSCRIPTION_CONTRACT_CREATE
RemovedNever

Recurring Order Tag (Liquid Template)

PropertyValue
ResourceOrder
Config fieldShopInfo.recurringOrderTag
FormatLiquid template string
Applied whenEach recurring billing attempt that creates an order
RemovedNever

Liquid Template Variables for Order Tags

Both firstTimeOrderTag and recurringOrderTag support Liquid template syntax using the TagModel:

VariableTypeDescriptionExample
{{customer.id}}StringShopify customer GIDgid://shopify/Customer/1234567890
{{subscriptionContract.id}}StringSubscription contract GIDgid://shopify/SubscriptionContract/9876
{{firstOrder.id}}StringFirst order GIDgid://shopify/Order/444
{{firstOrder.createdAt}}StringFirst order creation date (ISO 8601)2025-01-15T10:30:00Z

Example Liquid Templates

Static tag:

membership_first_order

Dynamic tag with contract info:

membership_{{subscriptionContract.id}}

Result: membership_gid://shopify/SubscriptionContract/9876

Variant Swap Order Tags

PropertyValue
ResourceOrder
Applied whenA plan upgrade/downgrade (variant swap) is processed
Tag valueNew plan's orderTag value
Set byVariantSwapPostProcessService (membership-async) and SubscriptionContractDetailsServiceImpl

🔗 Order Note Attributes

When transferOrderNoteAttributesToSubscription is enabled (default: true), the app transfers the original order's customAttributes (note attributes) to each renewal order. These are not tags but Shopify's native key-value pairs on orders.

SettingDefaultDescription
transferOrderNoteAttributesToSubscriptiontrueTransfer original order's custom attributes to renewal orders

📊 Summary Tables

All Metafields at a Glance

ResourceNamespaceKeyTypeVisibility
Shopappstle_membershipsettingjsonPublic
Shopappstle_membershipall_selling_plansjsonPublic
Shopappstle_membershiprules_by_customer_tagjsonPublic
Shopappstle_membershipcheckout_validationjsonPublic
Shopappstle_membershipwidget_labeljsonPublic
Shopappstle_membershipcustomer_portal_labeljsonPublic
Customerappstle_membershipsubscriptionsjsonPublic
Customerappstle_membershipsettingjsonPublic
Orderappstle_membershipdetailsjsonPublic

All Tags at a Glance

ResourceTag SourceWhen AppliedWhen RemovedPermanent?
CustomerSubscriptionGroupPlan.customerTagContract ACTIVE or TRIALContract CANCELLED, PAUSED, DUNNING, or EXPIREDNo
Customer(same tag on upgrade)Plan upgradePlan downgrade rollbackNo
OrderSubscriptionGroupPlan.orderTagOrder created (first or renewal)NeverYes
OrderShopInfo.firstTimeOrderTag (Liquid)First order onlyNeverYes
OrderShopInfo.recurringOrderTag (Liquid)Each renewalNeverYes
OrderNew plan's orderTagVariant swapNeverYes

GraphQL Mutations Used

MutationPurpose
MetafieldsSetMutationCreate or update metafields on any resource
TagsAddMutationAdd tags to orders or customers
TagsRemoveMutationRemove tags from customers
TranslationsRegisterMutationRegister translations for label metafields

❓ FAQ

Can I read membership metafields from my Liquid theme?

Yes. All membership metafields use the appstle_membership namespace (no $app: prefix), so they are accessible in Liquid templates via {{ shop.metafields.appstle_membership.setting }}, {{ customer.metafields.appstle_membership.subscriptions }}, etc.

How does membership access gating work with tags?

Each membership plan has a customerTag (e.g., premium-member). When a customer's contract is active, this tag is added to their Shopify customer profile. The rules_by_customer_tag shop metafield maps each tag to the collections/products that tag grants access to. Your theme reads these metafields and the customer's tags to show or hide gated content.

What happens when a member cancels mid-billing-cycle?

By default (immediateTagRemoveOnCancel=false), the customer keeps their membership tag until nextBillingDate. This means they retain access for the period they've already paid for. Set immediateTagRemoveOnCancel=true to remove access immediately on cancellation.

What if a customer has the same tag from two different memberships?

The app has cross-membership protection. When removing a tag due to cancellation/pause, it checks if any other active contract for this customer uses the same tag. If so, the tag is not removed. The customer retains access.

How are customer metafields updated — in real-time or batched?

Customer metafield updates are queued via SQS (membership-update-customer-metafields.fifo) and processed asynchronously by membership-async. Typical latency is a few seconds, but during high-traffic periods it may take longer.

Do trial members get the same tag as paying members?

Yes. During a free trial, the plan's customerTag is applied — there is no separate trial tag. Trial members get the same access as paying members. The trialTags field in the customer setting metafield tracks which tags are in trial for internal use only.

What happens during dunning (failed payment)?

When a billing attempt fails, the plan's customerTag may be removed (member loses access). The dunningTags field in the customer setting metafield tracks which tags are in dunning. If a retry succeeds, the tag is re-added and access is restored.