How to

Unstripe yourself: How to replace Stripe Elements with Basis Theory

Control your cardholder information while maintaining your existing scope. Learn how to swap out Stripe Elements with Basis Theory’s.

Elements provide modern building blocks for collecting sensitive information in your UI, allowing developers to build immersive forms that match the look and feel of your website. While these powerful components helped keep systems out of compliance scope, they also coupled applications and websites to the payment processors that offered them. Using Basis Theory Elements and our PCI-compliant environment, developers can decouple their card data from their payment processors in just a couple of hours. (Don’t worry, you can leave Basis Theory at any time, too)

Let’s look at how you insert Basis Theory in front of Stripe without changing your Stripe integration or downstream services. 

Application Overview

Every application will have a different architecture, and every implementation will be slightly unique. For this guide, I have chosen to use the <span class="code">with-stripe-typescript</span> example project from NextJS. To start a new project, execute <span class="code">npx create-next-app folder-name –example with-stripe-typescript</span>. This will set up the project in the folder you specify.

This NextJS project comes with three different examples, all highlighting a different way to use Stripe. Two of the examples (“Donate with Checkout” and “Use Shopping Cart”) use features that are hosted on and do not use Elements. We will upgrade the “Donate with Elements” page to Basis Theory Elements.

There are three things to notice about the new project. First is the <span class="code">.env.local.example</span> file, which contains environment variables that are injected into the project at build time. This is where the Stripe and Basis Theory API keys will be set. There is also a <span class="code">components</span> folder that contains several components used across the app, and the <span class="code">ElementsForm.tsx</span> component is the one we will be upgrading. Finally, there is the <span class="code">pages</span> folder, which contains the backend API code and the pages that consume the components.

The NextJS team has a great introductory guide if you aren’t familiar with Next. You do not need to know all the ins and outs of NextJS to follow this guide, but a basic understanding of how NextJS works will be helpful!

Environment Variables

The first step is configuring your environment variables. We need five keys for this project:

  1. A Private Basis Theory Application, with the following permissions: <span class="code">token:pci:create</span> and <span class="code">token:pci:use:reactor</span>. The API will use this to use the token to create a charge with Stripe.
  2. A Public Basis Theory Application, with the following permissions: <span class="code">token:pci:create</span>. This will be used for the frontend Elements. Public Applications only have write permissions, allowing you to use the key in your frontend code without worrying about malicious actors using it to read back sensitive data.
  3. A Reactor ID to securely send our card data to Stripe without consuming the data in our app.
  4. A Stripe publishable key
  5. A Stripe secret key

If you need help creating the Applications, you can refer to our complete guide to Applications. Once you have the API keys for both Applications, you will need to put the keys into the <span class="code">.env.local.example</span> file. The Public API key variable name will need to start with <span class="code">NEXT_PUBLIC_</span> - this ensures that NextJS makes the variable available to your frontend application. Next, you will need your Stripe publishable and secret keys. Enter the publishable key into the <span class="code">.env.local.example</span> file.

The last piece of the puzzle is the “Stripe - Create Payment Method for a Card” Reactor. Inside the Basis Theory Portal, select Reactors from the menu, then “Create Reactor”. Next, search for “Stripe” and select the “Stripe - Create Payment Method for a Card” formula, then “Use This Formula”. Give the Reactor a name and enter your Stripe secret key, then click “Create Reactor.” When you create the Reactor, you need to copy the Reactor ID and store it in the <span class="code">.env.local.example</span> file also. (Review our complete guide to Reactors to learn more!)

Basis Theory Elements Provider

Basis Theory’s React package uses Context to ensure the Basis Theory service is available throughout your application. Stripe’s Elements uses a similar provider to wrap and help initialize the fields. Therefore, we first need to remove the Stripe provider and replace it with the Basis Theory provider.

Open the <span class="code">pages/donate-with-elements.tsx</span> file. We can replace the entire <span class="code">useState</span> and <span class="code">useEffect</span> at the top of the component with a simple call to initial the Basis Theory service:

