CartThrob 9 documentation for ExpressionEngine commerce teams Visit CartThrob.com
CartThrob.com

Tax Plugins

This guide explains how to build a custom CartThrob tax plugin using the CartThrob 9.x plugin API. It assumes basic PHP knowledge: classes, methods, arrays, and returning values.

If you need ExpressionEngine add-on basics first, see the ExpressionEngine Docs and the Add-on Development Overview.

CartThrob ships with several working tax plugins. Start by copying one of those and changing it, rather than writing everything from scratch.

Good starter examples:

  • Cartthrob_tax_standard.php for location-based rates plus a default percent
  • Cartthrob_tax_default.php for a settings matrix of location rates
  • Cartthrob_tax_by_class.php for advanced per-item tax classes

Unlike shipping plugins, tax plugins return numeric amounts (float / int), not Money objects.

Start Here

A tax plugin is a PHP class that:

  1. Lives in CartThrob’s tax plugins folder
  2. Extends CartThrob\Plugins\Tax\TaxPlugin
  3. Declares a $title
  4. Implements get_tax($price) and returns the tax amount for that price

At a minimum, CartThrob needs that get_tax($price) method so it can calculate tax on items, and sometimes on shipping or discounts.

In practice you should also add:

  1. tax_rate() so CartThrob can display and reuse the current rate
  2. tax_name() for the tax label
  3. tax_shipping() so CartThrob knows whether shipping is taxable

File Location and Naming

Store tax plugins here:

system/user/addons/cartthrob/cartthrob/plugins/tax/

Rules:

  1. The filename and classname must match.
  2. The classname must begin with Cartthrob_tax.
  3. The file must begin with Cartthrob_tax.
  4. Do not start your own PHP session. CartThrob already manages session and cart state.

Example:

Filename: Cartthrob_tax_my_rate.php
Class:    Cartthrob_tax_my_rate

Minimum Working Plugin

This is the smallest useful tax plugin: one percent rate from a settings field.

<?php

use CartThrob\Math\Number;
use CartThrob\Plugins\Tax\TaxPlugin;

if (!defined('CARTTHROB_PATH')) {
    Cartthrob_core::core_error('No direct script access allowed');
}

class Cartthrob_tax_my_rate extends TaxPlugin
{
    public $title = 'My Flat Tax';

    public $settings = [
        [
            'name' => 'Tax percent',
            'short_name' => 'default_tax',
            'type' => 'text',
            'default' => '8',
            'note' => 'Enter 8 for 8%, not 0.08.',
        ],
    ];

    protected array $rules = [
        'default_tax' => 'required|numeric',
    ];

    public function get_tax($price)
    {
        return $price * $this->tax_rate();
    }

    public function tax_rate()
    {
        return abs(Number::sanitize($this->plugin_settings('default_tax')) / 100);
    }

    public function tax_name()
    {
        return 'Sales Tax';
    }

    public function tax_shipping()
    {
        return false;
    }
}

What this does:

  • Shows a Tax percent field in CartThrob tax settings
  • Reads that setting with plugin_settings('default_tax')
  • Converts 8 into a rate of 0.08
  • Returns $price * 0.08 as the tax amount

After adding the file, select the plugin in CartThrob’s tax settings and save.

Plugin Information

These public properties describe the plugin in the control panel.

public $title = 'My Flat Tax';
public $note = 'Optional note shown with the plugin settings.';
public $overview = 'Optional longer overview text.';
  • $title is required.
  • $note and $overview are optional.
  • You can use plain text, or a language key that CartThrob will look up.

Language Files

Language keys keep settings labels cleaner and make text easier to reuse.

Built-in CartThrob tax plugins store many labels in:

system/user/addons/cartthrob/language/english/cartthrob_lang.php

You can also place a plugin-specific language file next to the plugin, named like:

cartthrob_tax_my_rate_lang.php

Example language entries:

<?php

$lang = [
    'my_flat_tax_title' => 'My Flat Tax',
    'my_flat_tax_note' => 'Charges one percent rate for the whole order.',
    'default_tax' => 'Tax Percent',
    'default_tax_note' => 'Enter 8 for 8%, not 0.08.',
];

