Skip to content
  • Design
  • Customize with code

Understand Kinde page customization

The page is the full user interface rendered by the browser, including everything that surrounds the Kinde widget. You can provide your own markup to shape the page design. Use the widget placeholder to tell Kinde where to render the widget.

You define a page by default-exporting a function from a page.[ts/tsx/js/jsx] file.

"use server";
import React from "react";
import { renderToString } from "react-dom/server.browser";
import {
getKindeRequiredCSS,
getKindeRequiredJS,
getKindeNonce,
getKindeWidget,
getKindeCSRF,
getLogoUrl,
getSVGFaviconUrl,
setKindeDesignerCustomProperties,
getKindeRegisterUrl
} from "@kinde/infrastructure";
const Layout = async ({request, context}) => {
return (
<html lang={request.locale.lang} dir={request.locale.isRtl ? "rtl" : "ltr"}>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="robots" content="noindex" />
<meta name="csrf-token" content={getKindeCSRF()} />
<title>{context.widget.content.page_title}</title>
<link rel="icon" href={getSVGFaviconUrl()} type="image/svg+xml" />
{getKindeRequiredCSS()}
{getKindeRequiredJS()}
<style nonce={getKindeNonce()}>
{`:root {
${setKindeDesignerCustomProperties({
baseBackgroundColor: "#fff",
baseLinkColor: "#230078",
buttonBorderRadius: "0.5rem",
primaryButtonBackgroundColor: "#230078",
primaryButtonColor: "#fff",
inputBorderRadius: "0.5rem"
})}}
`}
</style>
<style nonce={getKindeNonce()}>
{`
:root {
--kinde-base-color: rgb(12, 0, 32);
--kinde-base-font-family: -apple-system, system-ui, BlinkMacSystemFont, Helvetica, Arial, Segoe UI, Roboto, sans-serif;
}
[data-kinde-control-select-text]{
background-color: rgb(250, 250, 251);
}
.c-container {
padding: 1.5rem;
display: grid;
gap: 230px;
}
.c-widget {
max-width: 400px;
width: 100%;
margin: 0px auto;
}
.c-footer {
border-top: 1px solid rgba(12, 0, 32, 0.08);
padding-block: 1.5rem;
display: flex;
justify-content: space-between;
}
.c-footer-links {
display: flex;
gap: 1.5rem;
}
`}
</style>
</head>
<body>
<div data-kinde-root="/admin" className="c-container">
<header className="c-header">
<img src={getLogoUrl()} alt={context.widget.content.logo_alt} />
</header>
<main>
<div className="c-widget">
<h1>{context.widget.content.heading}</h1>
<p>{context.widget.content.description}</p>
<div>{getKindeWidget()}</div>
</div>
</main>
<footer className="c-footer">
<p className="c-no-account-link">
No account? <a href={getKindeRegisterUrl()}>Sign up for free</a>
</p>
<ul className="c-footer-links">
<li>
<a href="">Privacy</a>
</li>
<li>
<a href="">Terms</a>
</li>
<li>
<a href="">Get help</a>
</li>
</ul>
</footer>
</div>
</body>
</html>
);
};
export default async function Page(event) {
const page = await Layout({...event});
return renderToString(page);
}

Supported templating languages

Link to this section

Kinde uses server-rendered JavaScript to compose custom pages and supports these templating options:

  • React
  • Liquid
  • JavaScript
  • TypeScript

Page structure in the Git directory

Link to this section

The code for your pages must live in a GitHub repository (other Git providers to be supported later). Correct directory structure is required for Kinde to run your design code.

The directory structure for a Kinde custom code repo should be as follows:

myApp/
├── kindeSrc/
│ └── environment/
│ └── pages/
│ └── (kinde)/
│ └── (default)/
│ └── page.tsx
├── package.json
└── kinde.json

kinde.json file

Link to this section

The kinde.json file defines the config for all custom code in Kinde, including workflows and custom pages. A typical config file looks like this:

{
"rootDir": "kindeSrc", // the directory where Kinde should look for your code
"version": "2024-12-09" // the last breaking change
}

rootDir - the directory where Kinde will look for your custom code. Defaults to kindeSrc so that you can include Kinde code alongside existing projects. Change this if you want the code in another directory.

version - based on the date when the API version was released. Breaking changes are released in a new API version.

Which pages can I customize?

Link to this section

All Kinde-hosted pages can be customized. URL route mapping determines which template to use.

If a specific mapped route does not exist, a special route called (default) is rendered. Set up the (default) page so all pages follow this design unless you override them with custom code.

