diff --git a/changelog.mdx b/changelog.mdx
index 99013426b..8a7de3c66 100644
--- a/changelog.mdx
+++ b/changelog.mdx
@@ -77,7 +77,7 @@ noindex: true
- **Redesigned editor:** New navigation tree, top bar, and page design. Create files in **Home** and structure your navigation in **Publishing**.
- **Organization-level credits:** Credits are now shared at the organization level instead of per deployment, so every deployment on a plan draws from one shared balance. See [Credit pricing](/credits) for more information.
- **Editor performance:** Navigation validation, page lookups, and tab switches in the editor are now linear instead of quadratic, so large sites open and edit faster.
- - **Personalization moved to add-ons:** [Personalization](/create/personalization) settings are now available under add-ons in the dashboard and no longer require a custom base path.
+ - **Personalization moved to add-ons:** [Personalization](/create/personalization) settings are now available under add-ons in the dashboard and can be configured on deployments that use a custom base path.
- **Truncated pagination titles:** Long titles in next/previous footer links truncate so they no longer overflow.
- **Long filename handling:** Uploaded files with names over 255 characters now truncate.
diff --git a/create/personalization.mdx b/create/personalization.mdx
index dd6a96fc5..103449479 100644
--- a/create/personalization.mdx
+++ b/create/personalization.mdx
@@ -1,14 +1,85 @@
---
title: "Personalized content"
-description: "Show personalized content based on user authentication data, group memberships, and custom variables to tailor documentation per audience."
+description: "Show personalized content based on identified visitor data, group memberships, and custom variables to tailor documentation per audience."
keywords: ["content personalization", "personalization", "user data", "groups", "dynamic", "prefill"]
---
-
- Personalization requires [authentication](/deploy/authentication-setup) configured with OAuth or JWT.
-
+Customize content for identified visitors while keeping your documentation public. Examples of personalization include prefilling API keys, showing content specific to a user's plan or role, and filtering API reference content based on group membership.
+
+Personalization uses a shared session, JWT, or OAuth to identify visitors without restricting access to your pages.
+
+| Method | Best for | Visitor identification |
+| :--- | :--- | :--- |
+| Shared session | Documentation and an existing application that can share a browser session | Mintlify requests user data from your Info API with the visitor's session cookies. |
+| JWT | An existing login flow that can sign Mintlify user data | Your login flow redirects the visitor back with a signed JWT. |
+| OAuth | An existing OAuth 2.0 provider | Mintlify completes an OAuth flow and requests user data from your Info API. |
+
+## Configure personalization
+
+Enable personalization on the [Add-ons](https://app.mintlify.com/settings/deployment/addons) page of your dashboard. Personalization is mutually exclusive with full authentication. JWT and OAuth authentication include personalization features.
+
+1. Navigate to the [Add-ons](https://app.mintlify.com/settings/deployment/addons) page of your dashboard.
+2. In the **Personalization** section, select shared session, JWT, or OAuth.
+3. Configure your selected personalization method.
+4. Click **Save changes**.
+
+### Shared session
+
+Shared session reuses a visitor's existing application session, so they don't need to log in again on your Mintlify site.
+
+1. Select **Shared session** in the **Personalization** settings.
+2. Enter an **Info API URL** that returns the current visitor's [user data](#user-data-format).
+3. Optionally enter a **Login URL**. Mintlify displays a login link when the Info API does not return user data.
+4. Click **Save changes**.
+
+Mintlify sends a `GET` request to the Info API from the visitor's browser with credentials included. Return a successful JSON response for an identified visitor:
+
+```json User data response
+{
+ "expiresAt": 1893456000,
+ "content": {
+ "firstName": "Jane",
+ "plan": "Enterprise"
+ },
+ "apiPlaygroundInputs": {
+ "header": {
+ "Authorization": "Bearer user_abc123"
+ }
+ }
+}
+```
+
+For a visitor without a valid session, return a non-success response such as `401`. Mintlify leaves the visitor unidentified and keeps public content available.
+
+If the Info API is on a different origin from your documentation, configure it to allow credentialed cross-origin requests from the exact documentation origin. Do not use a wildcard origin with credentials. Prevent browsers and intermediary caches from storing user data by returning `Cache-Control: private, no-store`.
+
+
+ Values in `apiPlaygroundInputs` are available to the browser so the API playground can send them. Return short-lived, appropriately scoped credentials, and avoid exposing a privileged application session token when a dedicated documentation token is available.
+
+
+### JWT and OAuth
+
+JWT and OAuth personalization use the same user data format as shared session but don't restrict access to your documentation. Configure these methods in **Add-ons**, not **Authentication**.
-Customize content for your users when they log in to your documentation site. You can prefill API keys, show content specific to a user's plan or role, and filter API reference content based on group membership.
+For JWT personalization:
+
+1. Enter the URL of your existing login flow.
+2. Click **Save changes**.
+3. Click **Generate new key** and store the downloaded private key securely.
+4. In your login flow, create a JWT containing the identified visitor's [user data](#user-data-format) and sign it with the generated private key using the ES256 algorithm.
+5. Redirect the visitor to a page on your documentation site with the signed JWT as the URL fragment. For example, `https://docs.example.com/get-started#{SIGNED_JWT}`. For a custom subpath, include the subpath in this URL.
+
+Set the JWT's `exp` claim to a short duration of 10 seconds or less. Use the user data `expiresAt` field to control how long Mintlify stores the personalization data.
+
+For OAuth personalization:
+
+1. Enter your authorization URL, client ID, scopes, token URL, Info API URL, and any optional settings, then click **Save changes**. OAuth personalization uses the Authorization Code flow with Proof Key for Code Exchange (PKCE) and does not require a client secret.
+2. Copy the **Redirect URL** from the dashboard and add it as an authorized redirect URL for your OAuth provider.
+3. Configure the Info API to accept a `GET` request with the OAuth access token in the `Authorization: Bearer ` header and return [user data](#user-data-format).
+
+The OAuth redirect path is `/mintlify-oauth-callback`. On a custom subpath, the dashboard includes the subpath in the redirect URL.
+
+Mintlify exchanges the authorization code and requests user data from the visitor's browser. If the token or Info API endpoint is on a different origin from your documentation, configure it to allow cross-origin requests from the exact documentation origin. The Info API must allow the `Authorization` request header.
## API key prefilling
@@ -59,11 +130,15 @@ For conditional rendering based on user data, use the `user` variable in JSX com
The `user` variable is an empty object for logged-out users. Use optional chaining on all `user` fields to prevent errors. For example, `{user.org?.plan}` instead of `{user.org.plan}`.
-To read the same user object from a [custom JavaScript file](/customize/custom-scripts#access-authenticated-user-data), use `window.mintlify.user` and listen for the `mintlify:user` event.
+To read the same user object from a [custom JavaScript file](/customize/custom-scripts#access-personalized-user-data), use `window.mintlify.user` and listen for the `mintlify:user` event.
## Page visibility
-Restrict pages to specific user groups by adding `groups` to page frontmatter. Users must belong to at least one listed group to access the page.
+Control which pages appear in navigation based on user groups by adding `groups` to page frontmatter.
+
+
+ With personalization, `groups` control visibility but do not restrict access to a page. A visitor can still open a group-filtered page by navigating directly to its URL. Use [authentication](/deploy/authentication-setup) to restrict access to sensitive content.
+
```mdx
---
@@ -72,15 +147,13 @@ groups: ["admin"]
---
```
-For more details on how groups interact with public pages, see [Control access with groups](/deploy/authentication-setup#control-access-with-groups).
-
## OpenAPI content filtering
Filter API reference content based on user groups with the `x-mint` extension in your OpenAPI specification. You can filter entire endpoints, individual schema properties, `oneOf` variants, and enum values.
### Filter endpoints
-Add `x-mint.groups` to an operation or path to restrict the endpoint page to specific user groups. Users not in the listed groups won't see the endpoint in navigation or be able to access its page.
+Add `x-mint.groups` to an operation or path to show the endpoint in navigation only to specific user groups. With standalone personalization, users not in the listed groups can still open the endpoint page by its direct URL.
@@ -207,7 +280,7 @@ In this example, all users see `free`. Users in the `pro` or `enterprise` groups
## User data format
-Your authentication system returns user data that controls personalization. The `groups`, `content`, and `apiPlaygroundInputs` fields described on this page are all part of the user data object.
+Your identification or authentication system returns user data that controls personalization. The `groups`, `content`, and `apiPlaygroundInputs` fields described on this page are all part of the user data object.
For the full user data format and field reference, see [User data format](/deploy/authentication-setup#user-data-format).
diff --git a/customize/custom-scripts.mdx b/customize/custom-scripts.mdx
index e306a2146..bc72f5133 100644
--- a/customize/custom-scripts.mdx
+++ b/customize/custom-scripts.mdx
@@ -412,16 +412,16 @@ Calls made before the client initializes queue. Values apply to open and future
Do not use server variables for API keys, tokens, or other secrets.
-### Access authenticated user data
+### Access personalized user data
-If your site uses [authentication](/deploy/authentication-setup), custom scripts can read the signed-in user from `window.mintlify.user`. This is the same object exposed to MDX pages as the [`user` variable](/create/personalization#dynamic-mdx-content), so it reflects the `content` field of your user data.
+If your site uses [authentication](/deploy/authentication-setup) or [personalization](/create/personalization), custom scripts can read the identified visitor from `window.mintlify.user`. This is the same object exposed to MDX pages as the [`user` variable](/create/personalization#dynamic-mdx-content), so it reflects the `content` field of your user data.
-Because custom scripts run before user info resolves, listen for the `mintlify:user` event to identify when the user object is available. The event fires when user info resolves and again on any change. Its `detail` is the user object, or `null` when the visitor is signed out.
+Because custom scripts run before user info resolves, listen for the `mintlify:user` event to identify when the user object is available. The event fires when user info resolves and again on any change. Its `detail` is the user object, or `null` when the visitor is signed out or unidentified.
```js Read the user after it resolves
window.addEventListener('mintlify:user', (event) => {
const user = event.detail;
- if (!user) return; // Signed out.
+ if (!user) return; // Signed out or unidentified.
renderAppLauncher(user);
});
@@ -436,8 +436,8 @@ if (user) {
}
```
-`window.mintlify.user` is `undefined` until user info resolves and when the visitor is signed out. Use optional chaining when reading nested fields.
+`window.mintlify.user` is `undefined` until user info resolves and when the visitor is signed out or unidentified. Use optional chaining when reading nested fields.
Client-side scripts can access anything you place in the user `content` field. Do not include secrets or credentials that shouldn't be readable in the browser.
-
\ No newline at end of file
+
diff --git a/deploy/authentication-setup.mdx b/deploy/authentication-setup.mdx
index 8a529bdd2..86cf17438 100644
--- a/deploy/authentication-setup.mdx
+++ b/deploy/authentication-setup.mdx
@@ -18,6 +18,8 @@ You can configure full authentication for all pages or partial authentication wh
Authentication is only available for sites hosted on a custom domain or Mintlify subdomain. For example, `docs.example.com` or `example.mintlify.site`. Authentication is **not supported** for sites with a [custom subpath](/deploy/docs-subpath). For example, `example.com/docs`.
+To identify visitors while keeping pages public, use [personalization](/create/personalization). Personalization supports custom subpaths and can prefill API playground inputs without requiring visitors to authenticate before viewing a page.
+
## Choose an authentication method
Use this comparison to pick the method that fits your use case. See [Feature availability](#feature-availability) for how each method interacts with other Mintlify features.
@@ -179,7 +181,7 @@ You host your documentation at `docs.foo.com` and you have an existing OAuth ser
```json
{
"groups": ["engineering", "admin"],
- "expiresAt": 1735689600,
+ "expiresAt": 1893456000,
"apiPlaygroundInputs": {
"header": {
"Authorization": "Bearer user_abc123"
@@ -209,9 +211,10 @@ You host your documentation at `docs.foo.com` and you have an existing OAuth ser
3. Click **Custom**.
4. Click **JWT**.
5. Enter the URL of your existing login flow.
- 6. Click **Save changes**.
- 7. Click **Generate new key**.
- 8. Store your key securely where your backend can access it.
+ 6. To offer more than one login flow, click **Add login URL** and enter a display name and URL for each option. You can configure up to 10 login URLs.
+ 7. Click **Save changes**.
+ 8. Click **Generate new key**.
+ 9. Store your key securely where your backend can access it.
After you generate a private key, your site redeploys. When it finishes deploying, anyone who visits your site must log in to your JWT authentication system to access your content.
@@ -224,6 +227,12 @@ You host your documentation at `docs.foo.com` and you have an existing OAuth ser
+When JWT authentication has one login URL, unauthenticated visitors redirect to it automatically. With two or more named login URLs, visitors first see a selection page and then continue to the selected login flow. Mintlify forwards the validated `redirect` parameter so the visitor returns to the documentation page they originally requested.
+
+
+ Multiple login URLs are available for full and partial JWT authentication. JWT [personalization](/create/personalization) accepts one login URL because it identifies visitors without requiring a login before they can view public content.
+
+
### JWT example
You host your documentation at `docs.foo.com` with an existing authentication system at `foo.com`. You want to extend your login flow to grant access to the docs while keeping your docs separate from your dashboard (or you don't have a dashboard).
@@ -363,7 +372,7 @@ Manage groups through user data passed during authentication. See [User data for
```json Example user info
{
"groups": ["admin", "beta-users"],
- "expiresAt": 1735689600
+ "expiresAt": 1893456000
}
```
@@ -410,7 +419,7 @@ groups: ["pro", "enterprise"]
## User data format
-When using OAuth or JWT authentication, your system returns user data that controls session length, group membership, and [content personalization](/create/personalization).
+When using OAuth or JWT authentication or standalone personalization, your system returns user data that controls session length, group membership, and [content personalization](/create/personalization).
```tsx Format
@@ -432,7 +441,7 @@ type User = {
```json Example
{
"host": "docs.example.com",
- "expiresAt": 1735689600,
+ "expiresAt": 1893456000,
"groups": ["admin", "beta-users"],
"content": {
"firstName": "Jane",
@@ -455,15 +464,15 @@ type User = {
- Session expiration time in seconds since epoch. When the current time passes this value, the user must re-authenticate.
+ Session expiration time in seconds since epoch. When the current time passes this value, Mintlify expires the stored user data. The visitor must authenticate again or repeat the identification flow to refresh it.
**For JWT:** This differs from the JWT's `exp` claim, which determines when a JWT is considered invalid. Set the JWT `exp` claim to a short duration (10 seconds or less) for security. Use `expiresAt` for the actual session length (hours to weeks).
- List of groups the user belongs to. Pages with matching `groups` in their frontmatter are accessible to this user.
+ List of groups the user belongs to. With authentication, pages with matching `groups` in their frontmatter are accessible to this user. With standalone personalization, groups control page and content visibility but do not restrict access to a page's direct URL.
- **Example**: A user with `groups: ["admin", "engineering"]` can access pages tagged with either the `admin` or `engineering` groups.
+ **Example**: A user with `groups: ["admin", "engineering"]` matches content tagged with either the `admin` or `engineering` groups.