Skip to main content

Migration Guide: v2 → v3

This guide covers every breaking change between @basis-theory/web-elements v2 and v3. Work through each section and use the upgrade checklist at the end to confirm your integration is complete.

The package name is unchanged: @basis-theory/web-elements. Only the major version changes.

1. Initialization

The most significant change in v3: initialization is now synchronous. The basistheory named export is replaced by a default BasisTheory constructor that returns an SDK instance immediately.

- import { basistheory } from '@basis-theory/web-elements';
+ import BasisTheory from '@basis-theory/web-elements';

- const bt = await basistheory('<PUBLIC_API_KEY>', { environment: 'test' });
+ const bt = BasisTheory('<PUBLIC_API_KEY>');

The environment option no longer exists. The SDK auto-detects the environment from the page origin (localhost → dev, btsandbox.com → UAT, everything else → production). To point at a specific API, use apiBaseUrl:

// Explicit API base URL (optional)
const bt = BasisTheory('<PUBLIC_API_KEY>', {
apiBaseUrl: 'https://api.btsandbox.com',
});

CDN global also changed from lowercase to uppercase:

- const bt = await basistheory('<PUBLIC_API_KEY>');
+ const bt = BasisTheory('<PUBLIC_API_KEY>');

2. Element types

Several element types were renamed; none were removed.

v2 typev3 typeNotes
'cardNumber''cardNumber'Unchanged
'cardExpirationDate''expiry'Renamed
'cardVerificationCode''cvv'Renamed
'text''text'Unchanged
'card''card'API changed — targetId removed (pass a selector to mount()), placeholder is now a structured object, and tokenization uses the card sub-field accessors
'copyButton''copyButton'API changed — now requires allowClipboard: true. The v2 enableCopy and copyIconStyles options were removed. See copyButton options

The combined card element is still available. Its API changed: remove targetId (pass the container selector to mount()), and placeholder is now a structured object with cardNumber, expiryDate, and cvc keys:

- const cardEl = bt.createElement('card', { targetId: 'card' });
+ const cardEl = bt.createElement('card', {
+ placeholder: { cardNumber: '4242 4242 4242 4242', expiryDate: 'MM/YY', cvc: 'CVC' },
+ });
await cardEl.mount('#card');

Tokenization no longer accepts the card element directly. Reference its sub-fields directly on the element — use cardEl.expiryDate for both expiration_month and expiration_year. Passing the same reference to both is required: the SDK parses the MM/YY value into a numeric month and four-digit year inside the secure element before the request is sent.

  await bt.tokens.create({
type: 'card',
- data: cardEl,
+ data: {
+ number: cardEl.number,
+ expiration_month: cardEl.expiryDate,
+ expiration_year: cardEl.expiryDate,
+ cvc: cardEl.cvc,
+ },
});

3. Events

Removed events

keydown and click events are not available in v3. The copy event remains, but is now emitted only by the copyButton element.

Added events

error is a new event in v3, fired when infrastructure or API errors occur on the element.

element.on('error', (event) => {
console.error(event.detail.code, event.detail.message);
});

Change event payload

The change event payload shape changed. Access event data via event.detail instead of directly on event:

- element.on('change', (event) => {
- if (event.complete) { ... }
- if (event.empty) { ... }
- if (event.errors) { ... }
- });

+ element.on('change', (event) => {
+ if (event.detail.isValid) { ... }
+ if (event.detail.isEmpty) { ... }
+ if (event.detail.error) { ... }
+ });

cardNumber change event — card metadata fields moved to event.detail:

- event.cardBrand
- event.cardLast4
- event.cardBin

+ event.detail.cardBrand
+ event.detail.last4
+ event.detail.bin

on() return value

on() now returns an unsubscribe function instead of a Subscription object:

- const subscription = element.on('change', handler);
- subscription.unsubscribe();

+ const unsubscribe = element.on('change', handler);
+ unsubscribe();

4. createElement options

The targetId option was removed. The target container is now passed to mount() instead:

- const el = bt.createElement('cardNumber', { targetId: 'my-card' });
- el.mount();

+ const el = bt.createElement('cardNumber');
+ await el.mount('#my-card');

Validation state tracking changed. Instead of reading state from element properties after a change event, read from event.detail:

- cardElement.on('change', () => {
- submitBtn.disabled = !cardElement.complete;
- });

+ cardNumberEl.on('change', (event) => {
+ submitBtn.disabled = !event.detail.isValid;
+ });

5. Tokenization

tokens.create is unchanged for card tokens. The data object field names are the same:

// Same in v2 and v3
await bt.tokens.create({
type: 'card',
data: {
number: cardNumberEl,
expiration_month: expiryEl,
expiration_year: expiryEl,
cvc: cvvEl,
},
});

If you use the combined card element instead of separate fields, pass its sub-field references — card.number, card.expiryDate (for both month and year), and card.cvc — rather than the element itself.

The tokenize method (batch) signature is unchanged.

tokenIntents gains a get() method in v3 (was only create() in v2):

// New in v3
const intent = await bt.tokenIntents.get(intentId, { apiKey: sessionApiKey });

Sending data to third-party endpoints

Use bt.proxy (get/post/put/patch/delete) to forward element-reference data through Basis Theory to a third-party endpoint. The method names are unchanged from v2, but the configuration moved from headers to options and the response behavior changed:

  • Pass the pre-configured proxy key as the proxyKey option instead of the BT-PROXY-KEY header, and the ephemeral destination as the url option instead of the BT-PROXY-URL header.
  • The response is now returned directly to your code. In v2 the response was a synthetic reference surfaced back into an element with setValue; in v3, proxy.* resolves to the destination's raw response body. Reveal (setValue) is not being ported to v3.
  • Per-call authentication is unchanged: bt.proxy.* still accepts an apiKey option that overrides the initialization key for that request, so a v2 apiKey: '<SESSION_API_KEY>' carries over as-is. See Services → proxy for the v3 authentication model.
