Framer Plugin Workaround
Framer is currently having issues with their package management service, and some users may face errors when using our Framer plugin. To unblock developers while the Framer team is looking into a solution, we have published this workaround guide.
The Issue
The Framer plugin utilizes our React SDK to embed the flow component and handle session management. In certain Framer projects, the Framer package manager is unable to resolve the import of our React SDK and also resolve the other components on the page.
The Workaround
Although it requires some manual configuration, you can follow these steps to add Descope authentication to their Framer site, utilizing our WebJS and Web Component SDKs instead of React. Make sure to follow all of the steps completely:
1. Create Component File
First, you will create a new code file in your Framer project. Call it DescopeWebComponent.tsx and select "New Component".

After creating the file, delete the contents of the file and paste the following instead:
import { useEffect, useRef } from "react"
const DescopeLogin = () => {
const containerRef = useRef(null)
const projectId = "<your-project-id>" // replace with your Descope Project ID
const flowId = "<your-flow-id>" // replace with your Descope Flow ID
useEffect(() => {
// Dynamically load Descope scripts
const loadScripts = async () => {
const loadScript = (src) =>
new Promise((resolve, reject) => {
const script = document.createElement("script")
script.src = src
script.async = true
script.onload = resolve
script.onerror = reject
document.head.appendChild(script)
})
try {
await loadScript(
"https://descopecdn.com/npm/@descope/web-component@3.21.0/dist/index.js"
)
await loadScript(
"https://descopecdn.com/npm/@descope/web-js-sdk@1.16.0/dist/index.umd.js"
)
const sdk = window.Descope({
projectId: projectId,
persistTokens: true,
autoRefresh: true,
})
const sessionToken = sdk.getSessionToken()
const notValidToken = sessionToken
? sdk.isJwtExpired(sessionToken)
: true
if (!sessionToken || notValidToken) {
const container = containerRef.current
if (container && !container.querySelector("descope-wc")) {
container.innerHTML = `<descope-wc project-id="${projectId}" flow-id="${flowId}"></descope-wc>`
const wcElement = container.querySelector("descope-wc")
wcElement.addEventListener("success", () => {
sdk.refresh()
})
wcElement.addEventListener("error", (err) => {
console.error("Descope error:", err)
})
}
}
} catch (error) {
console.error("Error loading Descope scripts", error)
}
}
loadScripts()
}, [])
return (
<div>
<div ref={containerRef}></div>
</div>
)
}
import React from "react"
function App() {
return (
<div className="App">
<DescopeLogin />
</div>
)
}
export default AppReplace <your-project-id> and <your-flow-id> with your Descope Project and Flow IDs, and save the file.
2. Insert Flow Component
Now you can drag and drop your flow component from the Project Assets toolbar onto anywhere you'd like on your page.

3. Create Overrides File
Now that you've added authentication to your page, you need a way to manage the authenticated user. We will achieve this using Framer Overrides.
You will create a new code file in your Framer project. Call it DescopeOverrides.tsx and select "New Override".

