prices-as-code
Version:
Prices as Code (PaC) - Define your product pricing schemas with type-safe definitions
447 lines (387 loc) • 14.8 kB
HTML
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Metadata - Prices as Code</title>
<meta name="description" content="Learn how to work with metadata in Prices as Code for enhanced flexibility">
<link rel="stylesheet" href="../assets/css/main.css">
<link rel="icon" href="https://raw.githubusercontent.com/wickdninja/assets/refs/heads/main/PaC.webp">
</head>
<body>
<header class="site-header">
<div class="container">
<div class="header-content">
<div class="logo">
<a href="../index.html">
<img src="https://raw.githubusercontent.com/wickdninja/assets/refs/heads/main/PaC.webp" alt="Prices as Code" width="40">
<span>Prices as Code</span>
</a>
</div>
<nav class="main-nav">
<ul>
<li><a href="index.html">Guides</a></li>
<li><a href="../api/index.html">API</a></li>
<li><a href="../providers/index.html">Providers</a></li>
<li><a href="https://github.com/wickdninja/prices-as-code" target="_blank">GitHub</a></li>
<li><a href="https://www.npmjs.com/package/prices-as-code" target="_blank">NPM</a></li>
</ul>
</nav>
</div>
</div>
</header>
<main class="content">
<div class="container">
<h1>Working with Metadata</h1>
<p>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.</p>
<h2>What is Metadata?</h2>
<p>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 <code>metadata</code> field where you can store any JSON-serializable data.</p>
<h2>Use Cases for Metadata</h2>
<p>Metadata can be used for various purposes:</p>
<ul>
<li>UI display preferences (order, highlighting, badges)</li>
<li>Feature flags and limitations</li>
<li>Integration with other systems</li>
<li>Categorization and filtering</li>
<li>Internal tracking and reporting</li>
<li>Localization information</li>
<li>Custom business logic</li>
</ul>
<h2>Adding Metadata to Products</h2>
<p>Here's how to add metadata to your product definitions:</p>
<div class="highlight">
<pre><code class="language-typescript">// 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"]
}
}</code></pre>
</div>
<div class="highlight">
<pre><code class="language-yaml"># 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</code></pre>
</div>
<h2>Adding Metadata to Prices</h2>
<p>Similarly, you can add metadata to your price definitions:</p>
<div class="highlight">
<pre><code class="language-typescript">// 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"]
}
}</code></pre>
</div>
<div class="highlight">
<pre><code class="language-yaml"># 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</code></pre>
</div>
<h2>Metadata Best Practices</h2>
<p>Follow these best practices when working with metadata:</p>
<ul>
<li><strong>Keep it structured</strong>: Organize your metadata with a consistent schema</li>
<li><strong>Use meaningful keys</strong>: Choose descriptive names for metadata properties</li>
<li><strong>Document your metadata</strong>: Maintain documentation of what each metadata field represents</li>
<li><strong>Validate metadata</strong>: Consider adding validation for your metadata structure</li>
<li><strong>Be mindful of size</strong>: Keep metadata reasonably sized (most providers have limits)</li>
<li><strong>Don't store sensitive data</strong>: Never store passwords, API keys, or personal data in metadata</li>
</ul>
<h2>Working with Metadata in Code</h2>
<p>Access and use metadata in your application code:</p>
<div class="highlight">
<pre><code class="language-typescript">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;
}</code></pre>
</div>
<h2>Using Metadata for UI Customization</h2>
<p>Metadata is particularly useful for customizing UI elements:</p>
<div class="highlight">
<pre><code class="language-typescript">// 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 (
<div className="pricing-table">
{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 (
<div
className={`pricing-card ${product.highlight ? 'highlighted' : ''}`}
key={product.key}
>
{product.metadata?.category && (
<div className="category-badge">{product.metadata.category}</div>
)}
<h3 className="product-name">{product.name}</h3>
<p className="product-description">{product.description}</p>
<div className="price-display">
<span className="currency">{displayPrice.currency.toUpperCase()}</span>
<span className="amount">
{(displayPrice.unitAmount / 100).toFixed(2)}
</span>
<span className="interval">
{displayPrice.recurring ? `/${displayPrice.recurring.interval}` : ''}
</span>
</div>
{displayPrice.metadata?.savings && (
<div className="savings-badge">
Save {displayPrice.metadata.savings}
</div>
)}
<ul className="features-list">
{product.features.map((feature, i) => (
<li key={i}>{feature}</li>
))}
</ul>
{displayPrice.metadata?.trialDays && (
<p className="trial-message">
{displayPrice.metadata.trialDays}-day free trial
</p>
)}
<button className="subscribe-button">
Subscribe to {displayPrice.metadata?.displayName || displayPrice.nickname}
</button>
{displayPrice.metadata?.upsellMessage && (
<p className="upsell-message">{displayPrice.metadata.upsellMessage}</p>
)}
</div>
);
})}
</div>
);
}</code></pre>
</div>
<h2>Provider-Specific Metadata</h2>
<p>Different payment providers might have specific metadata requirements or limitations:</p>
<div class="table-responsive">
<table>
<thead>
<tr>
<th>Provider</th>
<th>Metadata Limits</th>
<th>Special Considerations</th>
</tr>
</thead>
<tbody>
<tr>
<td>Stripe</td>
<td>Up to 50 keys, keys up to 40 characters, values up to 500 characters</td>
<td>Only strings are allowed for values</td>
</tr>
<tr>
<td>Recurly</td>
<td>Up to 255 characters for both keys and values</td>
<td>Limited to string key-value pairs</td>
</tr>
</tbody>
</table>
</div>
<p>When working with providers that have strict metadata limitations, Prices as Code automatically handles the serialization and deserialization of complex metadata objects:</p>
<div class="highlight">
<pre><code class="language-typescript">// 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\"]"
}</code></pre>
</div>
<h2>Metadata for Feature Flags</h2>
<p>Metadata is ideal for implementing feature flags in your pricing:</p>
<div class="highlight">
<pre><code class="language-typescript">// 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
}</code></pre>
</div>
<h2>Next Steps</h2>
<ul>
<li>Learn about <a href="custom-providers.html">Custom Providers</a> to extend the library</li>
<li>Explore <a href="custom-pricing.html">Custom Pricing Logic</a> for complex scenarios</li>
<li>Check out the <a href="../api/index.html">API Documentation</a> for more advanced features</li>
</ul>
</div>
</main>
<footer class="site-footer">
<div class="container">
<div class="footer-content">
<p>Copyright © 2025 Nate Ross. Distributed by an <a href="https://github.com/wickdninja/prices-as-code/blob/main/LICENSE">MIT license.</a></p>
<p><a href="#top" class="back-to-top">Back to top</a></p>
</div>
</div>
</footer>
<script src="../assets/js/main.js"></script>
</body>
</html>