About the integration
The Medusa TaxJar Integration connects Medusa with TaxJar, a popular tool for automating sales tax calculations, making tax management simple and efficient.
This integration helps developers save time by automating tax calculations for products and shipping, so there’s no need for manual work or extra tools. With TaxJar’s powerful API, you can manage taxes for different regions right from your Medusa backend.
In this guide, we’ll show you how to set it up and use its main features to handle taxes easily in your e-commerce project.
Key features of TaxJar
-
Automated tax calculations: Use TaxJar’s powerful API to automate tax calculations for transactions, without manual intervention.
-
Product tax codes: Assign and manage tax codes specific to your products, allowing for accurate tax calculation tailored to diverse product categories.
-
Real-time tax updates: Stay compliant effortlessly by syncing your tax data with TaxJar’s regularly updated database of tax rules and rates.
Getting started with TaxJar integration
1. Set up your TaxJar account
The integration begins with creating an account on the TaxJar.com platform. From there, there are two important steps.
First, you should obtain your API key linked to the account. Save the live token to use in the Medusa application.
Second, you have to enter the business and nexus details in the account configuration. That information will be used in tax calculations.
2. Extend the ProductCategory model
TaxJar uses a tax code to perform calculations. Each product type have different code. That means, the provider needs to know them before sending a calculation request.
Let’s assume that we want to keep all products of the same tax code in one category. That means that we have to extend the ProductCategory model by a new property called tax_code. Let’s add as well a code name and description to keep things easy. Our extension model should look like this:
import { model } from "@medusajs/utils";
const TaxCode = model.define("tax_code", {
id: model.id().primaryKey(),
name: model.text().default(""),
description: model.text().default(""),
code: model.text().unique(),
});
export default TaxCode;
Next, let’s link the ProductCategory to newly created model. We do that by creating a file in the links folder of the Medusa application. We define the link like this:
import { defineLink } from "@medusajs/framework/utils";
import ProductModule from "@medusajs/medusa/product";
import Taxcode from "../modules/taxcode";
export default defineLink(
{
linkable: ProductModule.linkable.productCategory,
isList: true,
},
Taxcode.linkable.taxCode
);
3. Sync database links and seed tax codes
Register the Taxcode module in the medusa-config.ts file, generate migration and run it.
The tax_code table will be created, but to actually create a relation between tax_code and ProductCategory a sync links command needs to be executed.
npx medusa db:sync-links
After we have created necessary tables in the database, we should seed tax_code with actual values.
We can pull them from the TaxJar API by performing a GET request to https://api.taxjar.com/v2/categories
, and attaching our API token as an Authorization header.
Then, insert them into the database, for example like in this script.
export default async function seedTaxCodes({ container }: ExecArgs) {
const taxjarClient = new TaxJarClient(
process.env.TAXJAR_API_KEY
);
const taxCodeService = container.resolve("Taxcode") as TaxCodeService;
const entries = await taxjarClient.getTaxCategories();
await taxCodeService.createTaxCodes(
entries.map((e) => {
return {
name: e.name,
description: e.description,
code: e.product_tax_code,
};
})
);
logger.log(`Created ${entries.length} entries!`);
}
4. Create a custom TaxProvider
Having a link between product-product_category-tax_code we can go on and implement a custom TaxProvider class.
By default, Medusa uses the built-in system tax provider, which simply proceeds with the tax rates entered by the application administrator. In order to use TaxJar calculations, we have to create a custom one.
To do this, we are going to create a separate module, and a class that implements interface ITaxProvider from the @medusajs/framework package.
It specifies two methods that have to be implemented:
-
getIdentifier(): Return an unique string that indicates the provider in the system.
-
getTaxLines(): Match the appropriate tax rate to each of the items in the cart.
It takes following arguments:
-
itemLines: TaxTypes.ItemTaxCalculationLine[] - all items stored in the cart,
-
shippingLines: TaxTypes.ShippingTaxCalculationLine[] - shipping information,
-
context: TaxTypes.TaxCalculationContext - buyer information.
So, having a product_id of each itemLine we can query the database in order to retrieve tax_codes used in the calculations.
Let’s perform ask TaxJar API for calculations. The request format is:
POST <https://api.taxjar.com/v2/taxes>
Authorization: Bearer <api token>
{
to_country: string;
to_zip: string;
to_state: string;
to_city: string;
to_street: string;
shipping: number;
line_items: {
id: string;
quantity: number;
unit_price: number;
discount: number;
product_tax_code: string;
}
}
In return we got total tax amount to charge, as well as tax rates for each item. We can use them to feed the TaxProvider.
Please note, that Medusa uses tax rates as percentage values, while TaxJar returns them in fraction format, so each value has to be multiplied by a hundred.
5. Register the Tax Provider
The newly created TaxJarProvider has to be registered in the medusa-config.ts file in order to be recognized by Medusa.js:
{
resolve: "@medusajs/medusa/tax",
options: {
providers: [ {
resolve: "./src/modules/taxjar",
id: "tax-jar-provider",
options: { apiKey: process.env.TAXJAR_API_KEY },
} ],
},
}
After finishing with the code, we need to save the provider information in the database.
In table tax-provider we need to create a record.
And link the US tax region to the tax-jar-provider in table tax-region:
The provider is now ready to go.
6. Admin configuration
We should also create an admin interface to assign tax code to product category.
Let’s create a widget in admin/widgets directory and attach it to zone product_category.details.after. That means the widget will be displayed in the category section, right after details widget.
export const config = defineWidgetConfig({
zone: "product_category.details.after",
});
Let’s also create an GET endpoint in api/admin/category/[categoryId]/taxcode catalog, that returns tax code details assigned to categoryId.
In order to fetch tax code details we use useEffect hook in the widget:
useEffect(() => {
axios
.get(`/admin/category/${props.data.id}/taxcode`, {
withCredentials: true,
})
.then((res) => res.data)
.then((res) => {
if (res.tax_code) {
setTaxCode(res.tax_code);
} else {
setTaxCode({});
}
});
}, []);
Having tax code fetched, we can display it, for example, like this.
Also, we should create a way to assign the code to the category.
Let’s create another endpoint, this time POST, that will accept both categoryId and taxCode as parameters, and create a link between them.
Wrapping up
Congratulations! Your configuration is now complete, and you can start automate tax calculations and simplify tax management.
By leveraging Medusa.js's modular architecture and TaxJar's powerful API, you can focus more on building unique commerce experiences and less on navigating tax complexities.
You can check out the GitHub repository here.
Transform your online store with our expertise in Medusa
Let's talk about your projectOther blog posts
Mercur Marketplace Leap to Medusa 2.0 and More
Mercur migrated to Medusa 2.0, introducing cleaner architecture, advanced seller management, and essential features like order splitting...
How to Get Started with Mercur Marketplace?
We'll show you how to get started with Mercur - Medusa MultiVendor Marketplace Accelerator. By the end of this video, you'll have a fully functional marketplace with both admin and vendor panels along with their features...