After creating the file, delete the contents of the file and paste the following instead:
"use client"
import { Override } from "framer"
import React, { useState, useEffect, useCallback } from "react"
import type { ComponentType } from "react"
declare global {
interface Window {
Descope: any
}
}
const projectId = "<your-project-id>" // replace with your Descope Project ID
const redirectPage = "<your-redirect-url>" // replace with your Redirect URL
const loadDescopeScripts = (): Promise<void> => {
return new Promise((resolve, reject) => {
if (typeof window === "undefined") return reject("Window undefined")
if (document.getElementById("descope-webjs-sdk")) {
resolve()
return
}
const webJsScript = document.createElement("script")
webJsScript.id = "descope-webjs-sdk"
webJsScript.src =
"https://descopecdn.com/npm/@descope/web-js-sdk@1.16.0/dist/index.umd.js"
webJsScript.async = true
const webComponentScript = document.createElement("script")
webComponentScript.id = "descope-web-component-sdk"
webComponentScript.src =
"https://descopecdn.com/npm/@descope/web-component@3.21.0/dist/index.js"
webComponentScript.async = true
webComponentScript.type = "module"
webJsScript.onload = () => {
document.head.appendChild(webComponentScript)
webComponentScript.onload = () => resolve()
webComponentScript.onerror = (e) => reject(e)
}
webJsScript.onerror = (e) => reject(e)
document.head.appendChild(webJsScript)
})
}
let sdkInstance: any = null
const initSDK = () => {
if (typeof window === "undefined") return null
if (!window.Descope) return null
if (!sdkInstance) {
sdkInstance = window.Descope({
projectId,
persistTokens: true,
autoRefresh: true,
})
}
return sdkInstance
}
const logout = async () => {
if (!sdkInstance) return
await sdkInstance.logoutAll()
window.location.reload()
}
const ClientSideWrapper: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [isClient, setIsClient] = useState(false)
useEffect(() => {
setIsClient(true)
}, [])
return isClient ? <>{children}</> : null
}
const AuthWrapperContent = ({
Component,
hidden,
redirect,
redirectURL,
...props
}: {
Component: ComponentType<any>
hidden: boolean
redirect: boolean
redirectURL: string
[key: string]: any
}) => {
const [sdk, setSdk] = useState<any>(null)
const [isReady, setIsReady] = useState(false)
const [isAuthenticated, setIsAuthenticated] = useState(false)
useEffect(() => {
const init = async () => {
try {
await loadDescopeScripts()
const sdkInstance = initSDK()
setSdk(sdkInstance)
if (sdkInstance) {
const sessionToken = sdkInstance.getSessionToken()
const isExpired = sessionToken
? sdkInstance.isJwtExpired(sessionToken)
: true
if (!sessionToken || isExpired) {
try {
await sdkInstance.refresh()
} catch (e) {
console.warn("Token refresh failed", e)
setIsAuthenticated(false)
setIsReady(true)
return
}
}
const freshToken = sdkInstance.getSessionToken()
const stillExpired = freshToken
? sdkInstance.isJwtExpired(freshToken)
: true
setIsAuthenticated(!!freshToken && !stillExpired)
} else {
setIsAuthenticated(false)
}
} catch (err) {
console.error("Descope init failed", err)
setIsAuthenticated(false)
} finally {
setIsReady(true)
}
}
init()
}, [])
useEffect(() => {
if (!isReady) return
if (hidden && !isAuthenticated && redirect) {
window.location.href = redirectURL
} else if (!hidden && isAuthenticated && redirect) {
window.location.href = redirectURL
}
}, [isReady, isAuthenticated, hidden, redirect, redirectURL])
if (!isReady) return <div>Loading...</div>
if (hidden) {
return isAuthenticated ? <Component {...props} /> : null
} else {
return !isAuthenticated ? <Component {...props} /> : null
}
}
const LogoutButtonContent = ({
Component,
...props
}: {
Component: ComponentType<any>
}) => {
const [isReady, setIsReady] = useState(false)
useEffect(() => {
loadDescopeScripts()
.then(() => {
initSDK()
setIsReady(true)
})
.catch(() => {})
}, [])
const handleLogout = useCallback(() => {
logout()
}, [])
if (!isReady) return null
return <Component {...props} onClick={handleLogout} />
}
export const protectedComponent = (
Component: ComponentType<any>
): ComponentType<any> => {
return (props) => (
<ClientSideWrapper>
<React.StrictMode>
<AuthWrapperContent
Component={Component}
hidden={true}
redirect={false}
redirectURL=""
{...props}
/>
</React.StrictMode>
</ClientSideWrapper>
)
}
export const unprotectedComponent = (
Component: ComponentType<any>
): ComponentType<any> => {
return (props) => (
<ClientSideWrapper>
<React.StrictMode>
<AuthWrapperContent
Component={Component}
hidden={false}
redirect={false}
redirectURL=""
{...props}
/>
</React.StrictMode>
</ClientSideWrapper>
)
}
export const protectedPage = (
Component: ComponentType<any>
): ComponentType<any> => {
return (props) => (
<ClientSideWrapper>
<React.StrictMode>
<AuthWrapperContent
Component={Component}
hidden={true}
redirect={true}
redirectURL={redirectPage}
{...props}
/>
</React.StrictMode>
</ClientSideWrapper>
)
}
export const unprotectedPage = (
Component: ComponentType<any>
): ComponentType<any> => {
return (props) => (
<ClientSideWrapper>
<React.StrictMode>
<AuthWrapperContent
Component={Component}
hidden={false}
redirect={true}
redirectURL={redirectPage}
{...props}
/>
</React.StrictMode>
</ClientSideWrapper>
)
}
export const logoutButton = (
Component: ComponentType<any>
): ComponentType<any> => {
return (props) => (
<ClientSideWrapper>
<React.StrictMode>
<LogoutButtonContent Component={Component} {...props} />
</React.StrictMode>
</ClientSideWrapper>
)
}
export function RefreshSession(): Override {
initSDK() // Call this at the top of the page via the override
return {}
}Replace <your-project-id> with your DescopeD and <your-redirect-url> with the URL of the page you want unauthenticated users to be redirected to (usually your login page), and save the file.
4. Apply Overrides
You will have to apply a refresh override on each page of your Framer project so that the user's session token is correctly refreshed.
To apply the refresh override:
- Select a page
- Navigate to "Code Overrides" at the bottom of the toolbar on the right
- Select "DescopeOverrides" as the file, and "RefreshSession" as the override.
The Descope Overrides file also includes overrides that you can utilize to protect your pages and components from unauthenticated users. You can learn more about those overrides here.