This page is for developers who adjust or extend Tiered Pricing Table for WooCommerce with code: read and change prices, override templates, hook into the JavaScript, use the REST API and the most used actions and filters. Store owners find the setup and every feature in the user documentation.
Conventions: PHP code lives in the TierPricingTable namespace under src/, templates under views/. Every hook is prefixed with tiered_pricing_table/ and uses / as a separator. Settings are options prefixed with tier_pricing_table_. Most filters can be hooked from a theme or plugin at any time; the boot-time filters addons/list, integrations/plugins and integrations/themes run while the plugin loads and must be hooked from a must-use plugin or a plugin that loads before tier-pricing-table.
Prices and rules
↑ Back to topRead the effective rule and price
↑ Back to topuse TierPricingTable\PriceManager;
$rule = PriceManager::getPricingRule( $product_id ); // product meta + role, customer and global rules applied
$rule->getRules(); // [ 5 => 9.5, 10 => 8.0 ] (fixed) or [ 5 => 10, 10 => 20 ] (percentage)
$rule->getType(); // 'fixed' | 'percentage'
$rule->getMinimum(); // ?int
$rule->provider; // 'product' | 'role-based' | 'user-based' | 'global-rules'
$rule->pricingData; // base price overrides: regular_price, sale_price, discount, discount_type, tax_status, tax_class
$unit = $rule->getTierPrice( 12 ); // display price for quantity 12, false below the first tier
$unit = $rule->getTierPrice( 12, false, 'cart' ); // raw price, cart context
$has = \TierPricingTable\PricingTable::getInstance()->productHasPricingRules( $product ); // variable products too
Change the rule for any product
↑ Back to toptiered_pricing_table/price/pricing_rule receives the rule after the product meta was read. The built-in providers use these priorities, so pick yours relative to them: quantity limits 1, role-based 20, customer-based 25, global rules 30, request a quote 99, currency conversion 9999.
add_filter( 'tiered_pricing_table/price/pricing_rule', function ( \TierPricingTable\PricingRule $rule, int $productId ) {
if ( has_term( 'clearance', 'product_cat', $productId ) ) {
$rule->setType( 'percentage' );
$rule->setRules( [ 3 => 15, 6 => 25 ] ); // 15 % off from 3 units, 25 % from 6
$rule->logPricingModification( 'Clearance tiers applied by my-plugin' ); // shown in Debug mode
}
return $rule;
}, 40, 2 );
Write rules programmatically
↑ Back to top| Meta key (product or variation) | Value |
|---|---|
_fixed_price_rules | array<int quantity, string price>, for example [5 => '9.50', 10 => '8.00'] |
_percentage_price_rules | array<int quantity, string percent> |
_tiered_price_rules_type | fixed or percentage |
_tiered_price_minimum_qty | integer above 1 |
_tiered_pricing_maximum_quantity, _tiered_pricing_group_of_quantity | integer (maximum, step) |
_tiered_pricing_template, _tiered_pricing_base_unit_name | layout slug; ['singular' => …, 'plural' => …] |
_{role}_fixed_price_rules, _{role}_percentage_price_rules, _{role}_tiered_price_rules_type, _{role}_tiered_price_minimum_qty, _{role}_tiered_price_regular_price, _{role}_tiered_price_sale_price, _{role}_tiered_price_discount | role-based rules, {role} is the role slug; customer rules use _user_{userId}_… |
// Product level
update_post_meta( 123, '_fixed_price_rules', [ 5 => '9.50', 10 => '8.00' ] );
update_post_meta( 123, '_tiered_price_rules_type', 'fixed' );
\TierPricingTable\PriceManager::updateProductMinimumQuantity( 123, 5 );
// Role based
\TierPricingTable\Addons\RoleBasedPricing\RoleBasedPricingRule::buildFromArray( 123, 'wholesale', [
'pricing_type' => 'flat', 'regular_price' => 20, 'sale_price' => 18,
'tiered_pricing_type' => 'fixed', 'fixed_tiered_pricing_rules' => [ 10 => 17, 50 => 15 ],
'minimum_order_quantity' => 5,
] )->save();
// Global rule (post type tpt-global-rule)
use TierPricingTable\Addons\GlobalTieredPricing\CPT\GlobalTieredPricingCPT;
use TierPricingTable\Addons\GlobalTieredPricing\GlobalPricingRule;
$ruleId = wp_insert_post( [ 'post_type' => GlobalTieredPricingCPT::SLUG, 'post_status' => 'publish', 'post_title' => 'Wholesale tiers' ] );
$rule = GlobalPricingRule::fromArray( [ 'tiered_pricing_type' => 'fixed', 'fixed_rules' => [ 10 => 9.5, 50 => 8 ], 'minimum' => 5, 'applying_type' => 'individual' ] );
$rule->setId( $ruleId );
$rule->setIncludedProductCategories( [ 15 ] );
$rule->setIncludedUsersRole( [ 'wholesale' ] );
$rule->save();
\TierPricingTable\Core\ServiceContainer::getInstance()->getCache()->purge();
Templates
↑ Back to topRender the table yourself
↑ Back to topob_start();
\TierPricingTable\PricingTable::getInstance()->renderPricingTable( $product_id, null, [ 'display' => true, 'display_type' => 'blocks', 'title' => 'Volume pricing' ] );
$html = ob_get_clean();
Settings keys you can pass: display_type (table, blocks, options, dropdown, horizontal-table, plain-text, tooltip), title, quantity_column_title, price_column_title, discount_column_title, quantity_type (range or static), show_discount_column, clickable_rows, active_tier_color, table_style, blocks_style, options_style, compact_layout, options_show_total, options_show_original_product_price, options_show_default_option, options_option_text, update_price_on_product_page, show_total_price, quantity_measurement_singular, quantity_measurement_plural. The same keys work as shortcode attributes. Return false from tiered_pricing_table/should_render_pricing_table to suppress the automatic output, and change settings on the fly with tiered_pricing_table/display_settings.
Override templates from a theme
↑ Back to topCopy any file from the plugin’s views/frontend/ folder (tiered-pricing-table.php, tiered-pricing-blocks-style-3.php, summary-table.php and so on) to wp-content/themes/{your-theme}/tiered-pricing-table/ and edit it there. The filter tiered_pricing_table/template/location lets a plugin redirect a template. Layout templates receive $pricing_rule, $price_rules, $pricing_type, $minimum, $product, $product_id, $settings and $id.
The JavaScript reads the markup, so keep the root element’s id and data-* attributes (data-product-id, data-price-rules, data-minimum, data-regular-price, data-sale-price, data-price), one element per tier with data-tiered-quantity and data-tiered-price, and the base row with data-tiered-quantity="{minimum}" and the class tiered-pricing--active.
Inject content into layouts
↑ Back to top// Extra column in the table layouts
add_action( 'tiered_pricing_table/tiered_pricing/header_columns', fn() => print '<th>Total</th>' );
add_action( 'tiered_pricing_table/tiered_pricing/row_columns', function ( \TierPricingTable\PricingRule $rule, $qty ) {
$qty = $qty ?: max( 1, (int) $rule->getMinimum() ); // null = base row
$price = $rule->getTierPrice( $qty );
echo '<td>' . ( $price ? wc_price( $price * $qty ) : '-' ) . '</td>';
}, 10, 2 );
// Badge next to a tier in every layout
foreach ( [ 'table', 'horizontal-table', 'blocks', 'options', 'dropdown', 'plain-text' ] as $layout ) {
add_action( "tiered_pricing_table/{$layout}/label", function ( $rule, $qty ) {
if ( 50 === (int) $qty ) { echo '<span class="my-badge">Best value</span>'; }
}, 10, 2 );
}
// "100 or more" instead of "100+"
add_filter( 'tiered_pricing_table/tiered_pricing/last_tier_postfix', fn() => ' or more' );
JavaScript
↑ Back to topThe frontend script exposes document.__tieredPricing:
// Point the plugin at a custom quantity input or price container
document.__tieredPricing = document.__tieredPricing || { overrides: {} };
document.__tieredPricing.overrides.$getQuantityField = ( parentId ) => jQuery( '#my-qty-' + parentId );
document.__tieredPricing.overrides.$getPriceContainer = ( productId, parentId ) => jQuery( '#my-price-' + parentId );
// React to quantity changes
jQuery( document ).on( 'tiered_price_update', '.tpt__tiered-pricing', function ( event, data ) {
// data.quantity, data.price, data.price_excl_tax, data.productId, data.parentId, data.pricing.priceHtml
} );
// Set the quantity
jQuery( '.quantity-input-product-123' ).val( 10 ).trigger( 'change' );
// Initialise tables inserted after page load (quick view, AJAX)
document.querySelectorAll( '.tpt__tiered-pricing' ).forEach( function ( el ) {
if ( ! el.__tptInit ) { el.__tptInit = true; document.__tieredPricing.initFunction( el ); }
} );
The live product price is WooCommerce’s price HTML wrapped in <span class="tiered-pricing-dynamic-price-wrapper" data-price-type="dynamic|static|no-rules">. Return static from tiered_pricing_table/frontend/default_price_behaviour_type to keep WooCommerce’s price, or false from tiered_pricing_table/frontend/wrap_price to skip the wrapper. Styling hooks: .tpt__tiered-pricing, .tiered-pricing--active, .tiered-pricing-table, .tiered-pricing-block, .tiered-pricing-option, .tiered-pricing-dropdown, .tier-pricing-summary-table, .tiered-pricing-you-save, .tiered-pricing-tier-label.
Shortcodes, block and widget
↑ Back to top| Integration point | Details |
|---|---|
[tiered-pricing-table] | product_id (the current post by default) plus any settings key from Render the table yourself, for example [tiered-pricing-table product_id="123" display_type="blocks" title="Volume pricing"] |
[tiered_price_you_save] | product_id, color, template, consider_sale_price |
[tiered_pricing_wholesale_registration] | The wholesale application form. No attributes; the fields and messages come from Settings → Wholesale. |
Block tiered-pricing-table/tiered-pricing-block | displayType, title, activeTierColor, showDiscountColumn, column titles |
Elementor widget tiered-pricing-table | product_id, display_type, title, active_tier_color, column titles, options and unit settings |
REST API
↑ Back to topThe WooCommerce product and variation endpoints (/wp-json/wc/v3/products/{id} and /products/{id}/variations/{vid}) read and write these fields:
| Field | Value |
|---|---|
tiered_pricing_type | fixed or percentage |
tiered_pricing_fixed_rules, tiered_pricing_percentage_rules | { "<quantity>": <price or percent> } |
tiered_pricing_minimum_quantity | integer or null |
tiered_pricing_roles_data | { "<role>": { pricing_type, regular_price, sale_price, discount, discount_type, tiered_pricing_type, fixed_rules, percentage_rules, minimum, tax_status, tax_class } }. Writing replaces all role data; send tiers and minimum as fixed_tiered_pricing_rules, percentage_tiered_pricing_rules and minimum_order_quantity. |
tiered_pricing_product_settings | { "layout": "default" or a layout slug, "base_unit_name": { "singular", "plural" }, "default_variation": <id> } |
PUT /wp-json/wc/v3/products/123
{
"tiered_pricing_type": "percentage",
"tiered_pricing_percentage_rules": { "10": 5, "50": 12.5 },
"tiered_pricing_minimum_quantity": 5,
"tiered_pricing_roles_data": { "wholesale": { "pricing_type": "percentage", "discount": 15, "tiered_pricing_type": "fixed", "fixed_tiered_pricing_rules": { "10": 7.5 }, "minimum_order_quantity": 4 } }
}
Add your own field by extending TierPricingTable\Services\API\ProductFields\ProductField and appending the class with tiered_pricing_table/api/product_fields. The WooCommerce CSV importer and exporter use the columns tiered_price_fixed, tiered_price_percentage (quantity:value,…), tiered_price_type, tiered_price_minimum and {role}_tiered_price_* per role.
Extending the admin
↑ Back to top// A switch on the General tab (option tier_pricing_table_my_hide_for_guests, 'yes' | 'no')
add_filter( 'tiered_pricing_table/settings/general_settings', function ( array $fields ) {
$fields[] = [
'title' => 'Hide table for guests',
'id' => \TierPricingTable\Settings\Settings::SETTINGS_PREFIX . 'my_hide_for_guests',
'type' => \TierPricingTable\Settings\CustomOptions\TPTSwitchOption::FIELD_TYPE,
'default' => 'no',
];
return $fields;
} );
// An integration with its own toggle under Settings → Integrations (boot-time filter)
class My_Integration extends \TierPricingTable\Integrations\Plugins\PluginIntegrationAbstract {
public function getTitle(): string { return 'My Plugin'; }
public function getDescription(): string { return 'Adds option costs to tiered prices.'; }
public function getSlug(): string { return 'my-plugin'; }
public function run() { add_filter( 'tiered_pricing_table/cart/product_cart_price', [ $this, 'addOptionsCost' ], 20, 2 ); }
}
add_filter( 'tiered_pricing_table/integrations/plugins', fn( array $list ) => array_merge( $list, [ My_Integration::class ] ) );
- Product editor: render fields on
tiered_pricing_table/admin/pricing_tab_begin,…/admin/advance_product_optionsor…/admin/pricing_tab_end($postId), and inside the rules form on…/admin/tiered_pricing_rules_form/inputs; save onwoocommerce_process_product_meta. - Global rules: add a tab with
tiered_pricing_table/global_pricing/form_tabs, save in…/global_pricing/before_updating, read your meta in…/global_pricing/after_built_rule, add list columns with…/global_pricing/columns. - Custom table columns: extend
Addons\CustomColumns\Columns\AbstractCustomColumnand register it withtiered_pricing_table/custom_columns/available_columns_typesand…/custom_columns/columns_handlers. - Modules: extend
Addons\AbstractAddonand add it withtiered_pricing_table/addons/list; it gets a toggle under Advanced → Modules.
Debugging
↑ Back to top- Debug mode (Settings → Tiered Pricing → Advanced) prints the resolved rule under each cart item and inside the pricing table, including the log written with
PricingRule::logPricingModification(). - Cache: catalog price HTML is cached per product. Purge it under Advanced → Cache or with
ServiceContainer::getInstance()->getCache()->purge(). $GLOBALS['tpt_current_user_id']makes every price calculation run for that user, as the manual order recalculation does.
Hooks
↑ Back to topThe most used actions (A) and filters (F), grouped by topic. The first parameter of a filter is the value to return. Every name is prefixed with tiered_pricing_table/. The full list is in the plugin source: search for apply_filters( 'tiered_pricing_table/ and do_action( 'tiered_pricing_table/.
Price calculation
↑ Back to top| Hook | Type | Parameters | What it does |
|---|---|---|---|
price/pricing_rule | F | PricingRule $rule, int $productId | The effective rule for a product. Main extension point. |
price/price_by_rules | F | float|false $price, int $quantity, int $productId, string $context, string $place, PricingRule $rule | Final unit price for a quantity. |
price/round_price, price/round_precision | F | bool $round / int $decimals | Rounding of tier prices. |
discount/decimals | F | int $decimals, float $percent, ?WC_Product $product, ?PricingRule $rule | Decimals of displayed percentage discounts (default 2; return 0 for “10%”). |
services/regular_pricing/price | F | ?float $price, ?WC_Product $product, ?string $specific, $original | Adjusted regular or sale price for the current user. |
advanced_quantity/get_maximum, advanced_quantity/get_group_of | F | ?int $value, int $productId, string|false $role | Maximum quantity and quantity step. |
current_user | F | WP_User $user | The user prices are calculated for. |
Cart and checkout
↑ Back to top| Hook | Type | Parameters | What it does |
|---|---|---|---|
cart/need_price_recalculation | F | bool $recalculate, array $cartItem, WC_Cart $cart | Return false to leave an item’s price to WooCommerce. |
cart/product_cart_price | F | float|false $price, array $cartItem, string $key, int $totalQuantity | Unit price set on the cart item. |
cart/total_product_count | F | int $count, array $cartItem | Quantity used to pick the tier for an item. |
cart/total_savings | F | float $savings, WC_Cart $cart | Amount in the Total savings row. |
manual_created_orders/modify_item_price | F | bool $modify, WC_Order_Item_Product $item | Skip an item when recalculating a manual order. |
Product page and catalog
↑ Back to top| Hook | Type | Parameters | What it does |
|---|---|---|---|
should_render_pricing_table | F | bool $render, int $parentProductId, ?int $variationId, array $settings | Return false to suppress the widget. |
display_settings | F | array $settings, int $productId | Final render settings. |
frontend/enqueue_assets | F | bool $enqueue | Whether the frontend script and stylesheet load on the current page (default true). |
frontend/default_price_behaviour_type | F | string $type, WC_Product $product, string $priceHtml, string $context | dynamic, static or no-rules. |
product/default_variation | F | ?WC_Product_Variation $variation, int $productId | Variation shown by default. |
ajax_variations_threshold | F | int $threshold | Variations above this count load over AJAX (default 10). |
catalog_pricing/price_html | F | string $newHtml, string $defaultHtml, WC_Product $product | Catalog price HTML. |
pricing_layouts | F | array $layouts | Layout slug to label. |
Templates
↑ Back to top| Hook | Type | Parameters | What it does |
|---|---|---|---|
tiered_pricing/header_columns, tiered_pricing/row_columns | A | PricingRule $rule / PricingRule $rule, ?int $qty | Extra header cells and row cells in table layouts. |
tiered_pricing/before, tiered_pricing/after | A | PricingRule $rule | Around the table. |
{layout}/label | A | PricingRule $rule, int $qty, array $args | A badge next to a tier; {layout} is table, horizontal-table, blocks, options, dropdown or plain-text. |
tiered_pricing/last_tier_postfix | F | string $postfix, int $qty, PricingRule $rule, string $layout | Suffix of the last tier, + by default. |
template/location | F | string $path, string $template | Final path of any template. |
addons/tier_labels/label_html | F | string $html, TierLabel $label | Rendered tier label markup. |
Rules, wholesale and settings
↑ Back to top| Hook | Type | Parameters | What it does |
|---|---|---|---|
global_pricing/matched_pricing_rule | F | ?GlobalPricingRule $rule, WC_Product $product, WP_User $user | The global rule chosen for a product and user. |
global_pricing/after_built_rule | F | GlobalPricingRule $rule | Rule built from post meta; read your own meta here. |
global_pricing/before_updating | A | GlobalPricingRule $rule, int $ruleId | On save, before the rule is stored. |
role_based_pricing/after_adjusting_pricing_rule | A | PricingRule $rule, RoleBasedPricingRule $roleRule, int $productId | After a role rule was applied. |
wholesale/application_submitted | A | int $applicationId, WholesaleApplication $application | A wholesale application was submitted. |
wholesale/application_approved, wholesale/application_rejected | A | int $applicationId, WholesaleApplication $application, int $decidedBy | The application was decided; $decidedBy is 0 for automatic approval. |
closed_store/is_open_page | F | bool $open | Keep the current request open for visitors while the store is closed. |
settings/sections, settings/general_settings, settings/advanced_settings | F | array | Settings tabs and fields. |
rule_separator, rules_separator | F | string $separator | Separators used in CSV export (: inside a rule, , between rules). |
Support
↑ Back to top- User documentation: setup and every feature for store owners.
- Get help: missing a hook or an integration? Open a ticket from your WooCommerce.com account.