Sales Tax at the Speed of Commerce
Adobe Commerce 2.4.9 Compatible ✅
This extension (Version 1.3.1) is tested and verified against Adobe Commerce
2.4.7-p10, 2.4.8-p5 and 2.4.9 — the unit, integration and E2E suites run on all
three on every change.
Supported Versions:
- Adobe Commerce 2.4.x
- Adobe Commerce 2.4.7-p10, 2.4.8-p5, 2.4.9 ✅
- Magento Open Source 2.4.x
TaxCloud is the Internet's most affordable sales tax compliance service with a state-paid option from 25 member states - because the states paying us lets us give back to you. Working closely with states allows TaxCloud to offer industry leading tax data, automated filing options, the lowest prices and best value for your business's transactions in the USA.
Calculate - We determine the applicable sales tax rate based on product and service taxability.
Collect - Sales tax is collected at the time of transaction on your site when your customers checkout.
File - We can file returns and remit your collected sales tax proceeds to the appropriate state and local jurisdictions.
Audit-Ready - We can support you with any state-issued notices or audit inquiries.
Create your TaxCloud account and select either Core or Enhanced services. New accounts are granted a free testing period to make sure your Magento store integrates properly.
Now that you have created your TaxCloud account, there are a few important matters to take care of. Please log in to your TaxCloud account and complete all of the items below.
- Add your website. While logged in, go to Settings → Stores. If your store is not listed on this page, you will need to add it by clicking Add Store and following the on-screen prompt.
- Add business locations. If your business has a physical presence in the United States, it is imperative that you register your business locations, including stores, warehouses, and distribution facilities, with TaxCloud. To do so, navigate to Settings → Locations and click Add Location.
- Select your tax states. Navigate to Settings → Tax States. You will be presented with a map of the United States. Click the map to highlight those states where you would like to collect sales tax.
Download the extension as a ZIP file from the releases page. Unzip the archive and place at app/code/Taxcloud/Magento2 on your webserver. Then run the following commands from your Magento 2 root directory.
bin/magento setup:upgrade
bin/magento setup:di:compile
{
"repositories": [
{
"url": "https://github.com/FedTax/Magento2.git",
"type": "git"
}
],
"require": {
"taxcloud/magento2": "*"
}
}
Installing the module adds three EAV attributes via two data patches:
taxcloud_ticon products (the TaxCloud TIC) andtaxcloud_certon customers (the exemption certificate ID), both fromInstallTaxcloudData;taxcloud_ticon categories (the inherited TIC), fromAddCategoryTicAttribute.
Both patches are revertable (they implement PatchRevertableInterface), so
these attributes are cleaned up automatically when the module is uninstalled.
Magento invokes each patch's revert() from the uninstall-data flow:
bin/magento module:uninstall Taxcloud_Magento2
(module:uninstall applies to Composer-installed modules; it runs the data
patch's revert() before removing the module's files.)
Reverting drops all three attributes (and, because EAV value storage cascades on
the attribute, any TIC/certificate values stored against products, categories
and customers).
Re-installing and running bin/magento setup:upgrade re-applies the patch and
recreates the attribute definitions — but not the previously stored values — so
revert is a destructive, one-way cleanup. It only runs on uninstall.
- Fork the repository and create a feature branch
- Make your changes and test them in a Magento installation
- Submit a pull request
Note: Tests run automatically on pull requests via GitHub Actions.
Unit tests run with the PHPUnit and Magento classes of the Magento
installation this module lives inside — the module has no vendor/ of its
own. With the module checked out at app/code/Taxcloud/Magento2/:
make test-unit # or: make test
make test-unit MAGENTO_ROOT=/path/to/magento # explicit install rootIn CI, unit tests run on every push/PR against a matrix of Magento
versions (see .github/workflows/test.yml).
Integration tests live in their own pipeline — they boot the full Magento application against a real database and run via PHPUnit. They are expensive and run on demand (or on release tags), not on every push. See docs/INTEGRATION_TESTS.md for the 5-minute local setup and the matrix of supported editions/versions.
This module is linted against the Magento2 ruleset from
magento/magento-coding-standard,
pinned to ^40. It replaces the PSR-2 gate this repo used to run — PSR-2 was
deprecated in 2019 — and adds the Magento-specific checks that a generic PHP
standard has no opinion on: short array syntax, constant visibility, discouraged
functions, proxies/interceptors requested in constructors, legacy Mage
entities, and Magento's PHPDoc conventions.
The ruleset, the scanned paths and the deferrals all live in
[phpcs.xml.dist](phpcs.xml.dist), which both CI and make lint run with no
arguments, so the two cannot drift:
make lint # run the standard (uses the Magento install's phpcs)
make lint-fix # auto-fix what phpcbf can (short arrays, whitespace, imports)Two things are worth knowing before changing this setup:
- The
Magento2standard reports findings as warnings, not errors. Nothing it reports on this module is an error, so the gate deliberately fails on any finding, viaphpcs's exit code. The previous job grepped stdout for"ERROR", which against this ruleset could never have failed — a green build would have meant nothing. Magento2is not a superset of PSR-12. It cherry-picks PSR-1/PSR-2 sniffs and exactly one PSR-12 sniff (PSR12.Properties.ConstantVisibility). It was adopted on its own here for the Magento-specific coverage; that choice trades away PSR-12's stricter whitespace, import and control-structure sniffs. Adding<rule ref="PSR12"/>alongside it is viable but not free — the two standards configure some shared Squiz sniffs with conflicting properties (ignoreBlankLines,equalsSpacing), so whichever is included last silently wins.
Some sniffs are deferred rather than satisfied, in the same tactical spirit as
[phpstan-baseline.neon](phpstan-baseline.neon), to burn down during the REST
migration. Each carries its rationale inline in phpcs.xml.dist; the largest is
Magento2.Annotation (PHPDoc formatting, redundant with the native PHP 8.2 types
already on the signatures). Deferrals are scoped to a specific file wherever the
findings are confined to one, so new code is still held to the rule.
Static analysis runs separately, via PHPStan at level 5 — see
[phpstan.neon](phpstan.neon) and make phpstan.
After installing the module, there are a few important configuration options you must set in the Magento 2 admin dashboard.
Navigate to Stores → Configuration and then Sales → Shipping Settings.
It is very important to enter the full shipping origin address for accurate sales tax calculation. Ensure that you enter the full Zip+4 code.
Navigate to Stores → Configuration and then Sales → Tax.
-
Enabled - Select
Enabledin order to enable the TaxCloud module. -
Logging - Writes TaxCloud activity to
var/log/taxcloud.log. Three modes:Enable - Basic- lifecycle events, decisions and errors: which observers ran, which API operations were called, cache hits, early exits (invalid ZIP, non-US address, …) and every warning/error. Enough to confirm the extension works and events trigger, at low log volume. This is the mode pre-existing installs land on after upgrading from the oldEnabledvalue.Enable - Advanced- everything in Basic, plus debug-level detail for support and troubleshooting: the full API request/response payloads, the raw SOAP request/response XML and HTTP headers as sent over the wire, per-call timing, and cache/context detail. Make sure to set up log rotation if you keep this mode enabled in production!Disable- no TaxCloud logging.
In every mode your TaxCloud
apiLoginIDandapiKeyare redacted: the field names still appear (in both the params dumps and the raw XML), but their values are replaced with***REDACTED***so credentials cannot be harvested from logs, backups, or log shippers (Datadog, Splunk, SIEM exports, etc.). The log file location is configurable — see Changing the log file location. -
Verify Address - Select
Enabledto turn on TaxCloud's address verification API calls. You may want to disable this if you have another module that validates shipping addresses. -
API ID - Enter your API ID from your TaxCloud account.
-
API Key - Enter your API Key from your TaxCloud account.
-
Guest Customer ID - Enter the customer ID to send to TaxCloud during a guest checkout. Unless there is a special reason to change this for your store, use the default value of
-1. -
Default TIC - Enter the Taxability Information Code you would like to use for products where no TIC has been specified on the product or on any of its categories. See How a TIC is chosen.
-
Shipping TIC - Enter the Taxability Information Code you would like to use for shipping costs. Use
11010if you charge only postage, and11000for shipping & handling. -
Cache Lifetime - Enter the amount of time in seconds you would like to cache the sales tax lookup and verify address API calls. The default value is
86400(24 hours), or enter0to disable caching for development purposes. -
WSDL Endpoint - Advanced. The TaxCloud SOAP endpoint the module calls. Defaults to the production endpoint
https://api.taxcloud.net/1.0/TaxCloud.asmx?wsdl; leave it alone unless TaxCloud support has directed you to a sandbox or staging endpoint. Clearing the field restores the production default. Because the setting is store-scoped, you can point a staging store at a sandbox endpoint while production keeps using the default. Note that tax lookups are cached by request payload, not by endpoint — if you switch endpoints while reusing the same API credentials, clear the TaxCloud cache type so results from the previous endpoint are not reused (see Clearing the TaxCloud cache). -
Only do tax calculations without further Taxcloud integration - Select
Enabledto keep tax calculation running while sending nothing back to TaxCloud that records or reverses a sale. Lookup, address verification and exempt-certificate validation still happen, so the storefront charges the right tax;AuthorizedWithCapture(order capture),Returned(credit memos and canceled unpaid orders) andOrderDetailsare all skipped. Useful for merchants that push orders to other systems (i.e. Quickbooks) that are themselves connected to TaxCloud, where a second push from Magento would report the same sale twice. Because the setting is store-scoped, you can run one store view calculation-only while another keeps the full integration. Defaults toDisabled. When enabled, Capture in TaxCloud is hidden — there is no capture left for it to schedule. -
Capture in TaxCloud - Choose when the order is sent to TaxCloud: On order creation (at checkout; default), On payment (when an invoice is paid; recommended to avoid canceled orders reaching TaxCloud), or On shipment (when a shipment is created). For online payment methods, "on creation" and "on payment" often fire together; the choice matters for offline payment or when you only want to report tax on fulfilled orders.
If applicable, you should set a Taxability Information Code per product. Navigate to Catalog → Products.
From the main product grid, you will be able to see and sort by each product's TIC. In order to edit a TIC, click on the Edit link for a specific product.
There are two main fields you should properly set per product.
- Tax Class - In most cases, you should select
Taxable Goodsfor this, even if the product you are selling may be tax exempt under certain, or all, circumstances. If you selectNone, then this product will never be sent to TaxCloud's API during checkout. It is strongly discouraged to selectNoneas you will not have an audit trail of the sale of this item in your TaxCloud account! - Taxcloud TIC - The five digit Taxability Information Code for this product. For more information, see Taxability Information Codes.
To bulk update product TICs, navigate to Catalog → Products.
Select the checkbox next to the items you want to update, then click Actions → Update attributes.
Find the Taxcloud TIC textbox and click the Change checkbox below it. Enter a new TIC and click Save.
Rather than setting a TIC on every product, you can set one on a category and let the products inside it inherit it. Navigate to Catalog → Categories, select a category, and open the TaxCloud section.
- TaxCloud TIC - The five digit Taxability Information Code applied to products in this category that have no TIC of their own.
The field is store-view scoped, like most category attributes: switch the store view at the top left of the category page to give one store view a different TIC, and leave the Use Default Value checkbox ticked everywhere else.
For each line item, the first of these that is set wins:
- The product's own Taxcloud TIC.
- The TIC of the nearest category above it — see the rules below.
- The store's Default TIC setting.
Category resolution looks at every category the product is assigned to and all of their parents, so a TIC set on Grocery applies to everything in Grocery → Snacks → Chips without tagging each sub-category. Two rules keep the outcome predictable when more than one of those categories carries a TIC:
- The deepest (most specific) category wins, so
Grocery → SnacksoverridesGrocery. - If two categories at the same depth both carry a TIC, the one with the lowest category ID wins.
A TIC set on a store's root category is ignored — use the Default TIC setting for a store-wide value.
Configurable products take their TIC from the purchased variant. Variants are usually not assigned to categories themselves, so if the variant has neither its own TIC nor a category with one, the categories of the configurable parent are used before falling back to the Default TIC.
Leaving every category TIC empty reproduces the previous behavior exactly: products fall straight from their own TIC to the Default TIC, and no category lookups are performed.
If you have tax exempt customers, you can add an exemption certificate ID per user. Currently, there is no method to create an exemption certificate through the Magento 2 module, but if you have an existing exemption certificate in TaxCloud, you can link it to a customer's profile.
Navigate to Customers → All Customers, click on the Edit link for a specific customer, and then click on Account Information.
Here you can add the 36 character plus dashes UUID for the already existing exemption certificate.
At checkout, the extension calls TaxCloud's GetExemptCertificates API to confirm that the linked certificate actually covers the destination state — a certificate registered for NY does not exempt a shipment to GA. The list of states covered by a certificate is cached per (customer, certificate) for 1 hour so that repeated checkouts do not call GetExemptCertificates on every page load.
The trade-off is a propagation window: if a certificate is revoked or its covered-states list is edited in the TaxCloud dashboard, this extension may continue to apply the previous covered-states list to that customer's checkouts for up to one hour. Cache entries expire on their own; there is no admin action required, and the next request after expiry validates against TaxCloud and refreshes the cache. The "Cache Lifetime" admin setting controls the tax-lookup and address-verification caches separately and does not shorten this 1-hour exempt-states window. If you have just revoked or modified a certificate and need the change reflected at checkout immediately, clear the TaxCloud cache type — see Clearing the TaxCloud cache.
At this point, the TaxCloud module should be fully configured. However, it is very important to test the integration before going live. This module has been tested with stock Magento, but your store may have other modules that could interfere or cause unintended consequences with the TaxCloud module.
Test adding an item to your cart, clicking Proceed to Checkout, entering a shipping address, and selecting a shipping option. At this point, you should verify that the calculated tax is accurate. Retry this process with each product type / TIC you sell through your store, and combinations of product types / TICs.
Note that on the shopping cart page, before the customer has entered their shipping address, sales tax will not be calculated unless the customer has started the checkout process and returned to the shopping cart page.
Test completing an order and comparing the results to your TaxCloud dashboard. The tax percentages may be slightly different on the Magento side due to rounding, but the tax amounts should match exactly. Make sure the correct TICs, quantities, and other fields are correct on the TaxCloud dashboard.
Test creating a credit memo for an order and comparing the results to your TaxCloud dashboard. The TaxCloud Magento 2 module supports both partial and total refunds.
When a credit memo is refunded in Magento, the extension automatically processes the refund through TaxCloud's API. Here's how the refund flow works:
-
Event Trigger: When a credit memo is refunded, Magento fires the
sales_order_creditmemo_refundevent, which is observed byTaxcloud\Magento2\Observer\Sales\Refund. -
Refund Processing: The observer calls
returnOrder()method in the API model, which:- Extracts all items from the credit memo (products and shipping)
- Builds cart items array with product details (SKU, TIC, Price, Quantity)
- Calculates item prices accounting for discounts
- Adds shipping as a separate cart item if shipping is being refunded
- Prepares API parameters including the order ID and returned date
-
Event Hooks: Before making the API call, the
taxcloud_returned_beforeevent is dispatched, allowing you to modify refund parameters. After the API call,taxcloud_returned_afteris dispatched, allowing you to modify the response. -
TaxCloud API Call: The extension makes a SOAP call to TaxCloud's
ReturnedAPI with retry logic for reliability. -
Response Validation: The response is validated to ensure the refund was processed successfully.
Key Features:
- Supports both partial and full refunds
- Handles product items and shipping separately
- Calculates prices with discounts applied
- Uses Taxability Information Codes (TIC) for accurate tax processing
- Includes retry logic for API failures
- Provides event hooks for extension customization
- Ensures all required parameters are present for API calls
When an order is canceled before any invoice is created, the extension automatically sends TaxCloud's Returned API so the sale is not reported. This ensures you do not remit tax on orders that were never paid (e.g. Check/Money Order, Bank Transfer, COD, or any order canceled with no invoice).
-
Cancellation detected: The extension listens to
order_cancel_afterand, as a fallback, tosales_order_save_afterwhen the order state changes to canceled (e.g. when a payment gateway usesregisterCancellation()). -
Conditions: Returned is only called when:
- The order state is canceled (entire order canceled, not partial).
- The order has no invoices (unpaid; refunds continue to use the credit memo flow).
- The order was captured in TaxCloud. The extension records this locally (a
taxcloud_capturedflag set on the order when capture succeeds), so the cancel decision needs no extra API call. For legacy orders placed before that flag existed, the extension falls back to TaxCloud's OrderDetails API and calls Returned when the response includes a non-empty CapturedDate.OrderDetailsis a license-gated API; when it is unavailable the fallback is skipped safely (the order is treated as not captured) rather than causing an error.
-
TaxCloud API call: The extension calls the
ReturnedAPI for the full order (all items and shipping), using the sametaxcloud_returned_beforeandtaxcloud_returned_afterevents (withcreditmemonull for cancellation). -
Refunds unchanged: Orders that have been invoiced and then refunded via credit memo are not affected; they continue to use the refund flow described above.
Note: This behavior applies to all merchants. It is especially relevant if you use offline or deferred payment methods where orders can be created and later canceled before payment.
The extension stores its API responses — tax lookups, address verifications, and exemption-certificate state lists — in a dedicated TaxCloud cache type rather than the general application cache. It appears as its own row under System → Cache Management, alongside Configuration, Page Cache, and the rest.
This means TaxCloud entries can be cleared on their own:
- Admin — System → Cache Management, tick TaxCloud, choose Refresh from the Actions dropdown, and Submit.
- CLI —
bin/magento cache:clean taxcloud
Both clear only TaxCloud entries; the configuration, block, and full-page caches are untouched, so clearing after a TaxCloud-side change no longer costs a site-wide cache warm-up. Conversely, bin/magento cache:flush still clears everything including TaxCloud.
Reach for this when:
- You changed a product's TIC, or origin/nexus settings, and want the next checkout to re-price immediately rather than serving a cached lookup.
- You revoked or edited an exemption certificate in the TaxCloud dashboard and need the change reflected at checkout before the 1-hour exempt-states window expires.
- You switched the WSDL Endpoint while keeping the same API credentials, so cached results from the previous endpoint could still be served.
Disabling the TaxCloud cache type entirely (System → Cache Management → Disable) stops all TaxCloud response caching regardless of the Cache Lifetime setting, which is useful when diagnosing whether a wrong tax figure is stale or freshly returned. Remember to re-enable it — every checkout will otherwise call TaxCloud.
A setup patch enables the type on bin/magento setup:upgrade, so no action is normally needed. Enabling writes to app/etc/env.php; if that file is read-only at deploy time — as it can be on Adobe Commerce Cloud or a pipeline deploy — the patch logs a warning instead of failing the upgrade, and the type stays disabled. Check with:
bin/magento cache:status
and enable it by hand if taxcloud shows as 0:
bin/magento cache:enable taxcloud
In certain cases, a store owner may need to extend this module. Specific use cases might include: needing to adjust the shipping cost for a shipment containing both taxable and non-taxable items, fetching exemption certificates from an external source, or changing the shipping origin for multi-warehouse fulfillment.
Each of these situations can be accomplished using an event observer. For every API call to TaxCloud, this module emits a before and after event. The before events can be used to modify the parameters sent to TaxCloud's API, and the after events can be used to modify the response.
| Event Name | Description | Data Objects |
|---|---|---|
taxcloud_lookup_before |
Emitted before the Lookup call to get tax rates |
$params, $customer, $address, $quote, $itemsByType, $shippingAssignment |
taxcloud_lookup_after |
Emitted after the Lookup call to get tax rates |
$result, $customer, $address, $quote, $itemsByType, $shippingAssignment |
taxcloud_verify_address_before |
Emitted before the VerifyAddress call during checkout |
$params |
taxcloud_verify_address_after |
Emitted after the VerifyAddress call during checkout |
$result |
taxcloud_authorized_with_capture_before |
Emitted before the AuthorizedWithCapture call (when the order is sent per "Capture in TaxCloud" setting) |
$params, $order |
taxcloud_authorized_with_capture_after |
Emitted after the AuthorizedWithCapture call (when the order is sent per "Capture in TaxCloud" setting) |
$result, $order |
taxcloud_returned_before |
Emitted before the Returned call when a credit memo is created or when a canceled unpaid order is reversed |
$params, $order, $items, $creditmemo |
taxcloud_returned_after |
Emitted after the Returned call when a credit memo is created or when a canceled unpaid order is reversed |
$result, $order, $items, $creditmemo |
For order cancellation, $creditmemo is null and $items are the order items.
TaxCloud logging goes to var/log/taxcloud.log. The filename is a constructor argument on Taxcloud\Magento2\Logger\Handler, bound to that default in the module's etc/di.xml, so you can redirect it from your own module's di.xml without editing this one:
<type name="Taxcloud\Magento2\Logger\Handler">
<arguments>
<argument name="fileName" xsi:type="string">/var/log/taxcloud-custom.log</argument>
</arguments>
</type>The path is relative to the Magento base directory, and the log directory is created if it does not exist. Run bin/magento setup:di:compile (or clear generated/) after changing it. To split TaxCloud logs per environment, bind the argument in an environment-specific di.xml area file as usual.
Two things to keep in mind:
- Whatever path you choose inherits the same rotation caveat as the default — see Logging under TaxCloud Settings.
- Credential redaction happens before records reach the handler, so
apiLoginIDandapiKeystay redacted at any destination.
This extension includes automated deployment to sandbox environments via GitHub Actions.
-
Configure GitHub Secrets in your repository settings:
SFTP_HOST: Your sandbox server IPSFTP_USERNAME: SSH username (e.g.,rootordeploy)SFTP_PORT: SSH port (default:22)MAGENTO_ROOT_PATH: Magento root directory (e.g.,/var/www/html)WEB_USER: Web server user (e.g.,www-data)WEB_GROUP: Web server group (e.g.,www-data)SSH_PRIVATE_KEY: Private SSH key for server access
-
Generate SSH Key (if needed):
./scripts/setup-ssh-deployment.sh
- Automatic: Push to
main,develop, orDEV-* branches - Manual: Go to Actions → Deploy to Sandbox → Run workflow
The deployment process will:
- Run all integration tests
- Deploy module files via SFTP
- Execute Magento setup commands
- Verify deployment success
New versions of the extension are distributed through the Adobe Commerce Marketplace.
Each GitHub release automatically produces a Marketplace-ready zip named taxcloud_magento2-<version>.zip via the Build Marketplace Release Package workflow. This zip respects .gitattributes export-ignore rules, so dev/CI files (Test/, Makefile, scripts/, .github/, phpunit.xml.dist, etc.) are excluded.
To cut a new release:
- Make sure
composer.jsonversionmatches the version you're about to tag (e.g.1.2.0). If it doesn't, bump it onmasterfirst — the Marketplace's EQP validation will reject a submission whosecomposer.jsonversion doesn't match the tag. - Bump
etc/module.xmlsetup_versionto the same value. Magento 2.3+ no longer usessetup_versionfor schema migration (data patches drive that now), so behavior does not change either way — but keeping it in sync withcomposer.jsonis the canary that catches a forgotten version bump. The two should never drift. - Create a tag on
master:git tag -a v1.2.0 -m "v1.2.0" git push origin v1.2.0 - Create a GitHub release from the tag at Releases → Draft a new release. Auto-generate release notes or write them manually.
- The
Build Marketplace Release Packageworkflow runs automatically on publish and attachestaxcloud_magento2-<version>.zipto the release. (If it ever fails, you can re-run it manually from Actions → Build Marketplace Release Package → Run workflow and pass the tag.) - Go to the Marketplace extension page and start a new version submission.
- Attach the zip from the GitHub release under Attach package, and paste the release notes from the GitHub release body into the submission form.
- Submit for review. Once Adobe approves the submission, the new version is published to the Marketplace automatically.
This project is distributed under the Open Software License ("OSL") v. 3.0 (see the LICENSE file in the project root).