Then reference those keys in the plugin:

public $title = 'my_flat_tax_title';
public $note = 'my_flat_tax_note';

public $settings = [
    [
        'name' => 'default_tax',
        'note' => 'default_tax_note',
        'short_name' => 'default_tax',
        'type' => 'text',
        'default' => '8',
    ],
];

If CartThrob finds the language key, it shows the translated text. If not, it shows the key itself.

A separate language file is optional for a basic plugin. Plain-text $title and setting labels work fine while you are learning.

Plugin Settings

Each plugin can define its own settings array. Every setting needs at least:

  • name
  • type
  • short_name

Optional keys:

  • default
  • options
  • note
  • settings for matrix rows
  • attributes for extra HTML attributes on the field

Setting Keys

name

Required. Label shown in the control panel. Use plain text or a language key.

type

Required. Supported types:

  1. text
  2. textarea
  3. radio
  4. select
  5. checkbox
  6. matrix
  7. header

text, textarea, and header store string-like values. The others usually store arrays or selected option values.

Unchecked checkboxes typically return no value.

short_name

Required. The key you use in PHP with plugin_settings('short_name').

Use letters, numbers, underscores, and hyphens only.

default

Optional. Default value for the setting.

Matrix fields can have defaults on individual columns. You generally cannot preset a full matrix row with one top-level default.

options

Usually required for radio, select, and checkbox.

'options' => [
    'yes' => 'Yes',
    'no' => 'No',
],

The array key is the stored value. The array value is the label shown to the admin.

note

Optional help text shown with the setting.

settings

Required for matrix settings. Contains the column definitions for each matrix row.

CartThrob tax plugins do not support a matrix inside another matrix.

Settings Example

public $settings = [
    [
        'name' => 'Default tax',
        'note' => 'Used when no location match is found. Enter 8 for 8%.',
        'short_name' => 'default_tax',
        'type' => 'text',
        'default' => '8',
    ],
    [
        'name' => 'Tax by location',
        'short_name' => 'tax_settings',
        'type' => 'matrix',
        'settings' => [
            [
                'name' => 'Name',
                'short_name' => 'name',
                'type' => 'text',
            ],
            [
                'name' => 'Tax percent',
                'short_name' => 'rate',
                'type' => 'text',
            ],
            [
                'name' => 'State / Country',
                'short_name' => 'state',
                'type' => 'select',
                'attributes' => [
                    'class' => 'states_and_countries',
                ],
                'options' => [],
            ],
            [
                'name' => 'Zip / Region',
                'short_name' => 'zip',
                'type' => 'text',
            ],
            [
                'name' => 'Tax shipping',
                'short_name' => 'tax_shipping',
                'type' => 'checkbox',
            ],
        ],
    ],
];

Read settings in your plugin like this:

$default_tax = $this->plugin_settings('default_tax');
$tax_settings = $this->plugin_settings('tax_settings', []);

Required and Expected Methods

get_tax($price)

Required for every tax plugin. This is the only abstract method on TaxPlugin.

Parameters:

  • $price (required): the amount being taxed
  • A second argument is often passed by CartThrob, even though the abstract signature only shows $price:
    • an item object for item tax
    • 'shipping' for shipping tax
    • 'discount' for discount tax

Returns: number (float or int) — the tax amount for that price, not the price plus tax

public function get_tax($price)
{
    return $price * $this->tax_rate();
}

Important:

  • Return the tax portion only. If $price is 100 and the rate is 8%, return 8, not 108.
  • Return a number. Do not return a Money object.
  • Settings are usually entered as percents like 8. Convert them with / 100 before multiplying.
  • Use CartThrob\Math\Number::sanitize(...) to clean admin input before math.

If you need the optional second argument:

public function get_tax($price, $context = null)
{
    // $context may be an item object, 'shipping', or 'discount'
    return $price * $this->tax_rate();
}

tax_rate()

Expected. CartThrob calls this for display and rate lookups.

