TaxKitProvider
TaxKitProvider is the entry point for the Tax Kit. It manages the iframe lifecycle, the post-robot bridge to the parent app, and serializes your configuration into the iframe's URL.
The new (2.2.0) prop shape groups configuration into four optional sub-objects: theme, options, metadata, and copy. Callbacks and the required fetchAccessToken stay at the top level. Each grouped sub-object has its own reference page linked from the sidebar.
Mount the provider once per page. Each TaxKitProvider renders its own iframe and its own post-robot bridge — mounting two on the same page produces two iframes (almost certainly not what you want) and useTaxKit() consumers will only see state from whichever provider is closest in their React tree. The provider itself produces no visible markup beyond the iframe: until the user triggers it (or you pass options.defaultOpen), the iframe is hidden behind an overlay primitive.
Required props
fetchAccessToken() => Promise<string | null | undefined>requiredReturns a fresh CoinTracker access token (JWT). The provider calls this whenever the iframe needs to authenticate — including when the existing token is within 30 seconds of expiry. The iframe deduplicates concurrent token requests internally, so your fetchAccessToken won't be hammered by parallel GraphQL queries.
Return null or undefined to signal that no authenticated session is available — the iframe will treat this as an unauthenticated state.
Configuration groups
themeTaxKitThemeColor tokens for light and dark mode, the mode override, and partner branding (logo + fonts). See Theme.
optionsTaxKitOptionsBehavior switches: environment, flow, overlay style, UI toggles, partner help URL. See Options.
metadataTaxKitMetadataPartner-supplied bag forwarded to the iframe. The reserved promoCode key has typed special handling; everything else passes through opaquely. See Metadata.
copyPartnerCopyOverride partner-facing copy strings inside the iframe (e.g. import confirmation checklist, add-connections disclaimer). See Copy.
Callbacks
See Callbacks for full signatures and use cases.
onOpenLink(href: string, options?: { target?: 'self' | 'blank' }) => voidInvoked when the iframe wants to open an external link. If omitted, the SDK falls back to a default open behavior.
onDisconnectUser() => voidInvoked when the iframe disconnects the user from CoinTracker.
onPartnerImportTransactionsSuccess() => voidInvoked after a partner-side import succeeds following an OAuth round-trip.
onReceivedAdditionalMetadata(metadata: Record<string, unknown>) => voidInvoked when the iframe forwards metadata back to the parent app. Use this for partner-specific analytics or business-logic hooks.
childrenReactNodeYour application tree. Anything that needs to read kit state via useTaxKit() must be rendered inside the provider.
Minimal example
import { TaxKitProvider } from '@cointracker/tax-kit';
<TaxKitProvider fetchAccessToken={fetchAccessToken}>
<YourApp />
</TaxKitProvider>
Full example
<TaxKitProvider
fetchAccessToken={fetchAccessToken}
theme={{
mode: 'dark',
light: lightTokens,
dark: darkTokens,
}}
options={{
mode: 'production',
flow: 'one-way',
overlayMode: 'responsive',
defaultOpen: false,
helpLinkUrl: 'https://help.your-partner.com/tax',
}}
metadata={{
promoCode: 'PARTNER_2026',
// Arbitrary partner-defined keys pass through opaquely.
sourcePage: 'tax-center',
}}
copy={{
addConnections: {
disclaimer: 'Connecting accounts shares transaction history with CoinTracker for tax calculation.',
},
}}
onOpenLink={(href) => window.open(href, '_blank', 'noopener')}
onDisconnectUser={() => analytics.track('tax_kit_disconnected')}
onPartnerImportTransactionsSuccess={() => refreshImports()}
onReceivedAdditionalMetadata={(meta) => analytics.track('tax_kit_metadata', meta)}
>
<YourApp />
</TaxKitProvider>
Embedding in your own dialog
By default the iframe is a fixed, full-viewport overlay that CoinTracker owns. If you'd rather host the kit inside your own dialog/drawer, pass a container — the iframe then renders inline, filling that node instead of the viewport.
Because most apps mount TaxKitProvider high in the tree (so useTaxKit() works app-wide), the iframe would otherwise render at that spot — not inside a dialog deeper in your routes. Passing a container portals the iframe into the node you choose, without moving the provider. The iframe stays mounted as you open/close the dialog (no re-handshake, no lost state) as long as that node stays mounted.
containerHTMLElement | RefObject<HTMLElement | null> | nullDOM node (or ref) to render the iframe into. When set, the kit renders inline — the iframe is portaled into this node and fills it. Keep the node mounted across open/close (e.g. toggle the dialog's visibility rather than unmounting it) so the iframe preserves its state. Omit it for the default full-viewport overlay.
import { useRef } from 'react';
import { TaxKitProvider } from '@cointracker/tax-kit';
function App() {
// Keep this node mounted; toggle the dialog's visibility instead of unmounting.
const dialogBodyRef = useRef<HTMLDivElement>(null);
return (
<TaxKitProvider
fetchAccessToken={fetchAccessToken}
options={{ overlayMode: 'dialog' }}
container={dialogBodyRef}
>
<YourApp />
<YourDialog>
{/* The Tax Kit iframe fills this node and provides its own chrome. */}
<div ref={dialogBodyRef} style={{ width: '100%', height: '100%' }} />
</YourDialog>
</TaxKitProvider>
);
}
Open and close the kit with useTaxKit().open() / close() as usual, and drive your dialog's visibility from isTaxKitOpen.
Legacy flat props
The pre-2.2.0 flat props (apiBaseUrl, themeContract, themeMode, etc.) are preserved as @deprecated aliases. When both the new shape and a legacy prop are passed, the new shape wins. See Migrating from 2.1 for the full mapping.