const { bt } = useBasisTheory(process.env.NEXT_PUBLIC_BT_ELEMENTS_API_KEY!, {
  elements: true,

Next, we can further simplify the component by replacing the entire <span class="code">Elements</span> provider usage with the much simpler <span class="code">BasisTheoryProvider</span>:

{bt ? (
  <BasisTheoryProvider bt={bt}>
    <ElementsForm />
) : (

We also no longer need to pass the Payment Intent from Stripe into the <span class="code">ElementsForm</span> component. Already our code is looking much simpler and easier to understand! (Don’t forget to import <span class="code">useBasisTheory</span> and <span class="code">BasisTheoryProvider</span> if your IDE didn’t do it automatically!)

View the changes to the file on GitHub!

The Payment Form

Now that we’ve configured the Basis Theory Provider, we can update the <span class="code">ElementsForm.tsx</span> component to use Basis Theory Elements instead of Stripe. First, we need to initialize a ref that will be used to reference the secure data, as well as get an instance of the Basis Theory service from the provider:

const { bt } = useBasisTheory();
const cardRef = useRef(null);

Then we can replace the <span class="code">PaymentElement</span> from Stripe with the <span class="code">CardElement</span> from Basis Theory:

<CardElement id="card-element" ref={cardRef}/>

The rest of the front-end changes happen in the <span class="code">handleSubmit</span> function. When using Basis Theory with Stripe, you do not need a Payment Intent until you are ready to charge the card. Therefore, we can remove all the calls to Stripe in this function, and instead create a Basis Theory Token using the <span class="code">bt</span> service and <span class="code">cardRef</span> we initialized earlier.

let token: Token | undefined;
try {
  token = await bt.tokens.create({
    type: 'card',
    data: cardRef.current,
} catch (error) {
  setErrorMessage(error instanceof Error ? error.message : 'Unknown Error');
  // eslint-disable-next-line no-console

Then, instead of attempting to confirm a Payment Intent with Stripe from the front end, we will send the token to our Basis Theory API to process the payment with Stripe. Moving the processing to the backend allows you to store the Token in your database if needed and gives you full control over how the payment is processed without worrying about client-side interference.

// Charge the card using our API
const response = await fetchPostJSON('/api/charge_with_reactor', {
  amount: input.customDonation

View the changes to the form on GitHub!

Updating the API

In the last step, we called the <span class="code">charge_with_reactor</span> endpoint, which doesn’t yet exist! NextJS uses a file-based routing convention: inside the <span class="code">api</span> folder, create a new folder called <span class="code">charge_with_reactor</span> and create an <span class="code">index.ts</span> file within it. This will hold our new API endpoint. Some boilerplate is needed to handle a request in NextJS, which I won’t cover here. (You can view the complete endpoint here for reference.) Let’s jump right into the body of the code:

const { raw } = await bt.reactors.react(process.env.BT_REACTOR_ID as string, {
  args: {
    card: `{{${}}}`,

const paymentIntent = await stripe.paymentIntents.create({
  currency: CURRENCY,
  payment_method: as string,
  confirm: true,

So what’s going on here, and what’s happening behind the scenes with that Reactor? When our updated <span class="code">ElementsForm</span> calls this new endpoint, we send the Basis Theory Token and the amount to charge. The first thing we do is pass the Token ID to the Reactor, making sure that the value is enclosed in double curly braces (<span class="code">{{ }}</span>). When the Reactor starts, the Token is automatically detokenized, and the Reactor has the complete PAN, CVC, and expiration. The Reactor we are invoking creates a Payment Method within Stripe for us to charge with their SDK, and it returns the value in the <span class="code">raw</span> property of the response.

Using the Payment Method ID we just received, we then pass that data to Stripe, ensuring we set <span class="code">confirm: true</span> to capture the payment immediately. And that’s it! You have successfully decoupled your application from Stripe by capturing and storing the card data with Basis Theory, then charged the card from your API without your application ever seeing the plaintext card data!

What’s Next

What’s next depends on your application and needs. Here are some other things to consider:

  • Migrating your Stripe payment methods to Basis Theory Tokens. Stripe provides a way to export all your PAN data. You can transform the export into a CSV and upload it securely to Basis Theory to bulk import and tokenize all of your data.
  • Secure PII with Basis Theory. Elements can help you secure your entire application by tokenizing more than just card data. Basis Theory Elements provides a flexible `TextElement` that you can use to capture any input (such as social security numbers, bank accounts, addresses, and more).
  • Implement other payment processes. Basis Theory Reactors support multiple payment processors. With very little change to your API logic, you can process payments with a processor other than Stripe, giving you the flexibility to fully control your payment flow.

Have feedback or questions? Join us in our slack community


Want product news and updates?

Receive the latest posts directly in your inbox.