TNT — Simple Product Add-Ons

Description

No bloat, no upsells, no page builder — just optional add-ons that follow your product through checkout.

Product Add-Ons lets a store admin attach optional extras to any simple or variable product — for example an Office Chair at $200 with an Extended Warranty (+$30), a Headrest (+$45), a Footrest (+$25) and Premium Assembly (+$50). Shoppers tick the add-ons they want on the product page; the total updates instantly with no page reload, and the selected add-ons and their prices ride along with that product through the cart, checkout, the resulting order, order emails and any invoice built from standard WooCommerce order data.

This plugin is intentionally simple: no page builder, no complex conditional logic, no separate add-on inventory. Just add-ons, priced, optionally imaged, shown in one of five ready-made designs.

Features

  • Up to 5 add-ons per product, managed from a repeater on the product’s own “Add-Ons” tab
  • Drag-and-drop reordering, per-add-on enable/disable, name/description/price/image fields
  • Global default position and design, with an optional per-product override
  • Five frontend designs: Classic List, Grid Cards, Compact List, Toggle Switches, and Image Cards
  • Live price preview (Product Price / Add-Ons / Total) with zero page reloads
  • Add-on price is only ever added when selected — the product’s own base price is never modified
  • Selections are re-validated and re-priced on the server at Add to Cart — the browser is never trusted for the actual charge
  • Different add-on selections never merge into the same cart line
  • Add-ons appear on the cart and checkout review tables, and are saved to the order as line item meta — visible in wp-admin (classic and HPOS), order emails, the customer’s account order view, and most invoice/PDF plugins that read standard order item meta
  • Works with WooCommerce’s High-Performance Order Storage (HPOS)
  • Graceful when WooCommerce is inactive — the plugin stays dormant instead of causing a fatal error
  • Optional data cleanup on uninstall (off by default — your data is safe unless you opt in)

Known limitations (by design, to keep this simple)

  • The Cart/Checkout blocks (as opposed to the classic shortcode-based Cart/Checkout pages) are not yet officially declared compatible; the underlying data hooks used here are widely supported by WooCommerce Blocks’ backward-compatibility layer, but this hasn’t been exhaustively tested against every block-based theme.
  • Add-ons are configured per simple/variable product (variable add-ons apply to the whole product, not per-variation).
  • Add-ons don’t carry their own separate stock/inventory tracking.
  • The Product CSV importer/exporter and the REST API do not currently include add-on fields.
  • “Order Again” (re-ordering a past order from My Account) re-adds the base product but does not currently re-select its add-ons.

Installation

  1. Upload the plugin folder to /wp-content/plugins/, or install the zip via Plugins Add New Upload Plugin.
  2. Activate the plugin. WooCommerce must already be active — if it isn’t, the plugin stays installed but inactive and shows a notice until WooCommerce is available.
  3. Go to WooCommerce Settings Product Add-Ons to set the default position, design, and what’s shown (image/price/description).
  4. Edit a product, open its Add-Ons tab, tick “Enable Add-Ons”, and add up to 5 add-ons.

FAQ

Does this change my product’s actual price?

No. The product’s stored price is never modified. Add-on prices are added only to the line item shown to the customer, and only for the add-ons they actually select.

What happens to an order if I later edit or delete an add-on?

Orders already placed keep the name and price that was charged at the time — editing an add-on afterwards never changes past orders. If you edit an add-on’s price while it’s still sitting unpurchased in someone’s cart, their cart total will reflect the corrected price the next time it recalculates (the same way a normal product price correction would). If you delete an add-on entirely, any cart that already had it selected keeps the price it had captured at the time it was added.

How many add-ons can a product have?

Up to 5. This is a fixed limit in the current version.

Why don’t I see the Google Pay / Apple Pay / Link button on a product with add-ons?

That’s intentional. Those “express checkout” buttons charge whatever price they’re shown before the customer ever sees the add-on choices, so letting one through would silently check out without any add-ons the customer might have wanted. Whenever a product has active add-ons, the plugin hides that button so customers go through the normal Add to Cart flow instead, where the price is always correct. The button is unaffected on products with no add-ons, and it still appears normally on the Cart and Checkout pages (by then the price is already correct).

Does the add-on price include or exclude tax?

Add-on prices are added directly into the product’s line item, so they automatically follow that product’s tax class and your store’s normal tax display settings — you don’t need to configure tax separately for add-ons.

Reviews

There are no reviews for this plugin.

Contributors & Developers

“TNT — Simple Product Add-Ons” is open source software. The following people have contributed to this plugin.

Contributors

Changelog

1.0.10

  • Change: the WordPress.org review team has confirmed and reserved the new slug tnt-extras-for-woocommerce. Text Domain header and every translation string now updated to match, for real this time — this is the final value going forward.
  • Add: Requires Plugins: woocommerce header, flagged as useful by the review team’s automated dependency check since the plugin extends WooCommerce; declares the dependency to WordPress core (6.5+) so it can prompt/require WooCommerce to be active before this plugin activates.

