Skip to main content
userTourKit
API Reference

@tour-kit/license API

API reference for @tour-kit/license: LicenseProvider, LicenseGate, ProGate, useLicense, useIsPro, useLicenseGate, validateLicenseKey, and related types

domidex01Published Updated

Complete API reference for the license package. Polar.sh-based key validation, domain activation, and React gating components used by Tour Kit Pro packages.

For installation and environment setup see Licensing. Free packages (@tour-kit/core, @tour-kit/react, @tour-kit/hints) never need this package.


Providers

LicenseProvider

Root provider that validates the key against Polar, maintains license state, and exposes it via context. Place once at the top of your app, above any pro components.

import { LicenseProvider } from '@tour-kit/license';

<LicenseProvider
  licenseKey={process.env.NEXT_PUBLIC_TOURKIT_LICENSE_KEY!}
  organizationId={process.env.NEXT_PUBLIC_POLAR_ORG_ID}
  onValidate={(state) => console.log('License:', state.status)}
  onError={(err) => console.error(err)}
>
  {children}
</LicenseProvider>
PropTypeDescription
licenseKeystringPolar license key (TOURKIT-...)
organizationIdstringPolar organization id; required for validation against the API
childrenReactNodeApp tree
onValidate(state: LicenseState) => voidCalled after each successful validation
onError(error: Error) => voidCalled on validation failure

Components

LicenseGate

Conditional rendering based on license tier. Renders children when valid, fallback when gated, loading while validating.

import { LicenseGate } from '@tour-kit/license';

<LicenseGate
  require="pro"
  fallback={<UpgradePrompt />}
  loading={<Skeleton />}
>
  <AdoptionDashboard />
</LicenseGate>
PropTypeDescription
require'pro'Minimum tier required
childrenReactNodeRendered when license is valid
fallbackReactNodeRendered when license is invalid/expired/revoked
loadingReactNodeRendered while validation is in progress

ProGate

Hard gate for Tour Kit Pro packages. Renders a branded placeholder when unlicensed instead of a custom fallback. Used internally by every pro package; expose only if you build your own pro extension.

import { ProGate } from '@tour-kit/license';

<ProGate package="@your-org/private-pro-pkg">
  <YourProComponent />
</ProGate>
PropTypeDescription
packagestringnpm package name shown in the placeholder + console warning
childrenReactNodeRendered when licensed; replaced by branded placeholder otherwise

Dev environments (localhost, 127.0.0.1, *.local) always render children.

LicenseWatermark

Semi-transparent "UNLICENSED" overlay with high z-index. Pro packages render this automatically when unlicensed in production. Inline-styled so it cannot be CSS-disabled.

import { LicenseWatermark } from '@tour-kit/license';

{isUnlicensed && <LicenseWatermark />}

No props.

LicenseWarning

Console-only warning component. Renders nothing visible; calls console.warn once on mount when the license is invalid. Use as a low-friction notification in dev/preview builds.

import { LicenseWarning } from '@tour-kit/license';

<LicenseWarning />
PropTypeDescription
messagestringOverride the default warning message
pricingUrlstringURL surfaced in the warning
dismissiblebooleanWhether the user can dismiss (visual variants only)
onDismiss() => voidDismissal callback
classNamestringClass name override

Hooks

useLicense

Returns the full license context value. Throws if no LicenseProvider is mounted above.

import { useLicense } from '@tour-kit/license';

const { state, refresh, isGated, isLoading, gracePeriodActive } = useLicense();
ReturnTypeDescription
stateLicenseStateFull license state object
refresh() => Promise<void>Force re-validation against Polar
isGatedbooleanWhether pro components should be blocked
isLoadingbooleanWhether validation is in progress
gracePeriodActivebooleanTrue when running on a fresh-but-stale cache after a network failure

useIsPro

Boolean shortcut. Equivalent to useLicense().state.tier === 'pro' && state.status === 'valid'.

import { useIsPro } from '@tour-kit/license';

const isPro = useIsPro();

Returns boolean.

useLicenseGate

Reads the precomputed gating decision from context. Use this inside pro components instead of recomputing logic on every render.

import { useLicenseGate } from '@tour-kit/license';

const { isGated, isLoading } = useLicenseGate();
if (isGated) return <UnlicensedPlaceholder />;
if (isLoading) return null;
ReturnTypeDescription
isGatedbooleanTrue if the component should be blocked
isLoadingbooleanTrue while validation is in progress (avoid flash)

