Skip to main content
The /prices route displays subscription plans loaded from configuration and allows the authenticated customer to select a plan. Selecting a plan creates a Stripe subscription and redirects the user to the payment confirmation flow.
Both the loader and action read the session cookie to retrieve the current customerId. Requests without a valid session containing a customerId will not be able to create a subscription.

GET /prices

Loads the list of available prices from application configuration and returns the current customer ID from the session.

Loader source

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const session = await getSession(request.headers.get("Cookie"));
  const { prices } = await getConfig();
  return { prices, customerId: session.get("customerId") };
};

Response data

prices
object[]
required
Array of price objects returned by getConfig(). Each price includes expanded product data from Stripe (name, description, metadata).
customerId
string
The Stripe customer ID read from the active session. Used by the UI to associate the selected plan with the correct customer when the form is submitted.

POST /prices

Accepts a selected price ID, creates an incomplete Stripe subscription for the session customer, and redirects to the Stripe Elements payment confirmation page.

Request

Content-Type: multipart/form-data
priceId
string
required
The Stripe price ID of the plan the customer has selected (e.g. price_1Abc123). Must correspond to a recurring price in your Stripe account.

Action source

export const action = async ({ request }: ActionFunctionArgs) => {
  const session = await getSession(request.headers.get("Cookie"));
  const data = await request.formData();
  const priceId = data.get("priceId") as string;
  const customerId = session.get("customerId") as string;
  try {
    const { clientSecret } = await subscribe(customerId, priceId);
    return redirect(`/subscribe?client_secret=${clientSecret}`, {
      headers: { "Set-Cookie": await commitSession(session) },
    });
  } catch (error) {
    return { error };
  }
};

Side effects

  1. Reads customerId from the active session.
  2. Calls subscribe(customerId, priceId), which creates a Stripe subscription with payment_behavior: 'default_incomplete' and returns the associated clientSecret from the first invoice’s PaymentIntent.
  3. Commits the session and forwards it as a Set-Cookie header on the redirect response.

Response

StatusDescription
302Redirects to /subscribe?client_secret=<clientSecret>. The client_secret query parameter is the PaymentIntent client secret required to confirm payment in Stripe Elements.
200Returns { error } if subscribe() throws (e.g. invalid customer ID or Stripe error). The error is surfaced inline on the prices page.
The customerId must exist in the session before this action is called. If the session is missing or the customer ID is absent, subscribe will fail. Ensure the user has completed the /register flow first.
Stripe subscriptions created with default_incomplete are not active until the PaymentIntent is confirmed. The subscription remains in incomplete status until the user completes payment on the /subscribe page.

Build docs developers (and LLMs) love