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:
- UI display preferences (order, highlighting, badges)
- Feature flags and limitations
- Integration with other systems
- Categorization and filtering
- Internal tracking and reporting
- Localization information
- Custom business logic
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:
- Keep it structured: Organize your metadata with a consistent schema
- Use meaningful keys: Choose descriptive names for metadata properties
- Document your metadata: Maintain documentation of what each metadata field represents
- Validate metadata: Consider adding validation for your metadata structure
- Be mindful of size: Keep metadata reasonably sized (most providers have limits)
- Don't store sensitive data: Never store passwords, API keys, or personal data in 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
- Learn about Custom Providers to extend the library
- Explore Custom Pricing Logic for complex scenarios
- Check out the API Documentation for more advanced features