Logic (mirrors LicenseProvider's derived signals):

  • Dev environment (localhost, 127.0.0.1, *.local) → never gated
  • No LicenseProvider in tree → gated
  • status: 'loading' → not gated, isLoading=true (avoid flash)
  • status: 'valid' + pro + render key → not gated
  • status: 'error' + fresh cache → not gated (grace period)
  • status: 'error' + no cache → gated
  • status: 'invalid' | 'expired' | 'revoked' → gated

Utilities

validateLicenseKey

Headless validation against the Polar API. Use in CI scripts or server-side checks.

import { validateLicenseKey } from '@tour-kit/license';

const state = await validateLicenseKey({ key: 'TOURKIT-...', organizationId: '...' });
if (state.status !== 'valid') throw new Error('License invalid');
ParamTypeDescription
configLicenseConfig{ key, organizationId }

Returns Promise<LicenseState>.

getCurrentDomain

Reads window.location.hostname. Returns null on the server.

import { getCurrentDomain } from '@tour-kit/license';

const domain = getCurrentDomain(); // 'app.example.com' | null

Returns string | null.

isDevEnvironment

Checks whether the current hostname is localhost, 127.0.0.1, or matches *.local.

import { isDevEnvironment } from '@tour-kit/license';

if (isDevEnvironment()) {
  console.log('Skipping license check in dev');
}

Returns boolean.


Types

LicenseTier

type LicenseTier = 'free' | 'pro';

LicenseState

Single source of truth for validity. Never derive validity from tier alone — a pro tier with status: 'expired' is invalid.

type LicenseState = {
  status: 'valid' | 'invalid' | 'expired' | 'revoked' | 'loading' | 'error';
  tier: LicenseTier;
  activations: number;
  maxActivations: number;
  domain: string | null;
  expiresAt: string | null;
  validatedAt: number;
  renderKey: string | undefined;
};

renderKey is set only when status === 'valid'. It is the anti-bypass mechanism consumed by <LicenseGate>.

LicenseCache

Shape stored in localStorage. keyHash is set when the cache is written with a license key; readers compare it against the current key's hash and invalidate on mismatch.

type LicenseCache = {
  state: LicenseState;
  cachedAt: number;
  domain: string;
  keyHash?: string;
};

Cache keys are domain-scoped: tourkit:license:{domain}. TTL is 72 hours.

LicenseActivation

type LicenseActivation = {
  id: string;
  licenseKeyId: string;
  label: string;
  createdAt: string;
  modifiedAt: string | null;
};

LicenseError

type LicenseError =
  | 'invalid_key'
  | 'network_error'
  | 'parse_error'
  | 'activation_limit_reached'
  | 'domain_mismatch';

LicenseConfig

type LicenseConfig = {
  key: string;
  organizationId: string;
};

LicenseContextValue

type LicenseContextValue = {
  state: LicenseState;
  refresh: () => Promise<void>;
  isGated: boolean;
  isLoading: boolean;
  gracePeriodActive: boolean;
};

LicenseGateResult

type LicenseGateResult = {
  isGated: boolean;
  isLoading: boolean;
};

LicenseProviderProps

type LicenseProviderProps = {
  licenseKey: string;
  organizationId?: string;
  children: React.ReactNode;
  onValidate?: (state: LicenseState) => void;
  onError?: (error: Error) => void;
};

LicenseGateProps

type LicenseGateProps = {
  require: 'pro';
  children: React.ReactNode;
  fallback?: React.ReactNode;
  loading?: React.ReactNode;
};

ProGateProps

type ProGateProps = {
  package: string;
  children: React.ReactNode;
};

LicenseWarningProps

type LicenseWarningProps = {
  message?: string;
  pricingUrl?: string;
  dismissible?: boolean;
  onDismiss?: () => void;
  className?: string;
};

Polar Response Types

Raw Polar API response shapes (after camelCase transform). Subject to upstream Polar SDK changes.

type PolarValidateResponse = {
  id: string;
  organizationId: string;
  status: 'granted' | 'revoked' | 'disabled';
  key: string;
  limitActivations: number | null;
  usage: number;
  validations: number;
  lastValidatedAt: string;
  expiresAt: string | null;
  activation: {
    id: string;
    licenseKeyId: string;
    label: string;
    meta: Record<string, unknown>;
    createdAt: string;
    modifiedAt: string | null;
  } | null;
};

type PolarActivateResponse = {
  id: string;
  licenseKeyId: string;
  label: string;
  meta: Record<string, unknown>;
  createdAt: string;
  modifiedAt: string | null;
  licenseKey: {
    id: string;
    organizationId: string;
    status: 'granted' | 'revoked' | 'disabled';
    limitActivations: number | null;
    usage: number;
    limitUsage: number | null;
    validations: number;
    lastValidatedAt: string;
    expiresAt: string | null;
  };
};

Internal Exports

These are exported for advanced use; most consumers should not need them.

ExportTypeNotes
LicenseContextContext<LicenseContextValue | null>Raw React context. Prefer useLicense()
LicenseRenderContextContext<string | undefined>Internal render-key context used by <LicenseGate> for anti-bypass