Farther ShoreDocs
Go to Farther Shore
What is FartherShore
Install the CLI
Quickstart
Core concepts
The @Product class
Meters & resources
Features & routes
Capabilities & entitlements
Plans & pricing
The Manifest IR
Bring your own backend
Transport modes
Metering & verification
Runtime tokens
Frontend SDK
The two ways to ship a UICreate a clientReact: the provider + hooksWhere requests go, and how they authServer usage (SSR)ErrorsNext
Root & data components
Auth & sessions
Entitlement gates
Connect Stripe
Subscriptions & usage
Plan changes & grandfathering
Billing strategies
Apply & deploy
Environments
Migrations
Docs versions & archive
Operate with an agent
Operation classes
MCP server
End-to-end via CLI/MCP
CLI reference
@farthershore/product
@farthershore/backend
@farthershore/farthershore-js
Environment variables
Response & deny codes
Add a metered capability
Gate a feature
Change a price
Prepaid credits
Meter AI tokens
Operate via an agent
Prepare for launch
Status
Docs/Build the frontend/Frontend SDK

Frontend SDK

Wire a portal or custom UI to the platform with @farthershore/farthershore-js.

PreviousRuntime tokensNextRoot & data components

@farthershore/farthershore-js is the browser integration layer between your frontend and the platform. Your UI expresses intent — "list my keys", "show usage", "call the cron-jobs feature" — and the SDK decides where each request goes (Core for platform state, the Gateway for your product's features), how it's authenticated, and what host/env scoping headers to attach. Frontend code never sees a Core URL, a Gateway URL, your backend origin, or an auth endpoint.

It ships in lockstep with @farthershore/product and @farthershore/backend: pin all three to the same version.

pnpm add @farthershore/farthershore-js   # React is an optional peer (for /react)

Your product contract (plans, pricing, routes, meters) is authored as a @Product decorated class in the repo's product/ tree — TypeScript, never YAML. The UI lives in frontend/. This page is about the UI; see Products and Plans for the contract.

The two ways to ship a UI

Every product gets a generic managed portal for free — the platform serves it from R2 with no code from you. You only touch the frontend when you want to customize it:

  1. Managed / template portal — the default. Nothing to build; it renders your plans, usage, keys, and billing out of the box. See Portals.
  2. A custom build — edit the product repo's frontend/ tree (the SDK-wired scaffold), push, and the platform rebuilds it into a custom release and serves it instead. You author with the SDK below.

The scaffold is already wired: one client under one <FartherShoreRoot> rendering the zero-prop data components.

Create a client

createFartherShoreClient builds the client. In the browser, coreUrl is usually the only field — portalHost defaults to window.location.host, and bootstrap() discovers the product, environment, and plans for that host.

import { createFartherShoreClient } from "@farthershore/farthershore-js";

const fs = createFartherShoreClient({
  coreUrl: "https://core.farthershore.com",
});

// Discover the product/env/plans for this host (memoized; safe to re-call).
const { product, plans } = await fs.bootstrap();

// Read platform state through typed resources (→ Core).
const keys = await fs.keys.list();          // ApiKey[]
const usage = await fs.usage.snapshot();     // { summary, events, billingBasis, … }

// Call a product-defined feature (→ Gateway) by name + path — no Gateway URL.
fs.setApiKey("fsk_live_…");
const jobs = await fs.feature("cron-jobs").json("/v1/cron-jobs");

createFartherShoreClient.fromEnv() — the scaffold entry

Portals served through the platform edge need no config at all: the edge injects window.__FS_CONFIG__ (the environment's coreUrl plus the Clerk connection) into the served HTML, and the SDK falls back to it. The scaffold uses .fromEnv() so the same code works in prod (injected) and local dev (bundler env override):

// The frontend ships env-agnostic; in prod the edge injects the config. The
// override is a dev/self-host escape hatch — undefined in prod, so it's ignored.
const fs = createFartherShoreClient.fromEnv({
  coreUrl: import.meta.env.VITE_FS_CORE_URL, // dev/self-host only
});

Explicit config always wins. Everywhere there's no injected channel (local dev, SSR, self-hosting), coreUrl is required, or the client throws an actionable coreUrl is required error.

Config reference

createFartherShoreClient({
  coreUrl,        // platform Core base URL — optional on platform-served portals
  portalHost,     // defaults to window.location.host
  productId,      // optional — bootstrap() discovers it
  environmentId,  // ephemeral/preview env scoping
  organizationId, // org-owned subscriptions
  gatewayUrl,     // override (else derived from the product's runtimeHostname)
  apiKey,         // consumer key for Gateway feature calls
  getToken,       // () => session bearer (Clerk) | null
  fetch,          // injectable (tests / SSR)
});

React: the provider + hooks

The hooks live in the /react subpath (react is an optional peer). Wrap your tree in FartherShoreProvider once at the root, then every read is a hook returning { data, loading, error, refresh } plus its mutations.

import { createFartherShoreClient } from "@farthershore/farthershore-js";
import {
  FartherShoreProvider,
  useApiKeys,
  useUsage,
} from "@farthershore/farthershore-js/react";

// Build the client at module scope (a stable reference — never per render).
const fs = createFartherShoreClient.fromEnv({
  coreUrl: import.meta.env.VITE_FS_CORE_URL,
});

function App() {
  return (
    <FartherShoreProvider client={fs}>
      <Portal />
    </FartherShoreProvider>
  );
}

function Keys() {
  const { data, loading, create, revoke } = useApiKeys();
  if (loading) return <p>Loading…</p>;
  return <ul>{data?.map((k) => <li key={k.id}>{k.label}</li>)}</ul>;
}

FartherShoreProvider takes either a pre-built client (preferred — you own its lifetime) or a config it builds internally (pass a stable object so the client isn't recreated each render).

The hooks: useBootstrap, useProduct, useSession, useApiKeys, useUsage, useBilling, usePlans, useCreditBalance, useEntitlements, useFeatureGate, useCapability, useFeature(name), plus useFartherShore() for the raw client. They're thin wrappers — no duplicated logic.

For a batteries-included root that also handles bootstrap, theming-from-branding, and managed auth, prefer <FartherShoreRoot> over a bare <FartherShoreProvider>. It wraps the provider and adds the gate.

Where requests go, and how they auth

NamespaceRoutes toAuth
fs.bootstrap(), fs.productCore (public resolve)none (public)
fs.keys, fs.usage, fs.billing, fs.plans, fs.authCoreconsumer session token (Authorization: Bearer)
fs.feature(name) / fs.invoke(path)Gatewayconsumer API key (fsk_…)
  • Core calls use the session token. The host owns the session: Clerk envs pass getToken; persona/preview envs call fs.auth.signIn({ apiKey: "fsk_test_…" }). See Auth & sessions.
  • Gateway (feature) calls use a fsk_ API key: fs.setApiKey(key). The gateway host is discovered at bootstrap.

Server usage (SSR)

On a server there's no window/location and no ambient session. Use createServerClient — a documented alias that makes that contract explicit: pass portalHost and fetch, and resolve the per-request session via getToken.

import { createServerClient } from "@farthershore/farthershore-js";

const fs = createServerClient({
  coreUrl: process.env.FS_CORE_URL!,
  portalHost: "cron.farthershore.com",        // no window.location on the server
  getToken: () => sessionTokenForThisRequest, // per-request bearer
  fetch,                                       // Node 18+ global, or injected
});

const sub = await fs.billing.subscription();

Errors

Reads and mutations throw typed errors you can branch on — FartherShoreApiError (with a .code from FS_DENY_CODES), LimitExceededError, FeatureNotGrantedError, FartherShoreRateLimitedError, and more. Pair a LimitExceededError with <UpgradePrompt>, a FeatureNotGrantedError with a <FeatureGate> upsell. See response codes for the wire vocabulary.

Next

  • Root & data components — <FartherShoreRoot> and the zero-prop cards.
  • Auth & sessions — sign-in, sessions, auth-gated routes.
  • Entitlement gates — gate UI by capability and feature.