
Dynamic Pricing
- 65 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Automatically adjust prices from demand signals, competitor prices, and inventory levels using a repricing app or custom pricing job.
About
Recommends repricing tools per platform and covers building a custom pricing job against the platform's pricing API. A developer uses it to react to competitor prices, liquidate slow stock, or run revenue management.
- Per-platform and Amazon repricer tool table (Prisync, Wiser, RepricerExpress)
- Custom pricing-job approach against the platform pricing API for unique logic
Dynamic Pricing by the numbers
- 65 all-time installs (skills.sh)
- Ranked #3,110 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill dynamic-pricingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 65 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Automatically adjust prices from demand signals, competitor prices, and inventory levels using a repricing app or custom pricing job.
Files
Dynamic Pricing
Overview
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.
When to Use This Skill
- When high-velocity SKUs lose revenue because prices are set-and-forgotten while competitors adjust hourly
- When you need to liquidate slow-moving inventory through automatic markdown schedules
- When running a marketplace where seller prices must respond to competitive pressure
- When building a revenue management system for perishable or time-sensitive inventory
- When A/B testing price elasticity at scale and needing a framework to safely roll out price changes
Core Instructions
Step 1: Determine the merchant's platform and choose the right tool
| 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 |
Step 2: Define your pricing rules before configuring any tool
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 |
Step 3: Platform-specific setup
---
Shopify
Option A: Prisync (competitor-based repricing)
1. Sign up at prisync.com and connect your Shopify store via the integration 2. Add competitor product URLs to track in the Prisync dashboard 3. Set repricing rules: "Match lowest competitor price", "Beat by X%", or "Match and beat" 4. Set your floor and ceiling prices per product 5. Prisync pushes price updates to Shopify automatically on your configured schedule (hourly, daily)
Option B: Shopify Flow (inventory/demand-based, Shopify Plus)
1. In your Shopify admin, go to Apps → Flow 2. Create a new workflow triggered by Inventory level changed or a custom webhook 3. Add a condition: e.g., "Inventory quantity is less than 10" 4. Add an action: Update product variant and set the price to a higher value 5. Add a separate workflow for when inventory recovers to restore the original price
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
- Wiser (Shopify App Store): uses sales velocity, add-to-cart rates, and inventory to suggest and apply price changes
- Bold Commerce's Pricing: allows scheduling price changes and automated rules
---
WooCommerce
Option A: Prisync + WooCommerce REST API
1. Connect Prisync to WooCommerce using the REST API credentials (WooCommerce Settings → Advanced → REST API → Add key) 2. Map your products in Prisync to competitor URLs 3. Configure repricing rules and schedules in Prisync 4. Prisync calls PUT /wp-json/wc/v3/products/{id}/variations/{id} to update prices
Option B: Custom scheduled repricing via WP-Cron
For demand-based repricing in WooCommerce, use a scheduled task: 1. Write a PHP function that checks inventory levels and sales velocity via WooCommerce's order history 2. Schedule it with WP-Cron or a server cron 3. Call wc_get_product()->set_price() and save() to update prices programmatically 4. Log every price change to a custom table for audit and rollback
Option C: YITH Dynamic Pricing plugin
For time-based price changes (scheduled markdowns): 1. Install YITH WooCommerce Dynamic Pricing & Discounts 2. Create rules that apply a percentage discount during specific date ranges 3. This is simpler than full dynamic repricing but covers "end of season" markdown use cases
---
BigCommerce
BigCommerce's Price Lists feature is ideal for dynamic customer-segment pricing:
1. Go to Products → Price Lists 2. Create price lists per customer group (e.g., "demand_high_inventory_low") 3. Use the BigCommerce Price Lists API to update prices programmatically based on your pricing logic 4. Assign price lists to customer groups via Customers → Customer Groups
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.
---
Custom / Headless
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');Best Practices
- Always enforce a floor price tied to cost — compute
floor = cost × 1 + min_marginand make it inviolable; no algorithm override permitted below cost - Store a full price history — every price change needs a row with timestamp, old price, new price, and reason for rollback, audits, and elasticity analysis
- Cap single-run price changes — limit any single job run to ±20% to prevent runaway repricing from bad data or bugs
- Separate recommendation from application — the engine proposes a price; a separate step applies it; this enables human review queues and dry-run mode
- Alert on large automatic changes — send a Slack or email alert when the engine applies a change greater than 10% so a human can review
- A/B test price changes — before rolling out a new price site-wide, run a test on a segment of visitors using the A/B testing pricing skill
Common Pitfalls
| 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 |
Related Skills
- @ab-testing-pricing
- @flash-sale-engine
- @price-rules-engine
- @volume-pricing
- @discount-engine
{
"context": "Tests whether the agent uses the correct scraping library (playwright/chromium), implements the parsePriceCents helper correctly, filters stale competitor data, uses the correct cron schedule pattern and timezone, structures the price_history table schema correctly, and applies price changes inside a database transaction while also updating the search index.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Uses playwright chromium",
"max_score": 8,
"description": "The competitor monitor imports from 'playwright' and uses chromium.launch() — NOT puppeteer, selenium, or any other browser automation library"
},
{
"name": "Headless browser mode",
"max_score": 5,
"description": "chromium.launch() is called with { headless: true }"
},
{
"name": "Page navigation timeout",
"max_score": 5,
"description": "page.goto() is called with a timeout of exactly 15000 milliseconds"
},
{
"name": "parsePriceCents: strips currency symbols",
"max_score": 8,
"description": "The price parsing function removes dollar signs and commas (via replace or similar) before extracting the numeric value"
},
{
"name": "parsePriceCents: converts to integer cents",
"max_score": 8,
"description": "The price parsing function multiplies the parsed float by 100 and rounds to an integer (not stored as dollars or float cents)"
},
{
"name": "Staleness filter: 4-hour cutoff",
"max_score": 10,
"description": "Competitor prices with a fetchedAt timestamp older than 4 hours are excluded from the returned results"
},
{
"name": "Cron pattern: every 30 minutes during business hours",
"max_score": 8,
"description": "The cron expression '*/30 6-22 * * *' (or functionally equivalent) is used for the pricing job schedule"
},
{
"name": "Cron timezone: America/New_York",
"max_score": 5,
"description": "The cron job is configured with timezone 'America/New_York'"
},
{
"name": "price_history table: correct field types",
"max_score": 8,
"description": "The price_history SQL table has old_price and new_price as INTEGER type (not DECIMAL or FLOAT), and changed_at as TIMESTAMPTZ"
},
{
"name": "price_history table: audit fields",
"max_score": 8,
"description": "The price_history table includes a reason column (TEXT) and a changed_by column with a default value identifying the pricing engine"
},
{
"name": "price_history index",
"max_score": 8,
"description": "An index is created on price_history for (product_id, changed_at DESC) to support efficient per-product history lookups"
},
{
"name": "Price change in transaction",
"max_score": 10,
"description": "The price history data-access function wraps both the product price update AND the price_history insert inside a single database transaction"
},
{
"name": "Search index update after transaction",
"max_score": 9,
"description": "After recording the price change, the code also updates the search index (e.g. calls a searchIndex.updatePrice or equivalent) in the same applyPriceChange flow"
}
]
}
Competitor Price Monitor and Price Audit Trail
Problem/Feature Description
PriceWatch is a B2B SaaS product that helps online retailers track competitor prices and maintain a complete audit trail of every price change for regulatory compliance. A new client in the EU needs two things: (1) a module that can scrape live prices from competitor product pages given a CSS selector and URL, discarding any data older than a certain freshness threshold; and (2) a PostgreSQL schema and corresponding data-access layer that records every price change with enough detail to support rollback, compliance audits, and volatility analytics. The scheduler should run frequently during store operating hours and be idle overnight.
The scraping module must handle pages that display prices in various formats like $1,299.00, € 899, or 1299.00 and convert them to integer cents for consistent internal storage.
Output Specification
Produce the following files:
1. competitor-monitor.ts — The competitor price fetching module. It should export a function that accepts a list of competitor configurations (each with a competitor name, product page URL, and CSS price selector) and returns parsed competitor prices. Include the parsePriceCents helper. Include staleness filtering so prices fetched more than 4 hours ago are excluded from results.
2. schema.sql — PostgreSQL DDL for the price history table, including the index needed for efficient per-product price history lookups.
3. price-history.ts — A data-access module that exports a function to record a price change (accepting old price, new price, product ID, and reason) using a database transaction, and also calls a search index update after the transaction.
4. scheduler.ts — Sets up the cron schedule for the pricing job. Should include a comment explaining when the job runs.
Input Files
No additional input files are provided. Implement all files from scratch.
{
"context": "Tests whether the agent implements the core dynamic pricing algorithm with the correct demand signal thresholds, inventory pressure logic, competitor undercutting rules, slow-mover markdown, and the two tiers of price guardrails (business floor/ceiling and cost-based margin floor). Also checks that the recommendation and application concerns are separated.",
"type": "weighted_checklist",
"checklist": [
{
"name": "TypeScript interfaces defined",
"max_score": 5,
"description": "Defines separate named types/interfaces for pricing context input, competitor price, and pricing decision output (three distinct structures)"
},
{
"name": "Prices stored as cents",
"max_score": 5,
"description": "The interface/type definitions use integer or number fields annotated or documented as cents for currentPrice, costPrice, floorPrice, ceilingPrice (not floating-point dollars)"
},
{
"name": "Demand signal: conversion threshold",
"max_score": 10,
"description": "High-demand price increase is triggered when conversionRate (salesLast24h divided by max(viewsLast24h, 1)) exceeds 0.08 — uses exactly 0.08 as the threshold"
},
{
"name": "Demand signal: inventory condition",
"max_score": 8,
"description": "The high-demand +5% increase is only applied when inventoryLevel is above the reorderPoint (not just on conversion rate alone)"
},
{
"name": "Inventory pressure: correct multiplier",
"max_score": 10,
"description": "Near-stockout condition (inventoryLevel <= reorderPoint * 0.5) triggers a price increase of exactly +8% (multiplied by 1.08, rounded to integer)"
},
{
"name": "Competitor undercut: 2% band",
"max_score": 8,
"description": "Competitor price reduction is only triggered when current price is more than 2% above the lowest in-stock competitor (threshold: lowestCompetitor * 1.02)"
},
{
"name": "Competitor undercut: 1% below",
"max_score": 8,
"description": "When the competitor undercut condition fires, the new price is set to lowestCompetitorPrice * 0.99 (1% below, not an exact match)"
},
{
"name": "Slow mover markdown",
"max_score": 8,
"description": "Products with salesVelocity7d below 0.1 receive a 5% markdown (multiplied by 0.95, rounded to integer)"
},
{
"name": "Business floor and ceiling enforced",
"max_score": 8,
"description": "Output price is clamped to the provided floorPrice and ceilingPrice values (both upper and lower bounds applied)"
},
{
"name": "15% margin floor enforced",
"max_score": 10,
"description": "Output price is always at least costPrice * 1.15 rounded to integer — this floor is applied regardless of floor/ceiling settings"
},
{
"name": "Confidence score varies by competitor data",
"max_score": 8,
"description": "confidenceScore in the output is higher (e.g. 0.85) when in-stock competitor prices are available vs lower (e.g. 0.6) when no in-stock competitors exist"
},
{
"name": "Recommendation separated from application",
"max_score": 12,
"description": "The compute/recommend function only returns a decision object — it does NOT directly mutate state, write to a database, or apply the price. A separate function or call site is responsible for applying changes."
}
]
}
Build a Dynamic Pricing Engine Module
Problem/Feature Description
ShopVelocity is a mid-size e-commerce retailer selling electronics and home goods. Their current pricing strategy is entirely manual: a merchandising analyst updates prices once a week based on gut feel, and the company is losing revenue to nimble competitors who adjust prices hourly. The VP of Product has commissioned a new automated pricing engine that can respond to live signals — how fast a product is selling, how much inventory remains, and what competitors are charging — to adjust prices automatically.
The engineering team needs a self-contained TypeScript module that accepts a snapshot of the current product state and pricing context, runs the decision logic, and produces a recommended price together with the reason for the change. The module must respect two levels of business constraints: hard per-product floor and ceiling prices set by the merchandising team, and an absolute cost-based margin floor that must never be violated regardless of any other rule.
Output Specification
Produce a single TypeScript file pricing-engine.ts that exports:
1. The data types/interfaces for the pricing context, competitor price input, and pricing decision output. 2. A computeRecommendedPrice function that accepts a pricing context and returns a pricing decision.
Also produce a pricing-engine.test.ts file that demonstrates the module working correctly across at least four distinct test cases covering different market conditions (e.g. high demand, near-stockout inventory, competitor price undercutting, slow-moving product). The tests should print their pass/fail result to stdout (use plain console.log assertions or a lightweight test runner — no test framework installation is required beyond what Node.js provides natively).
Input Files
No additional input files are provided. Implement the module from scratch based on the requirements above.
{
"context": "Tests whether the agent implements the price change safety and guardrails layer with the correct single-run cap (20%), human review threshold (10%), oscillation prevention (hysteresis band and minimum time gap), price-lock/promotion check, and alerting for large changes.",
"type": "weighted_checklist",
"checklist": [
{
"name": "20% single-run cap",
"max_score": 12,
"description": "Price changes exceeding 20% of the previous price are capped at exactly 20% (in the direction of the change), not rejected outright"
},
{
"name": "Cap direction preserved",
"max_score": 8,
"description": "When capping a change at 20%, the direction (increase vs decrease) is preserved — an upward change is capped upward and a downward change is capped downward"
},
{
"name": "10% threshold for human review",
"max_score": 12,
"description": "Changes between 10% and 20% of previousPrice are NOT auto-applied but instead queued for human review (e.g. inserted into a review queue with a pending status)"
},
{
"name": "Review queue entry has pending status",
"max_score": 5,
"description": "Queued entries include a status field with value 'pending_review'"
},
{
"name": "Price-locked products skipped",
"max_score": 10,
"description": "If the product has isPriceLocked set to true, no price change is applied and the function returns without modifying the price"
},
{
"name": "Active promotion check",
"max_score": 8,
"description": "If the product has hasActivePromotion set to true (or equivalent active-promotion flag), no algorithmic price change is applied"
},
{
"name": "Hysteresis band: 2% minimum change",
"max_score": 10,
"description": "Price changes smaller than 2% of the current price are suppressed as a no-op (hysteresis band to prevent micro-oscillations)"
},
{
"name": "Minimum time between changes",
"max_score": 10,
"description": "If the product was last repriced less than 2 hours ago, the new change is suppressed regardless of magnitude"
},
{
"name": "Alert triggered on large changes",
"max_score": 10,
"description": "When a change greater than 10% is auto-applied (not queued), an alert is triggered — either logged, returned in the result, or passed to a notification function"
},
{
"name": "Normal changes pass through unchanged",
"max_score": 15,
"description": "A change within bounds (less than 10% magnitude, more than 2 hours since last change, not price-locked, more than 2% change) is approved and returned as-is"
}
]
}
Implement a Price Change Safety Layer
Problem/Feature Description
RetailCore operates a large marketplace with tens of thousands of SKUs. They recently launched an algorithmic pricing engine that has been running for two weeks, but the ops team is alarmed by several incidents: some products swung 40% in a single hour, a few SKUs entered a rapid oscillation cycle where the price kept toggling between two values every 30 minutes, and — most embarrassingly — the engine overwrote the manually configured sale prices during last Friday's flash promotion, confusing customers who saw the advertised discount disappear at checkout.
The engineering team has been asked to build a safety and guardrails layer that wraps the raw pricing recommendations before they are applied to the catalog. This layer must prevent runaway repricing while still allowing the engine to make meaningful adjustments. It also needs to handle the human oversight workflow: large price movements should not be auto-applied but instead queued for a human reviewer to approve or reject.
Output Specification
Produce a TypeScript file price-guardrails.ts that implements the safety layer as a function (or class) accepting a raw pricing decision and the current product record, and returning either an approved decision (possibly adjusted), a queued-for-review record, or a no-op if the change should be suppressed.
Also produce a price-guardrails.test.ts file with test cases covering at minimum: a change that exceeds the single-run cap, a change that requires human review, a change on a price-locked product, a product that was last changed too recently, and a change within normal bounds. Print results to stdout.
Input Files
The following starter file provides the data types you should build upon. Extract it before beginning.
=============== FILE: inputs/types.ts =============== export interface PricingDecision { productId: string; recommendedPrice: number; // cents previousPrice: number; // cents changeReason: string; confidenceScore: number; effectiveAt: Date; }
export interface ProductRecord { id: string; price: number; // cents, current catalog price costPrice: number; // cents lastPricedAt: Date | null; // when the engine last changed this product's price isPriceLocked: boolean; // true when a promotion or manual override is active hasActivePromotion: boolean; }
export interface ReviewQueueEntry { decision: PricingDecision; status: 'pending_review'; queuedAt: Date; }
{
"name": "finsi/dynamic-pricing",
"version": "0.1.0",
"summary": "Demand-based pricing, competitor monitoring, and algorithmic price optimization",
"skills": {
"dynamic-pricing": {
"path": "SKILL.md"
}
}
}