Parameters: none
Returns: float rate as a decimal, such as 0.08 for 8%

public function tax_rate()
{
    return abs(Number::sanitize($this->plugin_settings('default_tax')) / 100);
}

tax_name()

Expected. Used for tax labels in the cart and templates.

Parameters: none
Returns: string

public function tax_name()
{
    return 'Sales Tax';
}

tax_shipping()

Expected. CartThrob checks this before taxing shipping.

Parameters: none
Returns: boolean

public function tax_shipping()
{
    return false;
}

If this returns true, CartThrob calls get_tax($shipping_amount, 'shipping').

initialize()

Optional.

Use this when you want to prepare plugin-wide values once.

Parameters: $params = [], $defaults = []
Returns: usually $this or void

protected $default_rate = 0;

public function initialize($params = [], $defaults = [])
{
    if ($this->plugin_settings('default_tax')) {
        $this->default_rate = Number::sanitize($this->plugin_settings('default_tax')) / 100;
    }

    return $this;
}

tax_data()

Optional helper pattern used by the included location plugins. Not required by the base class.

Use it when you want one place to resolve the current tax row (name, percent, shipping taxable flag) and then read keys from it:

$this->tax_data('percent');
$this->tax_data('tax_name');
$this->tax_data('shipping_is_taxable');

See Cartthrob_tax_standard.php and Cartthrob_tax_default.php for full examples.

How CartThrob Calls Your Plugin

Understanding the call sites helps you write the right methods.

Item tax

For each taxable item, CartThrob roughly does:

$tax = $plugin->get_tax($cost, $item);

Your plugin should return the tax for that item cost.

Shipping tax

If your plugin’s tax_shipping() returns true:

$amount = $plugin->get_tax($shipping, 'shipping');

Discount tax

When discounts are taxable, CartThrob may call:

$amount = $plugin->get_tax($discount, 'discount');

Rate and name display

Store and template variables use:

$plugin->tax_rate();
$plugin->tax_name();

So even though only get_tax($price) is abstract, a useful plugin almost always implements the companion methods too.

Useful Cart Helpers

These are the helpers you will use most often inside tax plugins.

Taxable cart total

$this->core->cart->taxable_subtotal();

Returns the total of taxable items in the cart.

Customer address data

$this->core->cart->customer_info();
$this->core->cart->customer_info('zip');
$this->core->cart->customer_info('state');
$this->core->cart->customer_info('country_code');
$this->core->cart->customer_info('region');

If CartThrob is configured to tax based on the shipping address:

$prefix = $this->core->store->config('tax_use_shipping_address') ? 'shipping_' : '';

$zip = $this->core->cart->customer_info($prefix . 'zip');
$state = $this->core->cart->customer_info($prefix . 'state');
$country = $this->core->cart->customer_info($prefix . 'country_code');

Plugin settings

$this->plugin_settings('default_tax');
$this->plugin_settings('tax_settings', []);

The second argument is the fallback if the setting is missing.

Tax rates from the database

If rates are stored in CartThrob’s tax table rather than only in plugin settings:

$locations = [
    'zip' => $this->core->cart->customer_info($prefix . 'zip'),
    'special' => $this->core->cart->customer_info($prefix . 'region'),
    'state' => $this->core->cart->customer_info($prefix . 'state'),
    'country' => $this->core->cart->customer_info($prefix . 'country_code'),
];

$tax_settings = $this->core->get_tax_rates($locations);

Cartthrob_tax_standard.php is the best reference for this pattern.

Cart items

foreach ($this->core->cart->items() as $item) {
    $item->quantity();
    $item->price();
    $item->product_id();
}

Rounding and number cleanup

use CartThrob\Math\Number;

$clean = Number::sanitize($this->plugin_settings('default_tax'));
$rounded = $this->core->round('11.203'); // 11.20

Prefer Number::sanitize() for settings input. Use $this->core->round() when you need money-style rounding inside the plugin.

ExpressionEngine helpers are available through ee(), for example:

ee()->lang->line('some_key');