- bt.proxy.post({
- headers: { 'BT-PROXY-KEY': '<YOUR_PROXY_KEY>' },
- body: { card: { number: cardNumberEl } },
- apiKey: '<SESSION_API_KEY>',
- }).then((response) => {
- revealEl.setValue(response.number); // synthetic reference revealed into an element
- });

+ const response = await bt.proxy.post({
+ proxyKey: '<YOUR_PROXY_KEY>', // was the BT-PROXY-KEY header; use `url` for an ephemeral destination
+ body: { card: { number: cardNumberEl } },
+ apiKey: '<SESSION_API_KEY>', // still supported — overrides the init key for this request only
+ });
+ // response is the destination's raw body, returned directly to your code.
+ // It may contain sensitive data (e.g. a card number) — do not log it.
+ // Use a response transform to tokenize or redact values before they reach your code.

See Services → proxy for request/response options and the URL and header requirements.


6. Theming

v3 introduces a design token system. Instead of passing CSS properties directly to elements for global styling, you now pass structured theme and optional darkTheme objects to BasisTheory():

- // v2: per-element style only, no global theme
- const el = bt.createElement('cardNumber', {
- style: { base: { color: '#111' } },
- });

+ // v3: design tokens at init — no per-element style option
+ const bt = BasisTheory('<PUBLIC_API_KEY>', {
+ themeMode: 'auto',
+ theme: {
+ colors: { primary: '#6366f1', text: { primary: '#111827' }, ... },
+ typography: { fontFamily: 'Inter', fontSize: { base: '16px' }, fontWeight: { normal: '400' } },
+ spacing: { sm: '8px', md: '12px', lg: '16px' },
+ borders: { radius: { base: '6px' }, width: { base: '1.5px' } },
+ },
+ });
+
+ const el = bt.createElement('cardNumber');

See Theming for the full token schema and dark mode setup.


7. React

Provider

The useBasisTheory(apiKey, options) initialization hook is replaced by BasisTheoryProvider:

- import { useBasisTheory, BasisTheoryProvider } from '@basis-theory/react-elements';
-
- function App() {
- const { bt, error } = useBasisTheory('<PUBLIC_API_KEY>', { environment: 'test' });
- if (!bt) return <div>Loading...</div>;
- return <BasisTheoryProvider bt={bt}><PaymentForm /></BasisTheoryProvider>;
- }

+ import { BasisTheoryProvider } from '@basis-theory/react-elements';
+
+ function App() {
+ return (
+ <BasisTheoryProvider apiKey='<PUBLIC_API_KEY>'>
+ <PaymentForm />
+ </BasisTheoryProvider>
+ );
+ }

useBasisTheory() inside the tree still works but now takes no arguments — it only reads from context:

- const { bt } = useBasisTheory('<PUBLIC_API_KEY>', options);
+ const { bt, error } = useBasisTheory(); // must be inside BasisTheoryProvider

Components

CardElement is still available, but id is no longer used and onChange payloads moved to event.detail. You can either keep the combined CardElement or switch to the three separate components:

- import { CardElement } from '@basis-theory/react-elements';
-
- <CardElement id="card" ref={cardRef} onChange={(e) => setComplete(e.complete)} />

+ import { CardElement } from '@basis-theory/react-elements';
+
+ <CardElement ref={cardRef} onChange={(e) => setComplete(e.detail.isValid)} />

To tokenize the combined element, reference its sub-fields directly on the ref (cardRef.current.number, .expiryDate, .cvc). Alternatively, split it into the three separate components:

+ import { CardNumberElement, ExpiryElement, CVVElement } from '@basis-theory/react-elements';
+
+ <CardNumberElement ref={cardNumberRef} onChange={(e) => setCardNumberValid(e.detail.isValid)} />
+ <ExpiryElement ref={expiryRef} onChange={(e) => setExpiryValid(e.detail.isValid)} />
+ <CVVElement ref={cvvRef} onChange={(e) => setCvvValid(e.detail.isValid)} />

Event prop payloads follow the same change as the web SDK — use event.detail.*:

- onChange={(event) => setComplete(event.complete)}
+ onChange={(event) => setValid(event.detail.isValid)}

Upgrade checklist

0 / 15 complete
Installation
Initialization
Element types
Element options
Events
React
Theming

Not yet available in V3

These v2 features are under consideration for future releases. This list is kept up to date as features are added to v3.

Element options

  • binLookup — opt-in BIN enrichment; adds co-badge networks and selectedNetwork to the cardNumber change event
  • Per-element style overrides — state variants (base, error, empty, complete) and pseudo-selectors (:hover, :focus, :disabled, ::placeholder, ::selection)
  • Custom Google Fonts — loading from the Google Fonts library

Methods

Events

  • keydown — keyboard event listener (altKey, ctrlKey, key, metaKey, shiftKey)

Init options


Coming to V3

These features are on the v3 roadmap and will be added in upcoming releases.

  • Dual Writing — write token data to multiple destinations simultaneously
  • Device Fingerprint — browser and device fingerprint collection

Not being ported

These v2 features were evaluated against usage patterns and will not be included in v3. If you rely on any of these features, contact support to discuss your use case.
  • Reveal (setValue) — programmatically populate an element with previously tokenized data
  • Custom Card Brand — custom card brand icons and detection logic