1.0.9

  • Fix: reverted the Text Domain header and every translation string from “tnt-extras-for-woocommerce” back to “product-add-ons”. WordPress.org’s Plugin Check derives its “expected” text domain from the slug currently reserved for this submission, which is still product-add-ons — the review team has not yet processed the tnt-extras-for-woocommerce rename requested in 1.0.8, so shipping that value now fails every text-domain check. The plugin’s display name stays “TNT Extras for WooCommerce” (that satisfies the reviewer’s naming objection); only the internal Text Domain reverts, and only until the new slug is confirmed, at which point it will be updated to match in a follow-up release.
  • Fix: removed a stray .DS_Store file from the plugin folder (“Hidden files are not permitted”).

1.0.8

  • Change: renamed the plugin from “Product Add-Ons” to “TNT Extras for WooCommerce” (slug: tnt-extras-for-woocommerce) at the request of the WordPress.org review team — the previous name was judged too close to WooCommerce’s own “Product Add-Ons” extension. Text Domain and every translation string updated to match; user-facing labels (“Product Add-Ons” settings tab, admin notices) renamed to “TNT Extras”.

1.0.7

  • Fix: WordPress.DB.SlowDBQuery.slow_db_query_meta_key warnings (4x) on the $wpdb->delete() cleanup calls in the uninstall routine — added to the existing phpcs:disable/phpcs:enable justification block alongside the direct-query codes already suppressed there.

1.0.6

  • Fix: Text Domain header and every translation string now consistently use “product-add-ons”, matching the plugin’s WordPress.org slug (the 1.0.5 changelog entry below describing the opposite move was itself a regression — this corrects it for good).
  • Fix: all internal class names (previously the generic AP_ prefix, e.g. AP_Settings) renamed to a unique, plugin-specific prefix (Product_Add_Ons_*) together with the plugin’s global helper functions and constants, resolving “non-prefixed” naming warnings from the WordPress.org Plugin Check.
  • Fix: added the missing nonce-verification justification comments on two $_POST reads in the add-to-cart handler (the add-to-cart form has no dedicated nonce in core WooCommerce; selections are always re-validated server-side against the product’s own configuration regardless).
  • Fix: $product global reassignment in the frontend renderer now carries the same justified phpcs:ignore as its declaration (WooCommerce’s own global, not one this plugin defines).

1.0.5

  • Fix: resolved a Plugin Check text-domain mismatch — the “Text Domain” header and every translation string now consistently use “addon-products”, matching the plugin’s actual folder/slug (this reverses the 1.0.4 change, which had moved the wrong direction).
  • Fix: a translatable string with two “%d” placeholders is now correctly numbered (“%1$d”/”%2$d”) so translators can reorder them.
  • Fix: corrected inline phpcs:ignore placement for two $_POST reads in the cart add-to-cart handling so the documented justification sits on the exact flagged line.
  • Fix: added justified phpcs:ignore annotations for WooCommerce’s own $product global in the frontend renderer, and renamed a local $settings variable in the uninstall routine to a prefixed name — both were flagged as unprefixed globals.
  • Fix: shortened the plugin’s short description so it’s no longer truncated on WordPress.org.

1.0.4

  • Fix: WordPress.org’s automated Plugin Check failed the submission on three points, now all resolved — “Tested up to” bumped to the current WordPress version; the Text Domain header (and every translation string in the code) changed from “addon-products” to “product-add-ons” to match the plugin’s assigned slug; the “Domain Path” header was removed since no bundled translation files are shipped (WordPress.org loads translations for hosted plugins automatically, without needing this).
  • Housekeeping: removed the load_plugin_textdomain() call, which is unnecessary for WordPress.org-hosted plugins and can trigger a “translation loading was triggered too early” notice on WordPress 6.7+; tidied the uninstall routine’s database cleanup to use $wpdb->delete() with clear justification comments instead of raw SQL.

1.0.3

  • Fix: plugin upload was rejected with “Your plugin and author URIs are the same” — the header had identical Plugin URI and Author URI values. Removed the Author URI line (Plugin URI stays topnewthings.com).

1.0.2

  • Fix: Google Pay / Apple Pay / Link “express checkout” buttons on the product page were charging only the base product price, skipping any selected add-ons, because those buttons bypass the normal Add to Cart flow entirely. These buttons are now hidden on any product page where add-ons are active, so customers are guided through the normal flow where the price is always correct; Add to Cart, Cart and Checkout are unaffected.

1.0.1

  • Fix: activation could trigger a fatal error (“Class AP_Settings not found”) because the activation hook loaded the install class without first loading the settings class it depends on. Activation now loads both, in the right order.

1.0.0

  • Initial release: repeater-based per-product add-ons (max 5), 5 frontend designs, live price preview, full Cart Checkout Order Invoice persistence, HPOS compatibility.