Working with Metadata

Metadata is a powerful feature in Prices as Code that allows you to attach custom information to your products and prices. This guide explains how to effectively use metadata to extend the functionality of your pricing configuration.

What is Metadata?

Metadata is additional information that doesn't affect the core functionality of products or prices but provides context, categorization, or custom attributes. Both products and prices in Prices as Code support a metadata field where you can store any JSON-serializable data.

Use Cases for Metadata

Metadata can be used for various purposes:

Adding Metadata to Products

Here's how to add metadata to your product definitions:

// TypeScript example
{
  provider: "stripe",
  name: "Pro Plan",
  description: "For growing businesses",
  features: [
    "Unlimited projects",
    "100GB storage",
    "Priority support"
  ],
  highlight: true,
  metadata: {
    displayOrder: 2,
    category: "business",
    planCode: "PRO",
    featureLimits: {
      maxUsers: 10,
      maxStorage: 100,
      maxProjects: -1 // unlimited
    },
    upsellTarget: "enterprise_plan",
    availableRegions: ["US", "EU", "APAC"]
  }
}
# YAML example
- provider: stripe
  name: Pro Plan
  description: For growing businesses
  features:
    - Unlimited projects
    - 100GB storage
    - Priority support
  highlight: true
  metadata:
    displayOrder: 2
    category: business
    planCode: PRO
    featureLimits:
      maxUsers: 10
      maxStorage: 100
      maxProjects: -1  # unlimited
    upsellTarget: enterprise_plan
    availableRegions:
      - US
      - EU
      - APAC

Adding Metadata to Prices

Similarly, you can add metadata to your price definitions:

// TypeScript example
{
  provider: "stripe",
  name: "Pro Annual",
  nickname: "Pro Annual",
  unitAmount: 19990,
  currency: "usd",
  type: "recurring",
  recurring: {
    interval: "year",
    intervalCount: 1,
  },
  productKey: "pro_plan",
  metadata: {
    displayName: "Pro Plan - Annual",
    popular: true,
    savings: "17%",
    planCode: "PRO_ANNUAL",
    billingCycle: "annual",
    trialDays: 14,
    upsellMessage: "Upgrade to Enterprise for more storage and users!",
    promotionEligible: true,
    tags: ["recommended", "best-value"]
  }
}
# YAML example
- provider: stripe
  name: Pro Annual
  nickname: Pro Annual
  unitAmount: 19990
  currency: usd
  type: recurring
  recurring:
    interval: year
    intervalCount: 1
  productKey: pro_plan
  metadata:
    displayName: Pro Plan - Annual
    popular: true
    savings: 17%
    planCode: PRO_ANNUAL
    billingCycle: annual
    trialDays: 14
    upsellMessage: Upgrade to Enterprise for more storage and users!
    promotionEligible: true
    tags:
      - recommended
      - best-value

Metadata Best Practices

Follow these best practices when working with metadata:

Working with Metadata in Code

Access and use metadata in your application code:

import { pac } from 'prices-as-code';
import config from './pricing.js';

// Example: Filter prices based on metadata
function getPricesForRegion(region) {
  return config.prices.filter(price => {
    // Check if price's product is available in the region
    const product = config.products.find(p => p.key === price.productKey);
    return product?.metadata?.availableRegions?.includes(region);
  });
}

// Example: Get recommended plan
function getRecommendedPlan() {
  return config.prices.find(price => 
    price.metadata?.tags?.includes('recommended')
  );
}

// Example: Get feature limits for a product
function getFeatureLimits(productKey) {
  const product = config.products.find(p => p.key === productKey);
  return product?.metadata?.featureLimits || {};
}

// Example: Check if user has exceeded limits
function hasExceededLimits(user, productKey) {
  const limits = getFeatureLimits(productKey);
  
  // Check against user's usage
  if (limits.maxUsers !== -1 && user.teamSize > limits.maxUsers) {
    return true;
  }
  
  if (limits.maxStorage !== -1 && user.storageUsed > limits.maxStorage) {
    return true;
  }
  
  return false;
}

// Example: Get upsell target for a product
function getUpsellTarget(productKey) {
  const product = config.products.find(p => p.key === productKey);
  const upsellKey = product?.metadata?.upsellTarget;
  
  if (upsellKey) {
    return config.products.find(p => p.key === upsellKey);
  }
  
  return null;
}

Using Metadata for UI Customization

Metadata is particularly useful for customizing UI elements:

// TypeScript example with React
import React from 'react';
import config from './pricing.js';

function PricingTable() {
  // Sort products by display order
  const sortedProducts = [...config.products].sort((a, b) => 
    (a.metadata?.displayOrder || 99) - (b.metadata?.displayOrder || 99)
  );
  
  return (
    
{sortedProducts.map(product => { // Get prices for this product const prices = config.prices.filter(p => p.productKey === product.key); // Find the "popular" price if any const popularPrice = prices.find(p => p.metadata?.popular); const displayPrice = popularPrice || prices[0]; return (
{product.metadata?.category && (
{product.metadata.category}
)}

{product.name}

{product.description}

{displayPrice.currency.toUpperCase()} {(displayPrice.unitAmount / 100).toFixed(2)} {displayPrice.recurring ? `/${displayPrice.recurring.interval}` : ''}
{displayPrice.metadata?.savings && (
Save {displayPrice.metadata.savings}
)}
    {product.features.map((feature, i) => (
  • {feature}
  • ))}
{displayPrice.metadata?.trialDays && (

{displayPrice.metadata.trialDays}-day free trial

)} {displayPrice.metadata?.upsellMessage && (

{displayPrice.metadata.upsellMessage}

)}
); })}
); }

Provider-Specific Metadata

Different payment providers might have specific metadata requirements or limitations:

Provider Metadata Limits Special Considerations
Stripe Up to 50 keys, keys up to 40 characters, values up to 500 characters Only strings are allowed for values
Recurly Up to 255 characters for both keys and values Limited to string key-value pairs

When working with providers that have strict metadata limitations, Prices as Code automatically handles the serialization and deserialization of complex metadata objects:

// In your configuration
metadata: {
  featureLimits: {
    maxUsers: 10,
    maxStorage: 100
  },
  availableRegions: ["US", "EU"]
}

// What gets stored in Stripe
metadata: {
  "featureLimits": "{\"maxUsers\":10,\"maxStorage\":100}",
  "availableRegions": "[\"US\",\"EU\"]"
}

Metadata for Feature Flags

Metadata is ideal for implementing feature flags in your pricing:

// Configuration
const config = {
  products: [
    {
      provider: "stripe",
      name: "Basic Plan",
      // ...
      metadata: {
        features: {
          reporting: true,
          export: false,
          api_access: false,
          white_label: false
        }
      }
    },
    {
      provider: "stripe",
      name: "Pro Plan",
      // ...
      metadata: {
        features: {
          reporting: true,
          export: true,
          api_access: true,
          white_label: false
        }
      }
    }
  ],
  // ...
};

// Usage in application
function hasFeatureAccess(user, featureName) {
  const subscription = getUserSubscription(user);
  if (!subscription) return false;
  
  const productKey = subscription.productKey;
  const product = config.products.find(p => p.key === productKey);
  
  return !!product?.metadata?.features?.[featureName];
}

// Example usage
if (hasFeatureAccess(currentUser, 'api_access')) {
  // Show API documentation and keys
} else {
  // Show upgrade prompt
}

Next Steps