diff --git a/apps/docs/content/docs/advanced/security.mdx b/apps/docs/content/docs/advanced/security.mdx
index 8c0a06e..8d90b2e 100644
--- a/apps/docs/content/docs/advanced/security.mdx
+++ b/apps/docs/content/docs/advanced/security.mdx
@@ -47,6 +47,11 @@ function LoginButton() {
const handleLogin = async () => {
const response = await login({ scope: 'email' });
+ if (response.status !== 'connected') return;
+
+ // Narrow to scope flow — authResponse contains accessToken, not code
+ if (!('accessToken' in response.authResponse)) return;
+
// Send the token to your server for validation and exchange
await fetch('/api/auth/facebook', {
method: 'POST',
diff --git a/apps/docs/content/docs/components/error-boundary.mdx b/apps/docs/content/docs/components/error-boundary.mdx
index 2e4ca5f..5ad4cc7 100644
--- a/apps/docs/content/docs/components/error-boundary.mdx
+++ b/apps/docs/content/docs/components/error-boundary.mdx
@@ -43,7 +43,7 @@ function App() {
}}
>
- Login with Facebook
+ Login with Facebook
);
diff --git a/apps/docs/content/docs/components/login.mdx b/apps/docs/content/docs/components/login.mdx
index 402c627..28b844e 100644
--- a/apps/docs/content/docs/components/login.mdx
+++ b/apps/docs/content/docs/components/login.mdx
@@ -17,22 +17,25 @@ import { Login } from 'react-facebook';
## Props
-| Prop | Type | Default | Description |
-| ------------------ | ---------------------------------------------------------- | ----------------------------- | ----------------------------------------------------------------------------------- |
-| `children` | `ReactNode \| ((props: LoginRenderProps) => ReactElement)` | `undefined` | Button content, or a render function receiving `{ onClick, loading, isDisabled }`. |
-| `onSuccess` | `(response: LoginResponse) => void` | `undefined` | Called after a successful login with the login response containing `authResponse`. |
-| `onError` | `(error: Error) => void` | `undefined` | Called when the login fails or the user cancels. |
-| `onProfileSuccess` | `(profile: Record) => void` | `undefined` | Called with the user profile when `fields` are provided and the profile is fetched. |
-| `scope` | `string \| string[]` | `['public_profile', 'email']` | Permissions to request. Accepts a comma-separated string or an array. |
-| `fields` | `string[]` | `[]` | Profile fields to fetch after login (e.g. `['name', 'email', 'picture']`). |
-| `as` | `ElementType \| ComponentType` | `'button'` | The HTML element or React component to render. |
-| `disabled` | `boolean` | `false` | Disables the login button. |
-| `returnScopes` | `boolean` | `undefined` | When `true`, the response includes the scopes the user actually granted. |
-| `authType` | `string[]` | `undefined` | Auth type array (e.g. `['rerequest']`). |
-| `rerequest` | `boolean` | `undefined` | Adds `'rerequest'` to `authType`, prompting for previously declined permissions. |
-| `reauthorize` | `boolean` | `undefined` | Adds `'reauthenticate'` to `authType`, forcing re-authentication. |
-| `className` | `string` | `undefined` | CSS class name applied to the rendered element. |
-| `style` | `CSSProperties` | `undefined` | Inline styles applied to the rendered element. |
+`LoginProps` is a **discriminated union** — you must provide either `configId` or `scope`, not both. Passing both is a TypeScript error.
+
+| Prop | Type | Default | Description |
+| ------------------ | ---------------------------------------------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
+| `children` | `ReactNode \| ((props: LoginRenderProps) => ReactElement)` | `undefined` | Button content, or a render function receiving `{ onClick, loading, isDisabled }`. |
+| `onSuccess` | `(response: LoginResponse) => void` | `undefined` | Called after a successful login with the login response containing `authResponse`. |
+| `onError` | `(error: Error) => void` | `undefined` | Called when the login fails or the user cancels. |
+| `onProfileSuccess` | `(profile: Record) => void` | `undefined` | Called with the user profile when `fields` are provided and the profile is fetched. |
+| `configId` | `string` | `undefined` | Facebook Business Login configuration ID. When set, triggers the BISU code flow (`response_type: 'code'`).
Note: Mutually exclusive with `scope`. |
+| `scope` | `string \| string[]` | `['public_profile', 'email']` | Permissions to request. Accepts a comma-separated string or an array.
Note: Mutually exclusive with `configId`. |
+| `fields` | `string[]` | `[]` | Profile fields to fetch after login (e.g. `['name', 'email', 'picture']`). |
+| `as` | `ElementType \| ComponentType` | `'button'` | The HTML element or React component to render. |
+| `disabled` | `boolean` | `false` | Disables the login button. |
+| `returnScopes` | `boolean` | `undefined` | When `true`, the response includes the scopes the user actually granted. Applies to the `scope` flow only. |
+| `authType` | `string[]` | `undefined` | Auth type array (e.g. `['rerequest']`). |
+| `rerequest` | `boolean` | `undefined` | Adds `'rerequest'` to `authType`, prompting for previously declined permissions. |
+| `reauthorize` | `boolean` | `undefined` | Adds `'reauthenticate'` to `authType`, forcing re-authentication. |
+| `className` | `string` | `undefined` | CSS class name applied to the rendered element. |
+| `style` | `CSSProperties` | `undefined` | Inline styles applied to the rendered element. |
Any additional props are spread onto the rendered element.
@@ -117,3 +120,59 @@ When you provide `fields`, the component automatically fetches the user profile
Sign in with Facebook
```
+
+### Facebook Login for Business (configId)
+
+
+ Facebook Login for Business uses a configuration ID created in the [Facebook App Dashboard](https://developers.facebook.com/docs/facebook-login/facebook-login-for-business).
+ Instead of a client-side access token the SDK returns a short-lived authorization **code** that must be exchanged
+ server-side for a BISU token. See the [Facebook Login for Business docs](https://developers.facebook.com/docs/facebook-login/facebook-login-for-business)
+ for the full server-side exchange flow.
+
+
+Pass `configId` instead of `scope`. The `onSuccess` callback receives `authResponse.code` — there is no `accessToken` in this flow.
+
+**With a default button:**
+
+```tsx
+ {
+ if (response.status === 'connected' && 'code' in response.authResponse) {
+ // Exchange this code on your server
+ fetch('/api/auth/facebook/exchange', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ code: response.authResponse.code }),
+ });
+ }
+ }}
+ onError={(error) => console.error('Login failed:', error)}
+>
+ Continue with Facebook
+
+```
+
+**With the render props pattern:**
+
+```tsx
+ {
+ if (response.status === 'connected' && 'code' in response.authResponse) {
+ fetch('/api/auth/facebook/exchange', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ code: response.authResponse.code }),
+ });
+ }
+ }}
+ onError={(error) => console.error('Login failed:', error)}
+>
+ {({ onClick, loading, isDisabled }) => (
+
+ )}
+
+```
diff --git a/apps/docs/content/docs/hooks/use-login.mdx b/apps/docs/content/docs/hooks/use-login.mdx
index 2c12946..b672fc3 100644
--- a/apps/docs/content/docs/hooks/use-login.mdx
+++ b/apps/docs/content/docs/hooks/use-login.mdx
@@ -27,24 +27,50 @@ The hook returns an object with the following properties:
### LoginOptions
-The `login` function accepts the following options:
+`LoginOptions` is a **discriminated union** — pass either `configId` or `scope`, not both.
-| Property | Type | Default | Description |
-| -------------- | ---------- | ----------- | ------------------------------------------------------------------------------- |
-| `scope` | `string` | `undefined` | Comma-separated list of permissions to request (e.g. `'email,public_profile'`). |
-| `returnScopes` | `boolean` | `undefined` | When `true`, the response includes the scopes that were granted. |
-| `authType` | `string[]` | `undefined` | Array of auth types to include in the request. |
-| `rerequest` | `boolean` | `undefined` | When `true`, asks the user again for previously declined permissions. |
-| `reauthorize` | `boolean` | `undefined` | When `true`, forces re-authentication of the user. |
+**Shared options** (apply to both flows):
+
+| Property | Type | Default | Description |
+| ------------- | ---------- | ----------- | --------------------------------------------------------------------- |
+| `returnScopes` | `boolean` | `undefined` | When `true`, the response includes the scopes that were granted. Applies to the `scope` flow only. |
+| `authType` | `string[]` | `undefined` | Array of auth types to include in the request. |
+| `rerequest` | `boolean` | `undefined` | When `true`, asks the user again for previously declined permissions. |
+| `reauthorize` | `boolean` | `undefined` | When `true`, forces re-authentication of the user. |
+
+**Scope flow** — pass `scope` to request permissions directly:
+
+| Property | Type | Default | Description |
+| -------- | -------- | ----------- | -------------------------------------------------------------------------------- |
+| `scope` | `string` | `undefined` | Comma-separated list of permissions to request (e.g. `'email,public_profile'`).
Note: Mutually exclusive with `configId`. |
+
+**Business Login flow** — pass `configId` to use a server-defined configuration:
+
+| Property | Type | Default | Description |
+| ---------- | -------- | ----------- | ------------------------------------------------------------------------------------------------------------- |
+| `configId` | `string` | `undefined` | Facebook Business Login configuration ID. Triggers the BISU code flow (`response_type: 'code'`).
Note: Mutually exclusive with `scope`. |
### LoginResponse
-When the status is `'connected'`, the response includes an `authResponse` object with:
+When `status` is `'connected'`, the response includes an `authResponse` whose shape depends on the login flow used.
+
+**Scope flow** (`authResponse` when `scope` was passed):
-| Property | Type | Description |
-| ------------- | -------- | ------------------------------- |
-| `userID` | `string` | The Facebook user ID. |
-| `accessToken` | `string` | The access token for API calls. |
+| Property | Type | Description |
+| ------------- | -------- | ------------------------------------ |
+| `userID` | `string` | The Facebook user ID. |
+| `accessToken` | `string` | The access token for API calls. |
+| `expiresIn` | `number` | Seconds until the token expires. |
+
+**Business Login flow** (`authResponse` when `configId` was passed):
+
+| Property | Type | Description |
+| ----------- | -------- | ----------------------------------------------------------------------------------------------- |
+| `code` | `string` | Short-lived authorization code to exchange server-side for a BISU token. |
+| `userID` | `null` | Always `null` — no user-scoped ID is returned in the `configId` flow. |
+| `expiresIn` | `number` | `NaN` — expiration is defined by the BISU configuration, not the SDK response. |
+
+Narrow `authResponse` with `'accessToken' in response.authResponse` before accessing flow-specific fields.
## Usage
@@ -99,7 +125,9 @@ function LoginWithErrorHandling() {
try {
const response = await login({ scope: 'email,public_profile' });
- console.log('Logged in as:', response.authResponse?.userID);
+ if (response.status === 'connected' && 'accessToken' in response.authResponse) {
+ console.log('Logged in as:', response.authResponse.userID);
+ }
} catch (err) {
const message = err instanceof Error ? err.message : 'An unexpected error occurred';
setLoginError(message);
@@ -178,7 +206,7 @@ function LoginAndProfile() {
## Forward Token to Server
-After login, send the `accessToken` to your backend API for server-side verification or session creation.
+After a scope-based login, send the `accessToken` to your backend for server-side verification or session creation. Narrow `authResponse` with `'accessToken' in response.authResponse` to confirm this is the scope flow before accessing `accessToken` and `userID`.
```tsx
import { useLogin } from 'react-facebook';
@@ -191,10 +219,15 @@ function LoginWithBackend() {
try {
const response = await login({ scope: 'email,public_profile' });
- if (response.status !== 'connected' || !response.authResponse) {
+ if (response.status !== 'connected') {
throw new Error('Login did not complete');
}
+ // Narrow to scope flow — authResponse contains accessToken, not code
+ if (!('accessToken' in response.authResponse)) {
+ throw new Error('Unexpected response type');
+ }
+
const { accessToken, userID } = response.authResponse;
// Send the access token to your backend for verification
@@ -262,3 +295,42 @@ function ConditionalLogout() {
);
}
```
+
+## Facebook Login for Business (configId)
+
+Pass `configId` instead of `scope` to use the [Facebook Login for Business](https://developers.facebook.com/docs/facebook-login/facebook-login-for-business) flow. The SDK returns a short-lived authorization **code** in `authResponse.code` — there is no `accessToken`. Exchange this code on your server for a BISU token.
+
+**Basic usage:**
+
+```tsx
+import { useLogin } from 'react-facebook';
+
+function BusinessLoginButton() {
+ const { login, loading } = useLogin();
+
+ const handleLogin = async () => {
+ try {
+ const response = await login({ configId: 'YOUR_CONFIG_ID' });
+
+ if (response.status !== 'connected' || !('code' in response.authResponse)) {
+ throw new Error('Login did not complete');
+ }
+
+ // Exchange this code server-side for a BISU token
+ await fetch('/api/auth/facebook/exchange', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ code: response.authResponse.code }),
+ });
+ } catch (err) {
+ console.error('Login failed:', err);
+ }
+ };
+
+ return (
+
+ );
+}
+```
diff --git a/apps/docs/content/docs/migration/facebook-login-setup.mdx b/apps/docs/content/docs/migration/facebook-login-setup.mdx
index 86c3598..b638019 100644
--- a/apps/docs/content/docs/migration/facebook-login-setup.mdx
+++ b/apps/docs/content/docs/migration/facebook-login-setup.mdx
@@ -29,7 +29,9 @@ function App() {
scope={['public_profile', 'email']}
fields={['name', 'email', 'picture']}
onSuccess={(response) => {
- console.log('Auth token:', response.authResponse.accessToken);
+ if (response.status === 'connected' && 'accessToken' in response.authResponse) {
+ console.log('Auth token:', response.authResponse.accessToken);
+ }
}}
onProfileSuccess={(profile) => {
console.log('User:', profile.name, profile.email);
@@ -58,6 +60,8 @@ function LoginButton() {
const handleLogin = async () => {
try {
const response = await login({ scope: 'email,public_profile' });
+ // Narrow to the scope flow before accessing accessToken / userID
+ if (response.status !== 'connected' || !('accessToken' in response.authResponse)) return;
// Send token to your backend
await fetch('/api/auth/facebook', {
method: 'POST',
@@ -189,11 +193,21 @@ import { FacebookProvider, FacebookErrorBoundary, Login } from 'react-facebook';
Every component and hook is fully typed. No separate `@types/` package needed.
```tsx
-import type { LoginResponse, AuthResponse } from 'react-facebook';
+import type { LoginResponse } from 'react-facebook';
function handleSuccess(response: LoginResponse) {
- const token: string = response.authResponse.accessToken;
- const userId: string = response.authResponse.userID;
+ if (response.status !== 'connected') return;
+
+ // Scope flow: authResponse contains accessToken and userID
+ if ('accessToken' in response.authResponse) {
+ const token: string = response.authResponse.accessToken;
+ const userId: string = response.authResponse.userID;
+ }
+
+ // Business Login (configId) flow: authResponse contains code instead
+ if ('code' in response.authResponse) {
+ const code: string = response.authResponse.code;
+ }
}
```
diff --git a/packages/react-facebook/e2e/app/pages/LoginPage.tsx b/packages/react-facebook/e2e/app/pages/LoginPage.tsx
index b616f71..a6754d7 100644
--- a/packages/react-facebook/e2e/app/pages/LoginPage.tsx
+++ b/packages/react-facebook/e2e/app/pages/LoginPage.tsx
@@ -83,6 +83,12 @@ export default function LoginPage() {
+
+ {}} data-testid="config-id-login">
+ Login with Config ID
+
+
+
{/* Result display */}