Do not use $this->EE =& get_instance(); in new plugins.

Location-Based Rates

Most real tax plugins need more than one flat percent. The usual pattern is:

  1. Read the customer location from billing or shipping fields
  2. Find a matching rate row
  3. Fall back to a default percent
  4. Expose that row through tax_rate(), tax_name(), and tax_shipping()

Which included plugin to copy

Plugin Best for
Cartthrob_tax_standard.php Default percent + tax table / location lookup
Cartthrob_tax_default.php Admin-managed matrix of location rates in plugin settings
Cartthrob_tax_default_plus_quebec.php Special regional handling on top of the default pattern
Cartthrob_tax_by_class.php Different rates by product/tax class

For a first custom plugin, copy standard if you want a default percent and location table. Copy default if you want admins to manage rows in a settings matrix.

Rate format reminder

Admins usually enter 8 meaning 8 percent. Your code should convert that:

return abs(Number::sanitize($percent) / 100); // 0.08

Then calculate tax as:

return $price * $this->tax_rate();

Validation Rules

Many current tax plugins validate settings with a $rules array:

protected array $rules = [
    'default_tax' => 'required|numeric',
];

This helps catch empty or non-numeric admin settings before they cause bad tax calculations.

Examples

Example 1: Flat percent rate

A beginner-friendly plugin with one setting.

<?php

use CartThrob\Math\Number;
use CartThrob\Plugins\Tax\TaxPlugin;

if (!defined('CARTTHROB_PATH')) {
    Cartthrob_core::core_error('No direct script access allowed');
}

class Cartthrob_tax_my_rate extends TaxPlugin
{
    public $title = 'My Flat Tax';

    public $settings = [
        [
            'name' => 'Tax percent',
            'short_name' => 'default_tax',
            'type' => 'text',
            'default' => '8',
        ],
    ];

    protected array $rules = [
        'default_tax' => 'required|numeric',
    ];

    public function get_tax($price)
    {
        return $price * $this->tax_rate();
    }

    public function tax_rate()
    {
        return abs(Number::sanitize($this->plugin_settings('default_tax')) / 100);
    }

    public function tax_name()
    {
        return 'Sales Tax';
    }

    public function tax_shipping()
    {
        return true;
    }
}

Example 2: Same calculation pattern as Standard

Cartthrob_tax_standard.php keeps the math tiny and puts the hard work in tax_data():

public function get_tax($price)
{
    return $price * $this->tax_rate();
}

public function tax_rate()
{
    return abs(Number::sanitize($this->tax_data('percent')) / 100);
}

public function tax_name()
{
    return $this->tax_data('tax_name');
}

public function tax_shipping()
{
    return (bool) $this->tax_data('shipping_is_taxable');
}

Copy that file when you need location matching and a fallback default percent.

Example 3: Settings matrix of locations

Use Cartthrob_tax_default.php when admins should manage name / percent / state / zip / tax-shipping rows in the plugin settings matrix.

That plugin:

  1. Reads plugin_settings('tax_settings')
  2. Matches the customer location
  3. Maps matrix fields onto percent, tax_name, and shipping_is_taxable
  4. Uses the same $price * tax_rate() calculation

Checklist

Before testing your plugin:

  1. File is in system/user/addons/cartthrob/cartthrob/plugins/tax/
  2. Filename and classname match
  3. Classname starts with Cartthrob_tax
  4. Class extends CartThrob\Plugins\Tax\TaxPlugin
  5. get_tax($price) exists and returns a number
  6. tax_rate(), tax_name(), and tax_shipping() are implemented
  7. Percent settings are converted with / 100 before multiplying
  8. Settings use valid name, type, and short_name keys
  9. Plugin is selected and saved in CartThrob tax settings
  10. Test with a taxable item, a non-taxable item if you support that, and shipping tax on/off

If something does not appear in the control panel, check the classname prefix and file location first. If tax calculates as zero or looks like a full price instead of a tax amount, check that get_tax() returns $price * rate and that the rate is a decimal like 0.08.

ExpressionEngine References