diff --git a/apps/web/cypress/tests/auth.spec.ts b/apps/web/cypress/tests/auth.spec.ts
index 2c7b61b8577..67e20fef42f 100644
--- a/apps/web/cypress/tests/auth.spec.ts
+++ b/apps/web/cypress/tests/auth.spec.ts
@@ -1,7 +1,10 @@
 import * as capitalize from 'lodash.capitalize';
-import { JobTitleEnum, jobTitleToLabelMapper } from '@novu/shared';
+import { FeatureFlagsKeysEnum, JobTitleEnum, jobTitleToLabelMapper } from '@novu/shared';
 
 describe('User Sign-up and Login', function () {
+  beforeEach(function () {
+    cy.mockFeatureFlags({ [FeatureFlagsKeysEnum.IS_INFORMATION_ARCHITECTURE_ENABLED]: false });
+  });
   describe('Sign up', function () {
     beforeEach(function () {
       cy.clearDatabase();
@@ -10,7 +13,9 @@ describe('User Sign-up and Login', function () {
 
     it('should allow a visitor to sign-up, login, and logout', function () {
       cy.intercept('**/organization/**/switch').as('appSwitch');
-      cy.visit('/auth/signup');
+      cy.waitLoadFeatureFlags(() => {
+        cy.visit('/auth/signup');
+      });
       cy.getByTestId('fullName').type('Test User');
       cy.getByTestId('email').type('example@example.com');
       cy.getByTestId('password').type('usEr_password_123!');
@@ -30,7 +35,9 @@ describe('User Sign-up and Login', function () {
     });
 
     it('should show account already exists when signing up with already registered mail', function () {
-      cy.visit('/auth/signup');
+      cy.waitLoadFeatureFlags(() => {
+        cy.visit('/auth/signup');
+      });
       cy.getByTestId('fullName').type('Test User');
       cy.getByTestId('email').type('test-user-1@example.com');
       cy.getByTestId('password').type('usEr_password_123!');
@@ -40,7 +47,9 @@ describe('User Sign-up and Login', function () {
     });
 
     it('should show invalid email error when signing up with invalid email', function () {
-      cy.visit('/auth/signup');
+      cy.waitLoadFeatureFlags(() => {
+        cy.visit('/auth/signup');
+      });
       cy.getByTestId('fullName').type('Test User');
       cy.getByTestId('email').type('test-user-1@example.c');
       cy.getByTestId('password').type('usEr_password_123!');
@@ -54,7 +63,9 @@ describe('User Sign-up and Login', function () {
       if (!isCI) return;
 
       cy.intercept('**/organization/**/switch').as('appSwitch');
-      cy.visit('/auth/signup');
+      cy.waitLoadFeatureFlags(() => {
+        cy.visit('/auth/signup');
+      });
 
       cy.loginWithGitHub();
 
@@ -82,7 +93,9 @@ describe('User Sign-up and Login', function () {
       const gitHubUserEmail = Cypress.env('GITHUB_USER_EMAIL');
 
       cy.intercept('**/organization/**/switch').as('appSwitch');
-      cy.visit('/auth/signup');
+      cy.waitLoadFeatureFlags(() => {
+        cy.visit('/auth/signup');
+      });
       cy.getByTestId('fullName').type('Test User');
       cy.getByTestId('email').type(gitHubUserEmail);
       cy.getByTestId('password').type('usEr_password_123!');
@@ -115,13 +128,19 @@ describe('User Sign-up and Login', function () {
     });
 
     it('should request a password reset flow', function () {
-      cy.visit('/auth/reset/request');
+      cy.waitLoadFeatureFlags(() => {
+        cy.visit('/auth/reset/request');
+      });
       cy.getByTestId('email').type(this.session.user.email);
       cy.getByTestId('submit-btn').click();
       cy.getByTestId('success-screen-reset').should('be.visible');
+
       cy.task('passwordResetToken', this.session.user._id).then((token) => {
         cy.visit('/auth/reset/' + token);
       });
+
+      // unfortunately there seems to be a timing issue in in which inputs are disabled
+      cy.wait(500);
       cy.getByTestId('password').type('A123e3e3e3!');
       cy.getByTestId('password-repeat').focus().type('A123e3e3e3!');
 
@@ -137,18 +156,24 @@ describe('User Sign-up and Login', function () {
 
     it('should redirect to the dashboard page when a token exists in query', function () {
       cy.initializeSession({ disableLocalStorage: true }).then((session) => {
-        cy.visit('/auth/login?token=' + session.token);
+        cy.waitLoadFeatureFlags(() => {
+          cy.visit('/auth/login?token=' + session.token);
+        });
         cy.location('pathname').should('equal', '/workflows');
       });
     });
 
     it('should be redirect login with no auth', function () {
-      cy.visit('/');
+      cy.waitLoadFeatureFlags(() => {
+        cy.visit('/');
+      });
       cy.location('pathname').should('equal', '/auth/login');
     });
 
     it('should successfully login the user', function () {
-      cy.visit('/auth/login');
+      cy.waitLoadFeatureFlags(() => {
+        cy.visit('/auth/login');
+      });
 
       cy.getByTestId('email').type('test-user-1@example.com');
       cy.getByTestId('password').type('123qwe!@#');
@@ -157,7 +182,9 @@ describe('User Sign-up and Login', function () {
     });
 
     it('should show incorrect email or password error when authenticating with bad credentials', function () {
-      cy.visit('/auth/login');
+      cy.waitLoadFeatureFlags(() => {
+        cy.visit('/auth/login');
+      });
 
       cy.getByTestId('email').type('test-user-1@example.com');
       cy.getByTestId('password').type('123456');
@@ -166,7 +193,9 @@ describe('User Sign-up and Login', function () {
     });
 
     it('should show invalid email error when authenticating with invalid email', function () {
-      cy.visit('/auth/login');
+      cy.waitLoadFeatureFlags(() => {
+        cy.visit('/auth/login');
+      });
 
       cy.getByTestId('email').type('test-user-1@example.c');
       cy.getByTestId('password').type('123456');
@@ -175,7 +204,9 @@ describe('User Sign-up and Login', function () {
     });
 
     it('should show incorrect email or password error when authenticating with non-existing email', function () {
-      cy.visit('/auth/login');
+      cy.waitLoadFeatureFlags(() => {
+        cy.visit('/auth/login');
+      });
 
       cy.getByTestId('email').type('test-user-1@example.de');
       cy.getByTestId('password').type('123456');
@@ -197,6 +228,8 @@ describe('User Sign-up and Login', function () {
       cy.getByTestId('password').type('123qwe!@#');
       cy.getByTestId('submit-btn').click();
 
+      cy.waitLoadFeatureFlags();
+
       cy.location('pathname').should('equal', '/workflows');
 
       // setting current time in future, to simulate expired token
@@ -207,6 +240,8 @@ describe('User Sign-up and Login', function () {
 
       cy.visit('/subscribers');
 
+      cy.waitLoadFeatureFlags();
+
       // checking if token is removed from local storage
       cy.getLocalStorage('auth_token').should('be.null');
       // checking if user is redirected to login page
diff --git a/apps/web/cypress/tests/invites.spec.ts b/apps/web/cypress/tests/invites.spec.ts
index 54733a8c16c..3e84386a203 100644
--- a/apps/web/cypress/tests/invites.spec.ts
+++ b/apps/web/cypress/tests/invites.spec.ts
@@ -1,7 +1,9 @@
+import { FeatureFlagsKeysEnum } from '@novu/shared';
 import * as capitalize from 'lodash.capitalize';
 
 describe('Invites module', function () {
   beforeEach(function () {
+    cy.mockFeatureFlags({ [FeatureFlagsKeysEnum.IS_INFORMATION_ARCHITECTURE_ENABLED]: false });
     cy.task('clearDatabase');
   });
 
@@ -120,7 +122,9 @@ describe('Invites module', function () {
       cy.initializeSession().as('session');
 
       const invitationPath = `/auth/invitation/${this.token}`;
-      cy.visit(invitationPath);
+      cy.waitLoadFeatureFlags(() => {
+        cy.visit(invitationPath);
+      });
       cy.getByTestId('success-screen-reset').click();
 
       // checking if token is removed from local storage
diff --git a/apps/web/src/AppRoutes.tsx b/apps/web/src/AppRoutes.tsx
index 0b5fb6b2b50..514f9b219ab 100644
--- a/apps/web/src/AppRoutes.tsx
+++ b/apps/web/src/AppRoutes.tsx
@@ -49,6 +49,7 @@ import { useSettingsRoutes } from './SettingsRoutes';
 
 export const AppRoutes = () => {
   const isImprovedOnboardingEnabled = useFeatureFlag(FeatureFlagsKeysEnum.IS_IMPROVED_ONBOARDING_ENABLED);
+  const isInformationArchitectureEnabled = useFeatureFlag(FeatureFlagsKeysEnum.IS_INFORMATION_ARCHITECTURE_ENABLED);
 
   return (
     <Routes>
@@ -116,13 +117,16 @@ export const AppRoutes = () => {
         <Route path={ROUTES.TEAM} element={<MembersInvitePage />} />
         <Route path={ROUTES.CHANGES} element={<PromoteChangesPage />} />
         <Route path={ROUTES.SUBSCRIBERS} element={<SubscribersList />} />
-        <Route path={ROUTES.BRAND} element={<BrandPage />}>
-          <Route path="" element={<BrandingForm />} />
-          <Route path="layouts" element={<LayoutsListPage />} />
-        </Route>
-        <Route path={ROUTES.LAYOUT} element={<LayoutsPage />}>
-          <Route path="" element={<LayoutsListPage />} />
-        </Route>
+        {!isInformationArchitectureEnabled ? (
+          <Route path={ROUTES.BRAND} element={<BrandPage />}>
+            <Route path="" element={<BrandingForm />} />
+            <Route path="layouts" element={<LayoutsListPage />} />
+          </Route>
+        ) : (
+          <Route path={ROUTES.LAYOUT} element={<LayoutsPage />}>
+            <Route path="" element={<LayoutsListPage />} />
+          </Route>
+        )}
         <Route path="/translations/*" element={<TranslationRoutes />} />
       </Route>
     </Routes>
diff --git a/apps/web/src/Providers.tsx b/apps/web/src/Providers.tsx
index 8b9a0b1eed9..bdc47fd51f7 100644
--- a/apps/web/src/Providers.tsx
+++ b/apps/web/src/Providers.tsx
@@ -1,12 +1,15 @@
-import { CONTEXT_PATH, LAUNCH_DARKLY_CLIENT_SIDE_ID, SegmentProvider } from '@novu/shared-web';
+import { Loader } from '@mantine/core';
+import { colors } from '@novu/design-system';
+import { CONTEXT_PATH, SegmentProvider } from '@novu/shared-web';
 import * as Sentry from '@sentry/react';
 import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import { withLDProvider } from 'launchdarkly-react-client-sdk';
 import { PropsWithChildren } from 'react';
 import { HelmetProvider } from 'react-helmet-async';
 import { BrowserRouter } from 'react-router-dom';
 import { api } from './api/api.client';
+import { LaunchDarklyProvider } from './components/launch-darkly';
 import { AuthProvider } from './components/providers/AuthProvider';
+import { css } from './styled-system/css';
 
 const defaultQueryFn = async ({ queryKey }: { queryKey: string }) => {
   const response = await api.get(`${queryKey[0]}`);
@@ -22,28 +25,41 @@ const queryClient = new QueryClient({
   },
 });
 
+/** Full-page loader that uses color-preferences for background */
+const fallbackDisplay = (
+  <div
+    className={css({
+      h: '100dvh',
+      w: '100dvw',
+      display: 'grid',
+      placeItems: 'center',
+      bg: 'surface.page',
+      // Root element may not have loaded so rely on OS
+      _osDark: { bg: 'legacy.BGDark' },
+      _osLight: { bg: 'legacy.BGLight' },
+    })}
+  >
+    <Loader size={64} variant="bars" color={colors.gradientMiddle} />
+  </div>
+);
+
 /**
  * Centralized Provider hierarchy.
  */
 const Providers: React.FC<PropsWithChildren<{}>> = ({ children }) => {
   return (
     <SegmentProvider>
-      <HelmetProvider>
+      <QueryClientProvider client={queryClient}>
         <BrowserRouter basename={CONTEXT_PATH}>
-          <QueryClientProvider client={queryClient}>
-            <AuthProvider>{children}</AuthProvider>
-          </QueryClientProvider>
+          <AuthProvider>
+            <LaunchDarklyProvider fallbackDisplay={fallbackDisplay}>
+              <HelmetProvider>{children}</HelmetProvider>
+            </LaunchDarklyProvider>
+          </AuthProvider>
         </BrowserRouter>
-      </HelmetProvider>
+      </QueryClientProvider>
     </SegmentProvider>
   );
 };
 
-export default Sentry.withProfiler(
-  withLDProvider({
-    clientSideID: LAUNCH_DARKLY_CLIENT_SIDE_ID,
-    reactOptions: {
-      useCamelCaseFlagKeys: false,
-    },
-  })(Providers)
-);
+export default Sentry.withProfiler(Providers);
diff --git a/apps/web/src/SettingsRoutes.tsx b/apps/web/src/SettingsRoutes.tsx
index 1d5c75e513a..b469ccc6726 100644
--- a/apps/web/src/SettingsRoutes.tsx
+++ b/apps/web/src/SettingsRoutes.tsx
@@ -6,7 +6,6 @@ import { ProductLead } from './components/utils/ProductLead';
 import { ROUTES } from './constants/routes.enum';
 import { useFeatureFlag } from './hooks';
 import { BillingRoutes } from './pages/BillingPages';
-import { BrandingForm as BrandingFormOld } from './pages/brand/tabs';
 import { BrandingPage } from './pages/brand/tabs/v2';
 import { MembersInvitePage as MembersInvitePageNew } from './pages/invites/v2/MembersInvitePage';
 import { AccessSecurityPage, ApiKeysPage, BillingPage, TeamPage, UserProfilePage } from './pages/settings';
@@ -42,7 +41,7 @@ export const useSettingsRoutes = () => {
     );
   }
 
-  /* TODO: remove all routes above once information architecture is fully enabled */
+  /* TODO: remove all routes below once information architecture is fully enabled */
   return (
     <>
       <Route path={ROUTES.SETTINGS} element={<SettingsPageOld />}>
@@ -50,7 +49,6 @@ export const useSettingsRoutes = () => {
         <Route path="billing/*" element={<BillingRoutes />} />
         <Route path="email" element={<EmailSettings />} />
         <Route path="team" element={<MembersInvitePageNew />} />
-        <Route path="brand" element={<BrandingFormOld />} />
         <Route
           path="permissions"
           element={
diff --git a/apps/web/src/components/launch-darkly/LaunchDarklyProvider.tsx b/apps/web/src/components/launch-darkly/LaunchDarklyProvider.tsx
new file mode 100644
index 00000000000..191348a5369
--- /dev/null
+++ b/apps/web/src/components/launch-darkly/LaunchDarklyProvider.tsx
@@ -0,0 +1,101 @@
+import * as Sentry from '@sentry/react';
+
+import { IOrganizationEntity } from '@novu/shared';
+import { asyncWithLDProvider } from 'launchdarkly-react-client-sdk';
+import { PropsWithChildren, ReactNode, useEffect, useMemo, useRef, useState } from 'react';
+import { useFeatureFlags, useAuthContext, LAUNCH_DARKLY_CLIENT_SIDE_ID } from '@novu/shared-web';
+import { selectShouldInitializeLaunchDarkly } from './utils/selectShouldInitializeLaunchDarkly';
+import { selectShouldShowLaunchDarklyFallback } from './utils/selectShouldShowLaunchDarklyFallback';
+
+/** A provider with children required */
+type GenericLDProvider = Awaited<ReturnType<typeof asyncWithLDProvider>>;
+
+/** Simply renders the children */
+const DEFAULT_GENERIC_PROVIDER: GenericLDProvider = ({ children }) => <>{children}</>;
+
+export interface ILaunchDarklyProviderProps {
+  /** Renders when LaunchDarkly is enabled and is awaiting initialization */
+  fallbackDisplay: ReactNode;
+}
+
+/**
+ * Async provider for feature flags.
+ *
+ * @requires AuthProvider must be wrapped in the AuthProvider.
+ */
+export const LaunchDarklyProvider: React.FC<PropsWithChildren<ILaunchDarklyProviderProps>> = ({
+  children,
+  fallbackDisplay,
+}) => {
+  const LDProvider = useRef<GenericLDProvider>(DEFAULT_GENERIC_PROVIDER);
+  const [isLDReady, setIsLDReady] = useState<boolean>(false);
+
+  const authContext = useAuthContext();
+  if (!authContext) {
+    throw new Error('LaunchDarklyProvider must be used within <AuthProvider>!');
+  }
+  const { currentOrganization } = authContext;
+
+  const shouldInitializeLd = useMemo(() => selectShouldInitializeLaunchDarkly(authContext), [authContext]);
+
+  useEffect(() => {
+    if (!shouldInitializeLd) {
+      return;
+    }
+
+    const fetchLDProvider = async () => {
+      try {
+        LDProvider.current = await asyncWithLDProvider({
+          clientSideID: LAUNCH_DARKLY_CLIENT_SIDE_ID,
+          reactOptions: {
+            useCamelCaseFlagKeys: false,
+          },
+          // determine which context to use based on if an organization is available
+          context: currentOrganization
+            ? {
+                kind: 'organization',
+                key: currentOrganization._id,
+                name: currentOrganization.name,
+              }
+            : {
+                /**
+                 * When user is not authenticated, assigns an id to them to ensure consistent results.
+                 * https://docs.launchdarkly.com/sdk/features/anonymous#javascript
+                 */
+                kind: 'user',
+                anonymous: true,
+              },
+        });
+      } catch (err: unknown) {
+        Sentry.captureException(err);
+      } finally {
+        setIsLDReady(true);
+      }
+    };
+
+    fetchLDProvider();
+  }, [setIsLDReady, shouldInitializeLd, currentOrganization]);
+
+  /**
+   * For self-hosted, LD will not be enabled, so do not block initialization.
+   * Must not show the fallback if the user isn't logged-in to avoid issues with un-authenticated routes (i.e. login).
+   */
+  if (selectShouldShowLaunchDarklyFallback(authContext, isLDReady)) {
+    return <>{fallbackDisplay}</>;
+  }
+
+  return (
+    <LDProvider.current>
+      <LaunchDarklyClientWrapper org={currentOrganization}>{children}</LaunchDarklyClientWrapper>
+    </LDProvider.current>
+  );
+};
+
+/**
+ * Refreshes feature flags on org change using the LaunchDarkly client from the provider.
+ */
+function LaunchDarklyClientWrapper({ children, org }: PropsWithChildren<{ org?: IOrganizationEntity }>) {
+  useFeatureFlags(org);
+
+  return <>{children}</>;
+}
diff --git a/apps/web/src/components/launch-darkly/index.ts b/apps/web/src/components/launch-darkly/index.ts
new file mode 100644
index 00000000000..b1c5cfef1eb
--- /dev/null
+++ b/apps/web/src/components/launch-darkly/index.ts
@@ -0,0 +1 @@
+export * from './LaunchDarklyProvider';
diff --git a/apps/web/src/components/launch-darkly/utils/selectShouldInitializeLaunchDarkly.tsx b/apps/web/src/components/launch-darkly/utils/selectShouldInitializeLaunchDarkly.tsx
new file mode 100644
index 00000000000..d8131950cfd
--- /dev/null
+++ b/apps/web/src/components/launch-darkly/utils/selectShouldInitializeLaunchDarkly.tsx
@@ -0,0 +1,31 @@
+import { selectHasUserCompletedSignUp, UserContext } from '@novu/shared-web';
+import { checkShouldUseLaunchDarkly } from '@novu/shared-web';
+
+/** Determine if LaunchDarkly should be initialized based on the current auth context */
+export function selectShouldInitializeLaunchDarkly(userCtx: UserContext): boolean {
+  const { isLoggedIn, currentOrganization } = userCtx;
+  // don't show fallback if LaunchDarkly isn't enabled
+  if (!checkShouldUseLaunchDarkly()) {
+    return false;
+  }
+
+  // enable feature flags for unauthenticated areas of the app
+  if (!isLoggedIn) {
+    return true;
+  }
+
+  /**
+   * Allow LD to load when the user is created but still in onboarding.
+   *
+   * After users provide their name, email, and password, we take them to an onboarding step where they provide details
+   * such as job title, use cases, company name, etc. When they reach this page, isLoggedIn is true, but they don't
+   * have an organizationId yet that we can use for org-based feature flags. To prevent from blocking this page
+   * from loading during this "limbo" state, we should initialize LD with the anonymous context.
+   */
+  if (!selectHasUserCompletedSignUp(userCtx)) {
+    return true;
+  }
+
+  // if a user is fully on-boarded, but no organization has loaded, we must wait for the organization to initialize the client.
+  return !!currentOrganization;
+}
diff --git a/apps/web/src/components/launch-darkly/utils/selectShouldShowLaunchDarklyFallback.tsx b/apps/web/src/components/launch-darkly/utils/selectShouldShowLaunchDarklyFallback.tsx
new file mode 100644
index 00000000000..aa4e9839abe
--- /dev/null
+++ b/apps/web/src/components/launch-darkly/utils/selectShouldShowLaunchDarklyFallback.tsx
@@ -0,0 +1,23 @@
+import { selectHasUserCompletedSignUp, UserContext, checkShouldUseLaunchDarkly } from '@novu/shared-web';
+
+/** Determine if a fallback should be shown instead of the provider-wrapped application */
+export function selectShouldShowLaunchDarklyFallback(userCtx: UserContext, isLDReady: boolean): boolean {
+  const { isLoggedIn, currentOrganization } = userCtx;
+  // don't show fallback if LaunchDarkly isn't enabled
+  if (!checkShouldUseLaunchDarkly()) {
+    return false;
+  }
+
+  // don't show fallback for unauthenticated areas of the app
+  if (!isLoggedIn) {
+    return false;
+  }
+
+  // don't show fallback if user is still in onboarding
+  if (!selectHasUserCompletedSignUp(userCtx)) {
+    return false;
+  }
+
+  // if the organization is not loaded or we haven't loaded LD, show the fallback
+  return !currentOrganization || !isLDReady;
+}
diff --git a/libs/shared-web/src/hooks/useFeatureFlags.ts b/libs/shared-web/src/hooks/useFeatureFlags.ts
index 488b0441eef..91a399480f4 100644
--- a/libs/shared-web/src/hooks/useFeatureFlags.ts
+++ b/libs/shared-web/src/hooks/useFeatureFlags.ts
@@ -1,19 +1,19 @@
 import { FeatureFlagsKeysEnum, IOrganizationEntity, prepareBooleanStringFeatureFlag } from '@novu/shared';
-import { useFlags } from 'launchdarkly-react-client-sdk';
-import { useLDClient } from 'launchdarkly-react-client-sdk';
+import { useFlags, useLDClient } from 'launchdarkly-react-client-sdk';
 import { useEffect } from 'react';
+import { checkShouldUseLaunchDarkly } from '../utils/checkShouldUseLaunchDarkly';
 
 import { FEATURE_FLAGS } from '../config';
 
-export const useFeatureFlags = (organization: IOrganizationEntity) => {
+export const useFeatureFlags = (organization?: IOrganizationEntity) => {
   const ldClient = useLDClient();
 
   useEffect(() => {
-    if (!organization?._id) {
+    if (!checkShouldUseLaunchDarkly() || !organization?._id || !ldClient) {
       return;
     }
 
-    ldClient?.identify({
+    ldClient.identify({
       kind: 'organization',
       key: organization._id,
       name: organization.name,
@@ -24,10 +24,12 @@ export const useFeatureFlags = (organization: IOrganizationEntity) => {
 };
 
 export const useFeatureFlag = (key: FeatureFlagsKeysEnum): boolean => {
-  const { [key]: featureFlag } = useFlags();
+  /** We knowingly break the rule of hooks here to avoid making any LaunchDarkly calls when it is disabled */
+  // eslint-disable-next-line
+  const flagValue = checkShouldUseLaunchDarkly() ? useFlags()[key] : undefined;
   const fallbackValue = false;
   const value = FEATURE_FLAGS[key];
   const defaultValue = prepareBooleanStringFeatureFlag(value, fallbackValue);
 
-  return featureFlag ?? defaultValue;
+  return flagValue ?? defaultValue;
 };
diff --git a/libs/shared-web/src/index.ts b/libs/shared-web/src/index.ts
index c903bad34fb..493e3ce358d 100644
--- a/libs/shared-web/src/index.ts
+++ b/libs/shared-web/src/index.ts
@@ -5,3 +5,4 @@ export * from './hooks';
 export * from './providers';
 export * from './constants';
 export * from './components';
+export * from './utils';
diff --git a/libs/shared-web/src/providers/AuthProvider.tsx b/libs/shared-web/src/providers/AuthProvider.tsx
index 04c72b2cc72..4534bf64587 100644
--- a/libs/shared-web/src/providers/AuthProvider.tsx
+++ b/libs/shared-web/src/providers/AuthProvider.tsx
@@ -1,9 +1,10 @@
 import React, { useContext } from 'react';
 import { IOrganizationEntity, IUserEntity, IJwtPayload } from '@novu/shared';
-import { useAuthController, useFeatureFlags } from '../hooks';
+import { useAuthController } from '../hooks';
 
-type UserContext = {
+export type UserContext = {
   token: string | null;
+  isLoggedIn: boolean;
   currentUser: IUserEntity | undefined;
   isUserLoading: boolean;
   currentOrganization: IOrganizationEntity | undefined;
@@ -15,6 +16,7 @@ type UserContext = {
 
 const AuthContext = React.createContext<UserContext>({
   token: null,
+  isLoggedIn: false,
   currentUser: undefined,
   isUserLoading: true,
   setToken: undefined as any,
@@ -27,13 +29,14 @@ const AuthContext = React.createContext<UserContext>({
 export const useAuthContext = (): UserContext => useContext(AuthContext);
 
 export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
-  const { token, setToken, user, organization, isUserLoading, logout, jwtPayload, organizations } = useAuthController();
-  useFeatureFlags(organization);
+  const { token, setToken, user, organization, isUserLoading, logout, jwtPayload, organizations, isLoggedIn } =
+    useAuthController();
 
   return (
     <AuthContext.Provider
       value={{
         currentUser: user,
+        isLoggedIn,
         isUserLoading,
         currentOrganization: organization,
         organizations,
diff --git a/libs/shared-web/src/utils/auth-selectors/index.ts b/libs/shared-web/src/utils/auth-selectors/index.ts
new file mode 100644
index 00000000000..53240e5334f
--- /dev/null
+++ b/libs/shared-web/src/utils/auth-selectors/index.ts
@@ -0,0 +1 @@
+export * from './selectHasUserCompletedSignUp';
diff --git a/libs/shared-web/src/utils/auth-selectors/selectHasUserCompletedSignUp.ts b/libs/shared-web/src/utils/auth-selectors/selectHasUserCompletedSignUp.ts
new file mode 100644
index 00000000000..31900f917cf
--- /dev/null
+++ b/libs/shared-web/src/utils/auth-selectors/selectHasUserCompletedSignUp.ts
@@ -0,0 +1,13 @@
+import { UserContext } from '../../providers';
+
+/**
+ * Determine if a user is fully-registered; if not, they're still in onboarding.
+ */
+export const selectHasUserCompletedSignUp = (userCtx: UserContext): boolean => {
+  if (!userCtx) {
+    return false;
+  }
+
+  // User has completed registration if they have an associated orgId.
+  return !!userCtx.jwtPayload?.organizationId;
+};
diff --git a/libs/shared-web/src/utils/checkShouldUseLaunchDarkly.ts b/libs/shared-web/src/utils/checkShouldUseLaunchDarkly.ts
new file mode 100644
index 00000000000..6cc3a28f6f0
--- /dev/null
+++ b/libs/shared-web/src/utils/checkShouldUseLaunchDarkly.ts
@@ -0,0 +1,6 @@
+import { LAUNCH_DARKLY_CLIENT_SIDE_ID } from '../config';
+
+/** Determine if client-side LaunchDarkly should be enabled */
+export const checkShouldUseLaunchDarkly = (): boolean => {
+  return !!LAUNCH_DARKLY_CLIENT_SIDE_ID;
+};
diff --git a/libs/shared-web/src/utils/index.ts b/libs/shared-web/src/utils/index.ts
index 18c52ee5ae1..6cd450c1251 100644
--- a/libs/shared-web/src/utils/index.ts
+++ b/libs/shared-web/src/utils/index.ts
@@ -1 +1,3 @@
 export * from './segment';
+export * from './checkShouldUseLaunchDarkly';
+export * from './auth-selectors';