Building an In-App Integrations Catalog
In this tutorial, we'll use the Paragon SDK to build an integrations catalog into your app.
This guide provides an example implementation of building an integrations catalog into your React app, using TypeScript. For a full implementation, see our Example App built in Next.js.

The repository with the completed code is available here: https://github.com/useparagon/paragon-integrations-catalog-tutorial
- Note: The demo repository signs Paragon User Tokens in the frontend application, which should NOT be used in production. Replace
getParagonUserToken
with your own app's Paragon User Token generation, which should be performed on your server only.
- This guide assumes that you've already installed the SDK and configured User Authentication. We will change your implementation slightly so that your React app can listen for the SDK events for loading and authenticating.
First, we will remove the global
<script>
tag that you may have added to your application.- <script type="text/javascript" src="https://cdn.useparagon.com/latest/sdk/index.js"></script>
We will replace this in the next step with a hook that can mount the same
script
tag to your application, but in a way that your React app can listen for when it finishes loading.Next, create a new file to define a hook. We'll call this
useParagonGlobal.ts
. Copy the following contents to this file:// useParagonGlobal.ts
import { useCallback, useEffect, useRef, useState } from "react";
export default function useParagonGlobal() {
const mountedParagon = useRef(false);
const [paragonReady, setParagonReady] = useState(false);
const initParagon = useCallback(async () => {
if (window.paragon) {
setParagonReady(true);
}
}, []);
useEffect(() => {
if (typeof window !== "undefined" && !paragonReady && !mountedParagon.current) {
if (window.paragon) {
initParagon();
} else {
mountedParagon.current = true;
const paragonSrc = document.createElement("script");
paragonSrc.src = "https://cdn.useparagon.com/latest/sdk/index.js";
paragonSrc.onload = initParagon;
document.body.appendChild(paragonSrc);
}
}
}, [paragonReady, initParagon]);
if (paragonReady && window.paragon) {
return window.paragon;
}
return undefined;
}
Create another new file to define a hook used for SDK authentication. We'll call this
useParagonAuth.ts
. Copy the following contents to this file:import { useEffect, useState } from "react";
async function getParagonUserToken(): Promise<string> {
// Replace this with the logic that your app uses for
// fetching the Paragon User Token.
}
export default function useParagonAuth(
paragon?: ConnectSDK
): { user?: ConnectUser; error?: Error } {
const [token, setToken] = useState<string | null>(null);
const [user, setUser] = useState<ConnectUser | undefined>();
const [error, setError] = useState<Error | undefined>();
useEffect(() => {
getParagonUserToken().then(setToken).catch(setError);
}, []);
useEffect(() => {
if (paragon && token && !error) {
paragon
.authenticate(PARAGON_PROJECT_ID, token)
.then(() => setUser(paragon.getUser()))
.catch(setError);
}
}, [paragon, token, error]);
return { user, error };
}
These hooks can then be consumed from a component that will render your integrations catalog as follows:
// IntegrationsCatalog.tsx
import useParagonGlobal from "../hooks/useParagonGlobal";
function IntegrationsCatalog() {
// paragon is the SDK singleton or `undefined`
const paragon = useParagonGlobal();
const { user } = useParagonAuth(paragon);
return (<div className="catalog">
<h1>Integrations Catalog</h1>
</div>);
}
export default IntegrationsCatalog;
Next, we'll show the list of integrations loaded from your project. The
paragon
global is loaded as a stateful value, so when the SDK loads, the component will update accordingly:// IntegrationsCatalog.tsx
import useParagonGlobal from "../hooks/useParagonGlobal";
function IntegrationsCatalog() {
// paragon is the SDK singleton or `undefined`
const paragon = useParagonGlobal();
const { user } = useParagonAuth(paragon);
return (<div className="catalog">
<h1>Integrations Catalog</h1>
+ {paragon &&
+ paragon.getIntegrationMetadata().map((integration) => {
+ return <div
+ key={integration.type}
+ onClick={() => paragon.connect(integration.type)}
+ >
+ <img src={integration.icon} width={20} height={20} />
+ <p>{integration.name}</p>
+ </div>;
+ })}
</div>);
}
export default IntegrationsCatalog;
After checking that
paragon
is not undefined, this snippet calls .getIntegrationMetadata
to get all active integrations in your project, along with their names and icon URLs.For every integration, we display their icons and names. We also set up an event handler to call
.connect
with the integration.type
when the element is clicked. This will display the Connect Portal over your app, prompting the user to connect an account to the specified integration.Result:

When you click on any one of the integrations, the Connect Portal overlay appears over your app.
Next, we'll update your integrations catalog to display your user's account status for each integration within the element that shows the icon and name.
We can get the account status using
.getUser
, which returns the current state of the authenticated user from the SDK, including their connected integrations and enabled workflows.// IntegrationsCatalog.tsx
import useParagonGlobal from "../hooks/useParagonGlobal";
function IntegrationsCatalog() {
// paragon is the SDK singleton or `undefined`
const paragon = useParagonGlobal();
const { user } = useParagonAuth(paragon);
return (<div className="catalog">
<h1>Integrations Catalog</h1>
{paragon &&
paragon.getIntegrationMetadata().map((integration) => {
+ const integrationEnabled = user?.integrations?.[integration.type]?.enabled;
return <div
key={integration.type}
onClick={() => paragon.connect(integration.type)}
>
<img src={integration.icon} width={20} height={20} />
<p>{integration.name}</p>
+ <p>{integrationEnabled ? "Connected" : "Not connected"}</p>
</div>
})}
</div>);
}
export default IntegrationsCatalog;
Result:

Each integration will show the "Connected" or "Not connected" status for the current user.
Finally, we want to make sure that the catalog components that we've created update automatically when the SDK handles a state change with one of our user's integrations. Specifically, when a user connects or disconnects their account, we want the account status to reflect that immediately.
To do this, we can use
.subscribe
, which allows us to listen for global SDK events. We will listen for the events "onIntegrationInstall"
and "onIntegrationUninstall"
to detect when a user has connected or disconnected an account.// useParagonAuth.ts
export default function useParagonAuth(
paragon?: ConnectSDK
): { user?: ConnectUser; error?: Error } {
const [token, setToken] = useState<string | null>(null);
const [user, setUser] = useState<ConnectUser | undefined>();
const [error, setError] = useState<Error | undefined>();
useEffect(() => {
getParagonUserToken().then(setToken).catch(setError);
}, []);
+ // Listen for account state changes
+ useEffect(() => {
+ const listener = () => {
+ if (paragon) {
+ setUser({ ...paragon.getUser() });
+ }
+ };
+ paragon?.subscribe("onIntegrationInstall", listener);
+ paragon?.subscribe("onIntegrationUninstall", listener);
+ return () => {
+ paragon?.unsubscribe("onIntegrationInstall", listener);
+ paragon?.unsubscribe("onIntegrationUninstall", listener);
+ };
+ }, [paragon]);
useEffect(() => {
if (paragon && token && !error) {
paragon
.authenticate(PARAGON_PROJECT_ID, token)
.then(() => setUser(paragon.getUser()))
.catch(setError);
}
}, [paragon, token, error]);
return { user, error };
}
Our integrations catalog will now re-render with the most up-to-date information when account state changes.
Result: You've completed a basic Integrations Catalog with the Connect SDK!

In this guide, you built an integrations catalog that you can embed in your app.
- You defined 2 hooks:
useParagonGlobal
(for mounting the Paragon SDK) anduseParagonAuth
(for authenticating a user). - You used
getIntegrationMetadata
to get all available integrations in the project with name and icon information.
Last modified 10mo ago