skills/pricing-promotions/dynamic-pricing/SKILL.md
Automatically adjust prices based on demand signals, competitor prices, and inventory levels to maximize revenue and stay competitive
npx skillsauth add finsilabs/awesome-ecommerce-skills dynamic-pricingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Dynamic pricing automatically adjusts product prices based on demand signals, inventory levels, competitor prices, and business rules. The goal is to maximize revenue per unit sold — raising prices when demand is strong or inventory is scarce, and reducing them to clear slow-moving stock. Most Shopify and WooCommerce merchants accomplish this with a repricing app rather than custom code. Custom implementations are reserved for headless storefronts or merchants with unique repricing logic that apps cannot handle.
| Platform | Recommended Tool | Why | |----------|-----------------|-----| | Shopify | Prisync, Wiser, or Skio Pricing | Prisync monitors competitor prices and pushes price updates via Shopify Admin API; Wiser uses demand signals and inventory | | Shopify Plus | Prisync + Shopify Flow automations | Shopify Flow can trigger price changes via webhooks based on inventory or sales velocity signals | | WooCommerce | Prisync, Repricer.com (via WooCommerce API), or custom via WooCommerce REST API | Most repricers support WooCommerce through the product API | | BigCommerce | Prisync, Linnworks, or ChannelAdvisor | BigCommerce's Price Lists API is designed for dynamic segment-based pricing | | Amazon/Multi-channel | RepricerExpress, BQool, or Seller Snap | Purpose-built for Amazon repricing with Buy Box optimization | | Custom / Headless | Build a pricing job that calls your platform's pricing API | Full control; required when custom logic exceeds what apps can handle |
Before touching any tool, document your guardrails — these prevent the repricing algorithm from making decisions that destroy margin or customer trust:
| Rule | Example | |------|---------| | Floor price | Never go below cost × 1.15 (15% gross margin minimum) | | Ceiling price | Never exceed MSRP or a set maximum | | Maximum change per cycle | Never change more than 20% in a single repricing run | | Change threshold | Only update if the new price differs by more than 2% (prevents micro-oscillation) | | Lock-out periods | Do not reprice during active flash sales or promotions | | Human review threshold | Any change greater than 10% must be queued for human approval before applying |
Option A: Prisync (competitor-based repricing)
Option B: Shopify Flow (inventory/demand-based, Shopify Plus)
Keep the original prices stored as a product metafield (product.metafields.pricing.original_price) so you can always restore them.
Option C: Third-party apps for demand-based repricing
Option A: Prisync + WooCommerce REST API
PUT /wp-json/wc/v3/products/{id}/variations/{id} to update pricesOption B: Custom scheduled repricing via WP-Cron
For demand-based repricing in WooCommerce, use a scheduled task:
wc_get_product()->set_price() and save() to update prices programmaticallyOption C: YITH Dynamic Pricing plugin
For time-based price changes (scheduled markdowns):
BigCommerce's Price Lists feature is ideal for dynamic customer-segment pricing:
For site-wide dynamic pricing without customer segmentation, use the BigCommerce Catalog API to update sale_price on products based on your repricing schedule.
Third-party tools: Linnworks and ChannelAdvisor both integrate with BigCommerce and include competitor monitoring and automated repricing.
For headless storefronts, implement a repricing job that runs on a schedule:
import { CronJob } from 'cron';
interface PricingContext {
productId: string;
currentPriceCents: number;
costCents: number;
inventoryLevel: number;
salesVelocity7d: number; // units/day rolling average
competitorLowestCents?: number; // from price intelligence feed
floorCents: number;
ceilingCents: number;
}
function computeNewPrice(ctx: PricingContext): { priceCents: number; reason: string } {
let price = ctx.currentPriceCents;
const reasons: string[] = [];
// Inventory pressure: near stockout → slow demand with a price increase
if (ctx.inventoryLevel <= 5 && ctx.salesVelocity7d > 0.5) {
price = Math.round(price * 1.08);
reasons.push('low_inventory');
}
// Slow mover: markdown if no meaningful sales in 7 days
if (ctx.salesVelocity7d < 0.1) {
price = Math.round(price * 0.95);
reasons.push('slow_mover_markdown');
}
// Competitor pricing: stay competitive
if (ctx.competitorLowestCents && price > ctx.competitorLowestCents * 1.05) {
price = Math.round(ctx.competitorLowestCents * 0.99); // undercut by 1%
reasons.push('competitor_undercut');
}
// Enforce guardrails
const marginFloor = Math.round(ctx.costCents * 1.15);
price = Math.max(price, marginFloor, ctx.floorCents);
price = Math.min(price, ctx.ceilingCents);
// Only apply if change exceeds 2% threshold
const changePct = Math.abs(price - ctx.currentPriceCents) / ctx.currentPriceCents;
if (changePct < 0.02) return { priceCents: ctx.currentPriceCents, reason: 'below_threshold' };
// Cap single-run change at 20%
if (changePct > 0.20) {
const direction = price > ctx.currentPriceCents ? 1 : -1;
price = Math.round(ctx.currentPriceCents * (1 + direction * 0.20));
reasons.push('capped_at_20pct');
}
return { priceCents: price, reason: reasons.join(',') || 'no_change' };
}
// Run every 30 minutes during business hours
new CronJob('*/30 6-22 * * *', async () => {
const products = await db.products.findAll({ dynamicPricingEnabled: true });
for (const product of products) {
const ctx = await buildPricingContext(product);
const { priceCents, reason } = computeNewPrice(ctx);
if (priceCents !== ctx.currentPriceCents) {
await platformApi.updatePrice(product.id, priceCents);
await db.priceHistory.insert({ productId: product.id, oldPrice: ctx.currentPriceCents, newPrice: priceCents, reason, changedAt: new Date() });
}
}
}, null, true, 'America/New_York');
floor = cost × 1 + min_margin and make it inviolable; no algorithm override permitted below cost| Problem | Solution |
|---------|----------|
| Price drops below cost during competitor war | Enforce Math.max(newPrice, costCents * 1.15) as an absolute floor that the algorithm cannot bypass |
| Stale competitor prices cause bad repricing | Store fetched_at on every competitor price record; skip prices older than 4 hours |
| Price oscillation — engine keeps raising then lowering | Add a minimum 2-hour cooldown between changes and a 2% hysteresis band |
| Repricing fires during an active flash sale | Check for active promotions before applying algorithmic changes; add an is_price_locked flag to products in active sales |
| CDN/search index serves old price after update | Purge the product page cache and update the search index immediately after each price change |
tools
Let shoppers save products to a wishlist, share it with friends, and get notified when saved items come back in stock or drop in price
development
Build a themeable storefront with design tokens and CSS custom properties that supports white-labeling, multi-brand variants, and dark mode
development
Speed up product discovery with instant search suggestions, fuzzy typo matching, and category-aware results powered by Algolia or Elasticsearch
development
Build a mobile-first storefront with thumb-friendly navigation, sticky add-to-cart buttons, and touch-optimized components for high mobile conversion