Add support for multiple custom auth providers in custom-content (#48030)

## Context

Adds support for multiple custom auth providers in custom-content

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added support for multiple custom sign-in providers via a new plural
configuration.
* Updated the sign-in page to render all configured custom provider
options while maintaining compatibility with the legacy single-provider
setting.
* Improved the custom provider button display to remove internal
prefixes from provider names.

* **Documentation**
* Updated the configuration schema, examples, and sample data to
document the new multi-provider setting.
* Marked the legacy single-provider configuration as deprecated in favor
of the plural option.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Joshen Lim authored and GitHub committed 2026-07-20 11:02:15 +08:00
1 parent fc72a6b259
commit 8962308215
7 files changed
+37 -14

No files matched your search

@@ -10,6 +10,10 @@ interface SignInWithCustomProps {
providerName: string
}
const formatProviderName = (name: string) => {
return name.replace('custom:', '')
}
export const SignInWithCustom = ({ providerName }: SignInWithCustomProps) => {
const [loading, setLoading] = useState(false)
@@ -42,7 +46,7 @@ export const SignInWithCustom = ({ providerName }: SignInWithCustomProps) => {
return (
<Button block onClick={handleCustomSignIn} size="large" variant="default" loading={loading}>
Continue with {providerName}
Continue with <span className="capitalize">{formatProviderName(providerName)}</span>
</Button>
)
}
@@ -5,6 +5,8 @@ export type CustomContentTypes = {
dashboardAuthCustomProvider: string
dashboardAuthCustomProviders: string[]
docsRowLevelSecurityGuidePath: string
organizationLegalDocuments: {
@@ -5,6 +5,8 @@
"dashboard_auth:custom_provider": null,
"dashboard_auth:custom_providers": null,
"docs:row_level_security_guide_path": "/guides/auth/row-level-security",
"organization:legal_documents": null,
@@ -5,6 +5,8 @@
"dashboard_auth:custom_provider": "Nimbus",
"dashboard_auth:custom_providers": ["Nimbus"],
"docs:row_level_security_guide_path": "/guides/database/postgres/row-level-security",
"organization:legal_documents": [
@@ -13,7 +13,15 @@
"dashboard_auth:custom_provider": {
"type": ["string", "null"],
"description": "Show a custom provider on the sign in page (Continue with X)"
"description": "Show a custom provider on the sign in page (Continue with X). Will be deprecated by dashboard_auth:custom_providers."
},
"dashboard_auth:custom_providers": {
"type": ["array", "null"],
"description": "Show a custom provider on the sign in page (Continue with X)",
"items": {
"type": "string"
}
},
"docs:row_level_security_guide_path": {
@@ -27,16 +27,12 @@ function contentToCamelCase(feature: CustomContent) {
export const useCustomContent = <T extends CustomContent[]>(
contents: T
): {
[key in CustomContentToCamelCase<T[number]>]:
| CustomContentTypes[CustomContentToCamelCase<T[number]>]
| null
[key in CustomContentToCamelCase<T[number]>]: CustomContentTypes[key] | null
} => {
// [Joshen] Running into some TS errors without the `as` here - must be overlooking something super simple
return Object.fromEntries(
contents.map((content) => [contentToCamelCase(content), customContentStaticObj[content]])
) as {
[key in CustomContentToCamelCase<T[number]>]:
| CustomContentTypes[CustomContentToCamelCase<T[number]>]
| null
[key in CustomContentToCamelCase<T[number]>]: CustomContentTypes[key] | null
}
}
+15 -6
View File
@@ -32,9 +32,14 @@ const SignInPage: NextPageWithLayout = () => {
'dashboard_auth:sign_up',
])
const { dashboardAuthCustomProvider: customProvider } = useCustomContent([
'dashboard_auth:custom_provider',
])
const {
dashboardAuthCustomProvider: customProvider,
dashboardAuthCustomProviders: customProvidersNew,
} = useCustomContent(['dashboard_auth:custom_provider', 'dashboard_auth:custom_providers'])
// [Joshen] This is just for backward compatibility - singular customProvider needs to be deprecated subsequently
// Just need to remove customProvider and rename customProvidersNew to customProviders
const customProviders = customProvidersNew ?? (customProvider ? [customProvider] : [])
const { focusProvider } = useInboundBranding('sign-in')
const signInProviders = useEnabledIdentityProviders().filter((provider) => provider.showOnSignIn)
@@ -44,11 +49,15 @@ const SignInPage: NextPageWithLayout = () => {
dividerBgClass = 'bg-studio'
) => {
const showOrDivider =
(providers.length > 0 || signInWithSsoEnabled || !!customProvider) && signInWithEmailEnabled
(providers.length > 0 || signInWithSsoEnabled || customProviders.length > 0) &&
signInWithEmailEnabled
return (
<>
{customProvider && <SignInWithCustom providerName={customProvider} />}
{Array.isArray(customProviders) &&
customProviders.map((providerName: string) => (
<SignInWithCustom key={providerName} providerName={providerName} />
))}
{providers.map((provider) => (
<SignInWithExternalProvider key={provider.id} provider={provider} />
))}
@@ -90,7 +99,7 @@ const SignInPage: NextPageWithLayout = () => {
const hasOtherOptions =
otherProviders.length > 0 ||
signInWithSsoEnabled ||
!!customProvider ||
customProviders.length > 0 ||
signInWithEmailEnabled
return (