Medusa plugin for integrating Avalara AvaTax as a tax provider, enabling automated tax calculations and compliance for e-commerce transactions.
# npmnpm install @u11d/medusa-avalara# yarnyarn add @u11d/medusa-avalara
Add the plugin to your Medusa config file () using the helper:
import { defineConfig } from "@medusajs/framework/utils";import { withAvalaraPlugin, AvalaraPluginOptions } from "@u11d/medusa-avalara";const avalaraPluginOptions: AvalaraPluginOptions = {accountId: 0, // Your Avalara account IDlicenseKey: "YOUR_LICENSE_KEY",environment: "sandbox",companyId: 0, // Your Avalara company IDcompanyCode: "DEFAULT",defaultTaxCode: "PC040100", // Clothing and related products (business-to-customer)-general};module.exports = defineConfig(withAvalaraPlugin({projectConfig: {// your project config},plugins: [// other plugins],modules: [// your modules],},avalaraPluginOptions));
For accurate tax calculations and optimal API efficiency, we recommend the following checkout workflow:
Why this workflow? The plugin operates as a tax provider within Medusa's framework and is invoked whenever Medusa triggers tax calculations. The plugin cannot modify Medusa's core checkout flow or enforce when taxes are calculated. By following this workflow and disabling "Automatic Taxes" in your region settings, you control exactly when tax calculations occur, ensuring they happen only after all necessary information (address and shipping method) is available.
Important Notes:
- Automatic Taxes Setting: The region's configuration significantly impacts API efficiency. When enabled, Medusa automatically calls the tax provider multiple times during checkout (on address changes, shipping method selection, etc.), increasing Avalara API requests. For better performance and cost control, disable automatic taxes and manually trigger calculations via after the shipping method is selected.
- Tax Recalculation: Medusa doesn't automatically recalculate taxes when cart properties change (quantity, address, discounts). Always call before checkout to ensure accurate amounts. See issue #13929
- The wrapper handles plugin modules registration and dependency injection automatically. See Manual Module Registration section if you need to understand what the helper does or configure modules manually
- You can use environment variables instead of hardcoding options, especially important for credentials like and
- The example above will use for each product. See Advanced Usage for assigning specific tax codes to individual products
- The plugin automatically syncs your Medusa stock locations with Avalara locations on startup for accurate tax calculations. However, since Medusa doesn't emit events for stock location changes, if you create or update a location after startup, you'll need to either restart the app or manually call to refresh the cache
- The plugin fully supports tax-inclusive pricing and automatically respects the region's tax-inclusive preferences
- Discounts should be used with caution. Medusa does not use the exact tax amounts returned by Avalara but instead calculates taxes using the rates provided, which may lead to minor differences in final tax calculations when discounts are applied.
After configuring your Medusa setup, run the database migration to create the required tables:
npx medusa db:migrate
After starting your Medusa server:
Note: To disable Avalara and return to the default Medusa tax calculations, simply select System as your tax provider in the same settings.
Access your Avalara dashboard to obtain the required credentials:
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
| ✅ | - | Your Avalara account ID | ||
| ✅ | - | Your Avalara license key | ||
| ✅ | - | AvaTax environment | ||
| ✅ | - | Your company code in AvaTax | ||
| ✅ | - | Your Avalara company ID | ||
| ❌ | Controls whether documents should be recorded in AvaTax. If set to transactions (sales invoices) are not created | |||
| ❌ | - | Machine identifier | ||
| ❌ | - | Default tax code for products | ||
| ❌ | Tax code for shipping |
The configuration endpoints for managing product tax codes and customer exemptions require admin credentials. You'll need to authenticate first to get a Bearer token that you'll use for subsequent API requests.
Get Authentication Token:
curl -X POST http://localhost:9000/auth/user/emailpass \-H "Content-Type: application/json" \-d '{"email": "your_admin_email@example.com","password": "your_admin_password"}'
This will return a response containing a field. Copy this token value to use in the header for the admin API endpoints described below.
In most e-commerce scenarios, different products require different tax codes based on their category, material, or intended use. The plugin uses the table to determine which specific Avalara tax code should be applied to each product during tax calculations. You can manage these product-specific tax codes either by updating the database table directly or by using the provided admin API endpoint.
You can find the complete list of available Avalara tax codes at: https://taxcode.avatax.avalara.com/
By default, all products will use the value. To assign specific Avalara tax codes to individual products, make a request to using your authentication token:
Update Product Tax Codes:
curl -X PUT http://localhost:9000/admin/avalara-products \-H "Content-Type: application/json" \-H "Authorization: Bearer YOUR_TOKEN_HERE" \-d '{"avalara_products": [{"product_id": "prod_123","tax_code": "PC040100"},{"product_id": "prod_456","tax_code": "PS081000"}]}'
Many businesses need to support tax-exempt customers such as government entities, non-profit organizations, or resellers. The plugin provides a complete system for managing customer exemptions using Avalara's Entity Use Codes.
For the complete list of predefined entity use codes, refer to the official Avalara documentation.
The plugin manages customer exemptions through the module, which stores entity use codes for exempt customers and caches them for fast lookup during tax calculations.
Setting Customer Exemptions:
curl -X PUT http://localhost:9000/admin/avalara-customers \-H "Content-Type: application/json" \-H "Authorization: Bearer YOUR_TOKEN_HERE" \-d '{"avalara_customers": [{"customer_id": "cus_federal_agency","entity_use_code": "A"},{"customer_id": "cus_nonprofit","entity_use_code": "E"}]}'
Listing Customer Exemptions:
curl -X GET "http://localhost:9000/admin/avalara-customers?offset=0&limit=20" \-H "Authorization: Bearer YOUR_TOKEN_HERE"
The plugin provides a endpoint that serves as a proxy to Avalara's ResolveAddressPost API. Address validation is critical for accurate tax calculations, as Avalara requires correctly formatted and validated addresses to determine proper tax rates and jurisdictions.
Request Body:
{"line1": "512 S Mangum St Ste 100","city": "Durham","region": "NC","postalCode": "27701","country": "US"}
Notes:
If you prefer to configure modules manually without using the wrapper, you can register each module individually:
import { defineConfig, Modules } from "@medusajs/framework/utils";import { AvalaraPluginOptions } from "@u11d/medusa-avalara";const options: AvalaraPluginOptions = {// your options here};module.exports = defineConfig({plugins: ["@u11d/medusa-avalara"],modules: [{resolve: "@u11d/medusa-avalara/modules/avalara-product",dependencies: [Modules.CACHE],},{resolve: "@u11d/medusa-avalara/modules/avalara-customer",dependencies: [Modules.CACHE],},{resolve: "@u11d/medusa-avalara/modules/avatax-factory",options,dependencies: [Modules.CACHE],},{resolve: "@medusajs/medusa/tax",options: {providers: [{resolve: "@u11d/medusa-avalara/providers/avatax",options,},],},dependencies: [Modules.CACHE],},],});
Note: Manual registration requires careful attention to module dependencies and isolation. The wrapper is recommended as it handles these complexities automatically.
The Medusa Avalara plugin integrates with Avalara's AvaTax service through a modular architecture:
modules/avalara-product
modules/avalara-customer
modules/avatax-factory
providers/avatax
Subscribers and Workflows:
Important Note on Voiding Transactions: A document can be voided only if it has not yet been locked or reported in a tax filing period. Once a document has been reported (filed) or the filing period is locked, it cannot be voided directly through the API. In such case the plugin will log an error and you must create an adjustment (return invoice) manually in Avalara to offset the original document.
This architecture ensures accurate tax calculations during checkout and proper transaction lifecycle management in Avalara's system. The wrapper simplifies the setup by automatically configuring all these modules with proper dependencies and isolation.
The plugin uses Redis cache to store frequently accessed data for optimal performance during tax calculations. This cache strategy minimizes database queries and API calls, ensuring fast response times during checkout.
Product Tax Codes ()
Customer Exemptions ()
Tax-Inclusive Settings ()
Location Mappings ( and )
Cache Management:
If you encounter any of the following errors:
Solution: Run the database migration:
npx medusa db:migrate
This ensures the table is created and the cache module is properly configured.
If you're seeing $0 tax amounts in your calculations, follow these troubleshooting steps:
If you encounter any issues or need assistance with this plugin, please visit our GitHub Issues page. Our team actively monitors and responds to bug reports, feature requests, and questions from the community. We aim to provide timely support to ensure your integration with Avalara runs smoothly.
Need expert assistance or want our team to support your Medusa project? We're here to help! Contact us at https://u11d.com/contact/ for professional support and consultation services.
Read our comprehensive blog article about integrating Avalara with Medusa: https://u11d.com/blog/automated-tax-calculations-medusa-avalara-integration
We welcome contributions! Please see our Contributing Guide for details.
This project is licensed under the MIT License - see the LICENSE file for details.