> ## Documentation Index
> Fetch the complete documentation index at: https://docs.priceos.com/llms.txt
> Use this file to discover all available pages before exploring further.

# React

> Integrate PriceOS on the frontend using our React hooks.

Our React hooks make it a lot easier to evaluate feature access and usage on the frontend.

Since PriceOS can only be safely used on the backend, using these hooks still requires server-side setup, but we've created helpers to make this really easy for you.

<Warning>Make sure your API key is only used server side. It should never be used client side.</Warning>

<Note>Be sure to read [Linking Stripe Customers](/linking-stripe-customers) to learn how to link Stripe customers to your own internal ID.</Note>

<Steps>
  <Step title="Install">
    [Create an API key](https://app.priceos.com/settings/api-keys) in the PriceOS dashboard and add to your environment variables.

    ```bash title=".env" theme={null}
    PRICEOS_API_KEY=pos_ab234cdef...
    ```

    <CodeGroup>
      ```bash npm theme={null}
      npm i priceos
      ```

      ```bash pnpm theme={null}
      pnpm add priceos
      ```

      ```bash yarn theme={null}
      yarn add priceos
      ```

      ```bash bun theme={null}
      bun add priceos
      ```
    </CodeGroup>
  </Step>

  <Step title="Add server side handlers">
    <Tabs>
      <Tab title="Next.js">
        <CodeGroup>
          ```ts App Router theme={null}
          // app/api/priceos/[...path]/route.ts
          import { priceosHandler } from "priceos/next";

          export const { GET, POST } = priceosHandler({
            identifyCustomer: async (req: Request) => {
              // Return a customer identity from your server
            },
          });
          ```

          ```ts Pages Router theme={null}
          // pages/api/priceos/[...path].ts
          import { priceosPagesHandler, type PriceOSPagesRequest } from "priceos/next";

          export default priceosPagesHandler({
            identifyCustomer: async (req: PriceOSPagesRequest) => {
              // Return a customer identity from your server
            },
          });
          ```
        </CodeGroup>

        <Tip>If you need to include an [auth token](/react/priceos-provider#param-get-bearer-token) from the client, you can do so on the [Provider](/react/priceos-provider).</Tip>
      </Tab>

      <Tab title="Other backends">
        <Note>We are working on adding more helpers for other backends. If there's a backend you would like us to create helpers for, [please reach out](mailto:andrew@priceos.com?subject=Backend%20integration\&body=Hey%20Andrew%2C%0A%0AI%20would%20love%20it%20if%20PriceOS%20supported%20%5Benter%20your%20desired%20backend%20and%2For%20other%20relevant%20information%5D%0A%0AThanks%2C) and let us know.</Note>
        Depending on which react hooks you want to use, you'll need to implement the following routes...

        | Hook                | Endpoint(s) you must add           |
        | ------------------- | ---------------------------------- |
        | `useCustomer`\*     | `GET /priceos/v1/customer`         |
        | `useFeatureAccess`  | `GET /priceos/v1/feature-access`   |
        | `useTrackUsage`     | `POST /priceos/v1/usage`           |
        | `useUsageEvents`    | `GET /priceos/v1/usage-events`     |
        | `useCheckout`       | `POST /priceos/v1/checkout`        |
        | `useCustomerPortal` | `POST /priceos/v1/customer-portal` |

        <div><small>\* If you use `trackUsage` from `useCustomer`, add `POST /priceos/v1/usage`. If you use `checkout` from `useCustomer`, add `POST /priceos/v1/checkout`.</small></div>

        <Warning>Make sure you resolve `customerId` from your server-side auth/session. </Warning>

        <Tip>If you need to include an [auth token](/react/priceos-provider#param-get-bearer-token) from the client, you can do so on the [Provider](/react/priceos-provider).</Tip>

        ### useCustomer()

        Requires a `GET /priceos/v1/customer` endpoint.

        <CodeGroup>
          ```ts Express theme={null}
          app.get("/priceos/v1/customer", async (req, res) => {
            const customerId = await identifyCustomerFromSession(req);
            const data = await priceos.customers.get(customerId);
            res.json(data);
          });
          ```

          ```ts Fastify theme={null}
          app.get("/priceos/v1/customer", async (request, reply) => {
            const customerId = await identifyCustomerFromSession(request);
            const upstream = await callPriceOS(`/v1/customers/${encodeURIComponent(customerId)}`);
            reply.send(await upstream.json());
          });
          ```

          ```py FastAPI theme={null}
          @app.get("/priceos/v1/customer")
          async def get_customer(request: Request):
              customer_id = await identify_customer_from_session(request)
              return await call_priceos("GET", f"/v1/customers/{customer_id}")
          ```
        </CodeGroup>

        ### useFeatureAccess()

        Requires a `GET /priceos/v1/feature-access` endpoint.

        <CodeGroup>
          ```ts Express theme={null}
          app.get("/priceos/v1/feature-access", async (req, res) => {
            const customerId = await identifyCustomerFromSession(req);
            const data = await priceos.features.getAccess(customerId);
            res.json(data);
          });
          ```

          ```ts Fastify theme={null}
          app.get("/priceos/v1/feature-access", async (request, reply) => {
            const customerId = await identifyCustomerFromSession(request);
            const upstream = await callPriceOS(
              `/v1/feature-access?customerId=${encodeURIComponent(customerId)}`
            );
            reply.send(await upstream.json());
          });
          ```

          ```py FastAPI theme={null}
          @app.get("/priceos/v1/feature-access")
          async def get_feature_access(request: Request):
              customer_id = await identify_customer_from_session(request)
              return await call_priceos("GET", f"/v1/feature-access?customerId={customer_id}")
          ```
        </CodeGroup>

        ### useTrackUsage()

        Requires a `POST /priceos/v1/usage` endpoint.

        <CodeGroup>
          ```ts Express theme={null}
          app.post("/priceos/v1/usage", async (req, res) => {
            const featureKey = req.body?.featureKey;
            const amount = req.body?.amount ?? 1;
            if (amount <= 0) // Return an error
            const customerId = await identifyCustomerFromSession(req);
            const data = await priceos.usage.track({
              customerId,
              featureKey,
              amount,
              eventKey: req.body.eventKey,
              occurredAt: req.body.occurredAt,
              metadata: req.body.metadata,
            });
            res.json(data);
          });
          ```

          ```ts Fastify theme={null}
          app.post("/priceos/v1/usage", async (request, reply) => {
            const body = request.body as any;
            const featureKey = body?.featureKey;
            const amount = body?.amount ?? 1;
            if (amount <= 0) // Return an error
            const customerId = await identifyCustomerFromSession(request);
            const upstream = await callPriceOS("/v1/usage", {
              method: "POST",
              body: JSON.stringify({
                customerId,
                featureKey,
                amount,
                eventKey: body.eventKey,
                occurredAt: body.occurredAt,
                metadata: body.metadata,
              }),
            });
            reply.send(await upstream.json());
          });
          ```

          ```py FastAPI theme={null}
          @app.post("/priceos/v1/usage")
          async def track_usage(request: Request):
              body = await request.json()
              feature_key = body.get("featureKey")
              amount = body.get("amount", 1)
              if amount <= 0:
                  pass  # Return an error
              customer_id = await identify_customer_from_session(request)
              return await call_priceos(
                  "POST",
                  "/v1/usage",
                  {
                      "customerId": customer_id,
                      "featureKey": feature_key,
                      "amount": amount,
                      "eventKey": body.get("eventKey"),
                      "occurredAt": body.get("occurredAt"),
                      "metadata": body.get("metadata"),
                  },
              )
          ```
        </CodeGroup>

        <Warning>Don't allow negative numbers from client for security reasons. </Warning>

        ### useUsageEvents()

        Requires a `GET /priceos/v1/usage-events` endpoint.

        <CodeGroup>
          ```ts Express theme={null}
          app.get("/priceos/v1/usage-events", async (req, res) => {
            const featureKey = req.query.featureKey
            const customerId = await identifyCustomerFromSession(req);
            const data = await priceos.usage.listEvents({
              customerId,
              featureKey,
              period: req.query.period,
              offset: req.query.offset,
              limit: req.query.limit,
              customRange:
                req.query.start && req.query.end
                  ? { start: req.query.start, end: req.query.end }
                  : undefined,
            });
            res.json(data);
          });
          ```

          ```ts Fastify theme={null}
          app.get("/priceos/v1/usage-events", async (request, reply) => {
            const query = request.query as any;
            const customerId = await identifyCustomerFromSession(request);
            const upstream = await callPriceOS("/v1/usage/events", {
              method: "POST",
              body: JSON.stringify({
                customerId,
                featureKey: query.featureKey,
                period: query.period,
                offset: query.offset,
                limit: query.limit,
                customRange:
                  query.start && query.end
                    ? { start: query.start, end: query.end }
                    : undefined,
              }),
            });
            reply.send(await upstream.json());
          });
          ```

          ```py FastAPI theme={null}
          @app.get("/priceos/v1/usage-events")
          async def list_usage_events(request: Request):
              q = request.query_params
              customer_id = await identify_customer_from_session(request)
              return await call_priceos(
                  "POST",
                  "/v1/usage/events",
                  {
                      "customerId": customer_id,
                      "featureKey": q.get("featureKey"),
                      "period": q.get("period"),
                      "offset": q.get("offset"),
                      "limit": q.get("limit"),
                      "customRange": {"start": q.get("start"), "end": q.get("end")}
                      if q.get("start") and q.get("end")
                      else None,
                  },
              )
          ```
        </CodeGroup>

        ### useCheckout()

        Requires a `POST /priceos/v1/checkout` endpoint.

        <CodeGroup>
          ```ts Express theme={null}
          app.post("/priceos/v1/checkout", async (req, res) => {
            const body = req.body ?? {};
            const customerId = await identifyCustomerFromSession(req);

            const data = await priceos.customers.createCheckout(customerId, {
              productKey: body.productKey,
              stripePriceId: body.stripePriceId,
              successUrl: body.successUrl,
              cancelUrl: body.cancelUrl,
              metadata: body.metadata,
              customerInfo: body.customerInfo,
              checkoutParams: body.checkoutParams,
            });
            res.json(data);
          });
          ```

          ```ts Fastify theme={null}
          app.post("/priceos/v1/checkout", async (request, reply) => {
            const body = request.body as any;
            const customerId = await identifyCustomerFromSession(request);

            const upstream = await callPriceOS(`/v1/customers/${encodeURIComponent(customerId)}/checkout`, {
              method: "POST",
              body: JSON.stringify({
                productKey: body.productKey,
                stripePriceId: body.stripePriceId,
                successUrl: body.successUrl,
                cancelUrl: body.cancelUrl,
                metadata: body.metadata,
                customerInfo: body.customerInfo,
                checkoutParams: body.checkoutParams,
              }),
            });
            reply.send(await upstream.json());
          });
          ```

          ```py FastAPI theme={null}
          @app.post("/priceos/v1/checkout")
          async def checkout(request: Request):
              body = await request.json()
              customer_id = await identify_customer_from_session(request)
              return await call_priceos(
                  "POST",
                  f"/v1/customers/{customer_id}/checkout",
                  {
                      "productKey": body.get("productKey"),
                      "stripePriceId": body.get("stripePriceId"),
                      "successUrl": body.get("successUrl"),
                      "cancelUrl": body.get("cancelUrl"),
                      "metadata": body.get("metadata"),
                      "customerInfo": body.get("customerInfo"),
                      "checkoutParams": body.get("checkoutParams"),
                  },
              )
          ```
        </CodeGroup>

        ### useCustomerPortal()

        Requires a `POST /priceos/v1/customer-portal` endpoint.

        <CodeGroup>
          ```ts Express theme={null}
          app.post("/priceos/v1/customer-portal", async (req, res) => {
            const customerId = await identifyCustomerFromSession(req);
            const data = await priceos.customers.createPortal(customerId);
            res.json(data);
          });
          ```

          ```ts Fastify theme={null}
          app.post("/priceos/v1/customer-portal", async (request, reply) => {
            const customerId = await identifyCustomerFromSession(request);
            const upstream = await callPriceOS(
              `/v1/customers/${encodeURIComponent(customerId)}/customer_portal`,
              { method: "POST" }
            );
            reply.send(await upstream.json());
          });
          ```

          ```py FastAPI theme={null}
          @app.post("/priceos/v1/customer-portal")
          async def customer_portal(request: Request):
              customer_id = await identify_customer_from_session(request)
              return await call_priceos("POST", f"/v1/customers/{customer_id}/customer_portal")
          ```
        </CodeGroup>
      </Tab>
    </Tabs>
  </Step>

  <Step title="Add provider component">
    <CodeGroup>
      ```tsx Next.js theme={null}
      // layout.tsx or wherever your providers are
      import { PriceOSProvider } from "priceos/react";

      <PriceOSProvider>
        {/* children */}
      </PriceOSProvider>;
      ```

      ```tsx Other Backends theme={null}
      import { PriceOSProvider } from "priceos/react";

      <PriceOSProvider backendUrl="https://api.yourapp.com/priceos">
        {/* app */}
      </PriceOSProvider>;
      ```
    </CodeGroup>

    For full Provider configuration details, see [PriceOSProvider](/react/priceos-provider).
  </Step>

  <Step title="Generate types (optional, but highly recommended)">
    <CodeGroup>
      ```bash npm theme={null}
      npx priceos generate-types
      ```

      ```bash pnpm theme={null}
      pnpm dlx priceos generate-types
      ```

      ```bash yarn theme={null}
      yarn dlx priceos generate-types
      ```

      ```bash bun theme={null}
      bunx priceos generate-types
      ```
    </CodeGroup>

    This will generate a local types file that you can include so all of your features are fully typed.

    Run with `--help` to see more configuration options.

    <Note>
      Remember to regenerate types any time you create a new feature.
    </Note>
  </Step>

  <Step title="Read feature access">
    <CodeGroup>
      ```tsx Boolean features theme={null}
      import { useFeatureAccess } from "priceos/react";
      import type { MyFeatures } from "./priceos.types";

      export function PrioritySupportButton() {
        const { hasAccess } = useFeatureAccess<MyFeatures>("priority_support");

        const onClick = () => {
          if (hasAccess){
            // Allow access
          }
        }

        return <button onClick={onClick}>Contact support</button>;
      }
      ```

      ```tsx Limit features theme={null}
      import { useFeatureAccess } from "priceos/react";
      import type { MyFeatures } from "./priceos.types";

      export function AddTeamSeatsButton() {
        const { hasAccess, usage } = useFeatureAccess<MyFeatures>("team_seats");

        const onClick = () => {
          const hasSeatAvailable = hasAccess && usage?.hasReachedLimit !== true;

          if (hasSeatAvailable) {
            // Allow access
          }
        };

        return <button onClick={onClick}>Add teammate</button>;
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="Track usage (optional)">
    Only if you're using PriceOS to track your customers' usage for limit features.

    <CodeGroup>
      ```tsx Typed theme={null}
      import { useTrackUsage } from "priceos/react";
      import type { MyFeatures } from "./priceos.types";

      export function SeatButton() {
        const { trackUsage } = useTrackUsage<MyFeatures>();

        async function handleClick() {
          // Add teammate first
          await trackUsage({
            featureKey: "team_seats",
            amount: 1,
            eventKey: `add_team_seat_${Date.now()}`,
          });
        }

        return <button onClick={handleClick}>Add teammate</button>;
      }
      ```
    </CodeGroup>

    <Note>
      If the action happens on your backend (for example in a server action, webhook, or background job), it usually makes more sense to track usage on the backend instead.
    </Note>

    <Warning>
      `useTrackUsage` with the built-in Next.js handlers (`priceos/next`) only accepts positive `amount` values (so that clients can't reverse their usage). For negative adjustments (for example when removing a team seat), track usage from your backend via the Node.js SDK or REST API endpoints.
    </Warning>

    For more info see [Tracking usage](/tracking-usage).
  </Step>
</Steps>
