Hooks Reference
Every read hook returns a handle with the same shape and reads through the session on SchematicProvider:
The three states a component renders follow from those fields. data === undefined && isPending is the first load. data === undefined && error is a failure with nothing to show. data !== undefined is ready, and a set error alongside it is a failure to report without replacing what is there.
Mounted outside any provider, every hook reports the missing source on its error rather than throwing, so a misconfigured page fails the same way a request would. Under a provider whose session is still undefined, the hooks stay pending instead, apart from useCatalog, which reads the public catalog meanwhile. Under session={null} they settle empty, again apart from useCatalog. The hooks never fetch during server rendering: without initialData they report pending on the server and load on the client.
The company endpoints, GET /company and the /company/* reads under it, are behind the company-context-api flag, per account, and answer 404 {"error":"Not found"} until it is on. A 404 there is the flag or the token, not a company that could not be found; ask Schematic to enable the flag for the account. Hooks surface it as an error rather than an empty answer, so a page cannot quietly report “nothing here” over real data. httpStatus(error) from @schematichq/schematic-components/elements reads the status off a billing error, and returns undefined for anything that was not an HTTP response.
The hooks are also re-exported from @schematichq/schematic-components/elements, so a file that imports a derivation can take its hook from the same place. Timestamps on every resource arrive as Date objects.
useCheckout
A persisted checkout on POST /checkouts, PUT /checkouts/{checkout_id} and POST /checkouts/{checkout_id}/finalize. The first setSelections opens it and every later one replaces the cart and re-prices on the server; rapid calls fold into the newest, a stale version is replayed once, and finalize charges under the session the last price handed out, then reloads every billing resource. A refused finalize puts the server’s list on problems and rejects, and checkoutProblemsOf(error) returns that list. { checkoutId } resumes a checkout, which js 1.16.1 and later read back before the first write. While isPricing is true, snapshot and problems are still the previous cart’s, so dim them until the write lands rather than show the old plan’s price under the new plan’s name. The drop-in dialog opens through useCheckoutLauncher. Checkout API describes the resource and the wire shapes.
useCheckoutLauncher
Exported from @schematichq/schematic-components/elements alone, beside the dialog it opens. CheckoutLauncherProvider renders one <Checkout /> and useCheckoutLauncher() below it opens that dialog on config (selection, steps, display, catalogId), each section laid over the provider’s defaults; every open starts a new cart. Outside a provider open does nothing and says so in development. Checkout shows the pattern.
useTaxIds
The tax IDs on the company’s billing customer, read from GET /checkout/tax-id: each has an id, its provider type such as eu_vat or us_ein, value, country and verificationStatus. data is empty when there are none. update posts one to POST /checkout/tax-id and reloads the list; it rejects with the failure and records it on mutationError. A finalize reads the tax ID from the billing customer rather than from the cart, so save it first, as the drop-in does when the field is left.
useCompany
The company and what it is subscribed to, read from GET /company:
Plan Manager and the Unsubscribe Button read this, and so does the Pricing Table once a session starts.
useCatalog
Plans, add-ons, and credit bundles with prices per period and currency, tier shapes, and entitlement summaries, for the environment’s catalog or the one catalogId names. The hook keys by value, so an inline object literal is fine.
The source follows the session. With a session it reads the company’s view from GET /catalog/view (or /catalogs/:catalog_id/view), which adds each plan’s and add-on’s standing: current, valid with an invalidReason and usageViolations when false, and companyCanTrial, plus capabilities.checkout for whether the company can check out at all. With no session, or while the session is still undefined, it reads GET /public/catalog (or /public/catalogs/:catalog_id) with the provider’s publishable key, so unlike the other hooks it does not wait on your auth. A session starting swaps the public catalog for the company’s view. data.source says which one you have; the public catalog carries no standing, so check it before reading current or valid.
Each plan’s autoTopups lists its included credits that top up: planCreditGrantId, which a checkout’s autoTopupOverrides name, creditId, the plan’s amount, thresholdCredits and enabled, the company’s own companyAmount, companyThresholdCredits and companyEnabled where availability is user_controlled, and unitPrice, the credit’s price a top-up is charged at. It carries no credit name: take it from the plan’s includedCreditGrants entry with the same creditId, or let deriveCheckout do both, since its autoTopups rows carry creditName and costText, the cost of one top-up at amount times the unit price.
A 404 from either means the account is not enabled for catalogs (the multi-product-catalog flag), or that catalogId names no catalog of the account’s. The Pricing Table reads this, and derivePricingTable takes it whole.
useFeatureUsage
One row per feature the company is entitled to, read from GET /company/usage. An empty list is a loaded answer that means the company is entitled to nothing.
Included Features, Metered Features, Plan Manager, and the Unsubscribe Button all read this, so a page showing all four makes one request.
useFeatureUserUsage
One event-based feature’s usage by user over its metric period, read from GET /company/usage/:feature_id/users. users holds up to FEATURE_USER_USAGE_LIMIT (20) of the heaviest users, each with userId, name, usage, share, and lastSeen. count is how many users there are in all, total is the period’s usage, startTime and endTime bound the period, and unattributed is usage from events sent without a user. A feature that is not an event-based entitlement of the company answers 404.
useCreditBalances
One balance per credit, read from GET /company/credits. An empty list is a loaded answer that means the company holds no credits and its plan draws on none.
A credit the plan draws on that the company holds none of is listed with a zero balance and no grants. Expired and zeroed-out grants leave the list on the server.
useCreditUserUsage
One credit’s consumption by user over the span of its live grants, read from GET /company/credits/:credit_id/users. users holds up to FEATURE_USER_USAGE_LIMIT (20) of the heaviest users, each with userId, name, used, and share, alongside count, total, startTime, endTime, and unattributed. A credit the company holds no live grant of comes back empty; an unknown credit answers 404.
useUpcomingInvoice
The next bill: amountDue and subtotal in minor units, currency, dueDate, the active discounts, and customerBalanceApplied and customerBalanceRemaining computed on the server. null is a loaded answer that means nothing to bill; only undefined means the resource has not loaded.
useInvoices
The invoice history, INVOICE_PAGE_SIZE (12) invoices a page. count is how many invoices the query matches on the server, so a list showing four rows can say how many the company has. hasMore is that count set against the rows loaded, and an empty page ends the list regardless. loadMore appends the next page, settles when it lands, and never rejects; read error for the failure, and the rows already fetched stay put. isPending is true while the page is on the wire, so a button can disable itself without awaiting anything, and clicking twice makes one request. refetch re-requests every page loaded so far, not only the first.
{ includePending: true } includes unpaid invoices that are not yet due. Each distinct query is its own list with its own paging, so useInvoices({ includePending: true }) and useInvoices() never share rows. The hook keys by value, so an inline object literal is fine.
An empty history is a successful response with count: 0. A 404 surfaces on error, so a page cannot report “no invoices” over a real failure.
Each Invoice:
The optional fields arrive as null or are absent, so read them with ?? rather than a strict null check.
usePaymentMethods
Every payment method on file. A company with none is a successful response with an empty list, so an empty data is a loaded answer. A 404 surfaces on error.
setDefault and remove each reload the list once they land, and reject with the failure while also recording it on mutationError, so a component can await the promise or read the field, whichever it prefers. A write’s failure never lands on error, which stays the list’s own. setDefault takes the method’s externalId, the billing provider’s id; remove takes Schematic’s id.
Each PaymentMethod:
The optional fields arrive as null or are absent, so read them with ?? rather than a strict null check.
The server decides both flags, so a component never re-derives the provider’s rules. Removing the default promotes nothing; the list then has no default until someone sets one. Call setDefault with a new method’s id once Stripe confirms it; the server only makes a customer’s first method the default.
useSetupIntent
Mints a Stripe SetupIntent for adding a payment method. Each call makes a new intent, because an intent is single-use. The hook holds no state; create rejects with the failure.
Load Stripe with schematicPublishableKey and stripeAccount: accountId when accountId is set, else with publishableKey. Mount Stripe’s Payment Element on the client secret, confirm the setup, then make the new method the default with usePaymentMethods().setDefault, which also reloads the list. Checkout with Stripe Elements walks through the Stripe side.
useUnsubscribe
Sets the company’s subscription to cancel at the end of its billing period. Once it lands, whichever of the company, the upcoming invoice, and the credit balances are loaded reload, so useCompany shows the cancellation date on its next render. unsubscribe rejects with the failure and records it on mutationError. The server refuses when the company has no subscription, when the subscription is already set to cancel at period end, or when another Stripe app manages it.
useInvalidateBillingData
Returns a function that reloads every loaded billing resource while keeping its current data on screen. Call it after an action that changes what the company is billed for, such as a plan change your own backend makes.
Locale and copy
The drop-ins format and word everything through the locale, strings, and translate on SchematicProvider. These read the same settings, so a component of your own follows the same overrides. Localization covers the settings themselves.
The useSchematic* hooks return the provider’s settings as given: useSchematicLocale is undefined when the provider sets no locale, and outside any provider useSchematicI18n returns an empty object. useResolvedLocale applies the drop-ins’ fallback: the argument, else the provider’s locale, else the viewer’s. The viewer’s language is left out of a server render and its hydration, so set locale on the provider for a server-rendered page.
useTranslator returns the function the drop-ins word their copy with. It looks a key up in the strings you pass, then the provider’s strings, then the provider’s translate, then the English default, so it always returns a string. The customer portal recipe uses it to word a payment method’s label.
SchematicI18nProvider sets locale, translate, strings, and onMissingString for the part of the tree below it. SchematicProvider renders one, so you need it only to change the settings for one part of a page. A nested provider merges with the one above it: one that sets only locale keeps the translate above it, and strings combine key by key.
Testing and fixtures
BillingDataProvider serves fixed data to the hooks with no network and no session, so a component can be rendered in a test or a story with the rows you choose.
data takes a value per resource: catalog, publicCatalog, company, featureUsage, creditBalances, upcomingInvoice, paymentMethods, and invoices. A missing key reports as pending. Given publicCatalog without catalog, useCatalog reports the public source, as on a signed-out page. The per-user breakdowns are not part of data: they have their own props, featureUserUsage and creditUserUsage, each keyed by feature or credit id. status overrides a resource’s isPending or error, so the loading and failure states render on demand, and onRefetch and onLoadMoreInvoices receive the calls a component makes. actions stands in for the writes: setDefaultPaymentMethod, removePaymentMethod, createSetupIntent, and unsubscribe receive the calls usePaymentMethods, useSetupIntent, and useUnsubscribe make, and whatever they resolve or reject with is what the component sees. The provider also accepts locale, strings, translate, and onMissingString, so a fixture can exercise copy overrides too.
Flags and entitlement checks
The hooks above read billing state. Feature flags and entitlement checks keep their own hooks on the same provider: useSchematicFlag, useSchematicEntitlement, useSchematicIsPending, useSchematicPlan, useSchematicCreditBalance, useSchematicCreditSpendPolicies, and useSchematicEvents. See the React SDK.