Tiered Pricing Table – Developer Documentation

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 top

Read the effective rule and price

↑ Back to top
use 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 top

tiered_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_rulesarray<int quantity, string price>, for example [5 => '9.50', 10 => '8.00']
_percentage_price_rulesarray<int quantity, string percent>
_tiered_price_rules_typefixed or percentage
_tiered_price_minimum_qtyinteger above 1
_tiered_pricing_maximum_quantity, _tiered_pricing_group_of_quantityinteger (maximum, step)
_tiered_pricing_template, _tiered_pricing_base_unit_namelayout 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_discountrole-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 top

Render the table yourself

↑ Back to top
ob_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 top

Copy 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 top

The 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 pointDetails
[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-blockdisplayType, title, activeTierColor, showDiscountColumn, column titles
Elementor widget tiered-pricing-tableproduct_id, display_type, title, active_tier_color, column titles, options and unit settings

REST API

↑ Back to top

The WooCommerce product and variation endpoints (/wp-json/wc/v3/products/{id} and /products/{id}/variations/{vid}) read and write these fields:

FieldValue
tiered_pricing_typefixed or percentage
tiered_pricing_fixed_rules, tiered_pricing_percentage_rules{ "<quantity>": <price or percent> }
tiered_pricing_minimum_quantityinteger 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_options or …/admin/pricing_tab_end ($postId), and inside the rules form on …/admin/tiered_pricing_rules_form/inputs; save on woocommerce_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\AbstractCustomColumn and register it with tiered_pricing_table/custom_columns/available_columns_types and …/custom_columns/columns_handlers.
  • Modules: extend Addons\AbstractAddon and add it with tiered_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 top

The 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
HookTypeParametersWhat it does
price/pricing_ruleFPricingRule $rule, int $productIdThe effective rule for a product. Main extension point.
price/price_by_rulesFfloat|false $price, int $quantity, int $productId, string $context, string $place, PricingRule $ruleFinal unit price for a quantity.
price/round_price, price/round_precisionFbool $round / int $decimalsRounding of tier prices.
discount/decimalsFint $decimals, float $percent, ?WC_Product $product, ?PricingRule $ruleDecimals of displayed percentage discounts (default 2; return 0 for “10%”).
services/regular_pricing/priceF?float $price, ?WC_Product $product, ?string $specific, $originalAdjusted regular or sale price for the current user.
advanced_quantity/get_maximum, advanced_quantity/get_group_ofF?int $value, int $productId, string|false $roleMaximum quantity and quantity step.
current_userFWP_User $userThe user prices are calculated for.

Cart and checkout

↑ Back to top
HookTypeParametersWhat it does
cart/need_price_recalculationFbool $recalculate, array $cartItem, WC_Cart $cartReturn false to leave an item’s price to WooCommerce.
cart/product_cart_priceFfloat|false $price, array $cartItem, string $key, int $totalQuantityUnit price set on the cart item.
cart/total_product_countFint $count, array $cartItemQuantity used to pick the tier for an item.
cart/total_savingsFfloat $savings, WC_Cart $cartAmount in the Total savings row.
manual_created_orders/modify_item_priceFbool $modify, WC_Order_Item_Product $itemSkip an item when recalculating a manual order.

Product page and catalog

↑ Back to top
HookTypeParametersWhat it does
should_render_pricing_tableFbool $render, int $parentProductId, ?int $variationId, array $settingsReturn false to suppress the widget.
display_settingsFarray $settings, int $productIdFinal render settings.
frontend/enqueue_assetsFbool $enqueueWhether the frontend script and stylesheet load on the current page (default true).
frontend/default_price_behaviour_typeFstring $type, WC_Product $product, string $priceHtml, string $contextdynamic, static or no-rules.
product/default_variationF?WC_Product_Variation $variation, int $productIdVariation shown by default.
ajax_variations_thresholdFint $thresholdVariations above this count load over AJAX (default 10).
catalog_pricing/price_htmlFstring $newHtml, string $defaultHtml, WC_Product $productCatalog price HTML.
pricing_layoutsFarray $layoutsLayout slug to label.

Templates

↑ Back to top
HookTypeParametersWhat it does
tiered_pricing/header_columns, tiered_pricing/row_columnsAPricingRule $rule / PricingRule $rule, ?int $qtyExtra header cells and row cells in table layouts.
tiered_pricing/before, tiered_pricing/afterAPricingRule $ruleAround the table.
{layout}/labelAPricingRule $rule, int $qty, array $argsA badge next to a tier; {layout} is table, horizontal-table, blocks, options, dropdown or plain-text.
tiered_pricing/last_tier_postfixFstring $postfix, int $qty, PricingRule $rule, string $layoutSuffix of the last tier, + by default.
template/locationFstring $path, string $templateFinal path of any template.
addons/tier_labels/label_htmlFstring $html, TierLabel $labelRendered tier label markup.

Rules, wholesale and settings

↑ Back to top
HookTypeParametersWhat it does
global_pricing/matched_pricing_ruleF?GlobalPricingRule $rule, WC_Product $product, WP_User $userThe global rule chosen for a product and user.
global_pricing/after_built_ruleFGlobalPricingRule $ruleRule built from post meta; read your own meta here.
global_pricing/before_updatingAGlobalPricingRule $rule, int $ruleIdOn save, before the rule is stored.
role_based_pricing/after_adjusting_pricing_ruleAPricingRule $rule, RoleBasedPricingRule $roleRule, int $productIdAfter a role rule was applied.
wholesale/application_submittedAint $applicationId, WholesaleApplication $applicationA wholesale application was submitted.
wholesale/application_approved, wholesale/application_rejectedAint $applicationId, WholesaleApplication $application, int $decidedByThe application was decided; $decidedBy is 0 for automatic approval.
closed_store/is_open_pageFbool $openKeep the current request open for visitors while the store is closed.
settings/sections, settings/general_settings, settings/advanced_settingsFarraySettings tabs and fields.
rule_separator, rules_separatorFstring $separatorSeparators 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.
Use of your personal data
We and our partners process your personal data (such as browsing data, IP Addresses, cookie information, and other unique identifiers) based on your consent and/or our legitimate interest to optimize our website, marketing activities, and your user experience.