These are the most frequently used or fallback pages.

  • (default) - Fallback template used when a specific page is not found.
  • (register) - The sign-up page.
  • (login) - The sign-in page.
  • (index) - Landing page when visiting the root domain (e.g. https://auth.your_business.com).

Authentication

Link to this section

Pages involved in initiating authentication flows.

  • (register) - The sign-up page.
  • (login) - The sign-in page.
  • (sso_home_realm) - Displayed when a user selects Continue with SSO to choose their identity provider.
  • (provide_email) - Shown when using a social login provider that does not return an email address.
  • (clickwrap) - Displays terms and conditions or agreements that must be accepted.

Password flows

Link to this section

Used in password-based authentication and recovery. Password entry always takes place on Kinde’s hosted domain; only the visual appearance of these pages can be customized. Embedding the password entry screen on your own domain (without redirecting to Kinde) is not supported for security reasons.

  • (verify_password) - Shown during sign-in when using a password-based method.
  • (set_password) - Shown during a password-based sign-up flow.
  • (reset_password_verify_email_otp) - First step of the reset flow where the user verifies identity using an email OTP.
  • (reset_password) - Password entry screen shown after verification during the reset flow.

One-Time Passcode (OTP) verification

Link to this section

Used in sign-in and sign-up flows with OTP (email or phone).

  • (sign_in_use_email_otp) - Sign in with a one-time email code.
  • (sign_up_use_email_otp) - Sign up with a one-time email code.
  • (sign_in_sign_up_use_phone_otp) - Sign in or sign up with a one-time phone code.

Multi-Factor Authentication (MFA)

Link to this section

Shown after primary authentication when MFA is required or being set up.

  • (mfa_method_selection) - User selects their preferred MFA method.
  • (mfa_authenticator_app) - Setup screen for using an authenticator app.
  • (mfa_setup_email) - Setup screen for email-based MFA.
  • (mfa_setup_phone) - Setup screen for phone-based MFA.
  • (mfa_use_email_otp) - Enter a one-time email code for MFA.
  • (mfa_use_phone_otp) - Enter a one-time phone code for MFA.
  • (mfa_use_recovery_code) - Enter a recovery code if unable to use other MFA methods.
  • (mfa_view_recovery_codes) - View and save MFA recovery codes.

Pages related to choosing a plan and making a payment.

  • (choose_plan) - User selects a subscription plan.
  • (collect_payment_details) - User enters billing information.
  • (subscription_success) - Confirmation page shown after successful subscription.

Pages for gated access to your product before full launch.

  • (request_access) - Request early access to your product.
  • (request_access_success) - Confirmation page after a request is submitted.

Shown when a user encounters an error state.

  • (account_locked) - The user’s account has been locked.
  • (account_not_found) - No account was found for the entered credentials.
  • (invalid_redirect_url) - The redirect URL is invalid or not on the allowed list.
  • (error) - Generic error screen.

If you want specific customization for a page not listed here, reach out and let us know.

Set the page default export function

Link to this section

Your page.[ext] file should contain a default export. The name does not matter, but the usual convention is to call it Page. It receives a single event argument: an object with two keys, context and request.

The request key contains information about the request to the page and includes the following sub-objects:

  • authUrlParams contains useful info from the authorization request URL
    • orgCode - the requested organization code, e.g. org_12345
    • state - the state parameter in the auth URL
    • clientId - the client_id query param from the auth URL (the application ID)
    • redirectUri - the redirect_uri query param from the auth URL
  • locale - localization data
    • isRtl - whether the requested language should be rendered right to left
    • lang - the code for the requested language
  • route
    • context - the requested widget CX_m, e.g. register | choose_organization
    • flow - whether the flow is a sign-in or sign-up flow, e.g. register | login
    • path - the path of the request: auth | account | /

The top-level context object contains information about the page itself, such as page state and content.

  • domains contains relevant domain info
    • kindeDomain your domain on Kinde, e.g. https://example.kinde.com
  • widget contains data generated from the widget
    • content an object containing page content - translated and with placeholders replaced
      • page_title the title of the page, commonly used in the <title> tag for the browser tab
      • heading the main heading for the page
      • description the page description
      • logo_alt the alt text for your company logo

pageSettings object

Link to this section

The pageSettings object is required when you need to enable bindings that give your page access to additional Kinde features, such as environment variables or the fetch API. Without declaring these bindings in pageSettings, these features won’t be available in your page code.

export const pageSettings = {
bindings: {
"kinde.env": {}
}
}

Connection switching context

Link to this section

On authentication pages, the context object includes the connections a user can switch to and the action needed to perform a switch. Use these to build a connection switcher — see Add a connection switcher to a custom page.

context.connections

Link to this section
  • available - an array of connections the current user can switch to. Each entry includes id, friendlyId, name, connectionType (social, enterprise, credential, or other), credentialMethod, identityType, provider, and logoName.

context.actions

Link to this section
  • switchConnection - the action used to switch connection. Contains actionUrl, method, and a fields map. Pass this object (JSON-encoded) to the data-kinde-change-connection-action attribute on your switch buttons.

context.session

Link to this section
  • pipelineStepId - the current pipeline step identifier. Pass it to the data-kinde-change-connection-psid attribute.
  • state - the current authentication state value, when present.

context.auth additions

Link to this section
  • activeConnectionId - the connection the flow is currently using.
  • sessionConnectionId - the connection originally requested for the session, if any.

How to access environment variables in your page

Link to this section

You can access environment variables in your custom UI pages using the getEnvironmentVariable method. To enable this, add the pageSettings object with the environment binding to each custom page.

Follow these steps:

  1. Set up your environment variables in Environment > Settings > Env variables.
  2. Add the pageSettings object with the kinde.env binding to your page file.
  3. Import the getEnvironmentVariable method from @kinde/infrastructure.
  4. Access the environment variable within your Page component.

Example:

"use server";
import { type KindePageEvent, getEnvironmentVariable } from "@kinde/infrastructure";
import React from "react";
import { renderToString } from "react-dom/server.browser";
const DefaultPage: React.FC<KindePageEvent> = ({ context, request }) => {
// Access environment variables inside your component
const publicUrl = getEnvironmentVariable("APP_PUBLIC_URL")?.value;
return (
<div>
<p>Public URL: {publicUrl}</p>
</div>
);
}
export default async function Page(event: KindePageEvent): Promise<string> {
const page = await DefaultPage(event);
return renderToString(page);
}
// Required: Add pageSettings with kinde.env binding to enable environment variable access
export const pageSettings = {
bindings: {
"kinde.env": {}
}
}