diff --git a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx
index e7164270a9..7a49e0414e 100644
--- a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx
+++ b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx
@@ -6,14 +6,6 @@ import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLo
import { SettingsPath } from '@/types/SettingsPath';
import { PermissionFlagType } from '~/generated/graphql';
-const SettingsApiKeys = lazy(() =>
- import('~/pages/settings/developers/api-keys/SettingsApiKeys').then(
- (module) => ({
- default: module.SettingsApiKeys,
- }),
- ),
-);
-
const SettingsGraphQLPlayground = lazy(() =>
import(
'~/pages/settings/developers/playground/SettingsGraphQLPlayground'
@@ -30,14 +22,6 @@ const SettingsRestPlayground = lazy(() =>
),
);
-const SettingsWebhooks = lazy(() =>
- import(
- '~/pages/settings/developers/webhooks/components/SettingsWebhooks'
- ).then((module) => ({
- default: module.SettingsWebhooks,
- })),
-);
-
const SettingsAccountsCalendars = lazy(() =>
import('~/pages/settings/accounts/SettingsAccountsCalendars').then(
(module) => ({
@@ -112,12 +96,6 @@ const SettingsDevelopersApiKeysNew = lazy(() =>
})),
);
-const Releases = lazy(() =>
- import('~/pages/settings/Releases').then((module) => ({
- default: module.Releases,
- })),
-);
-
const SettingsServerlessFunctions = lazy(() =>
import(
'~/pages/settings/serverless-functions/SettingsServerlessFunctions'
@@ -146,6 +124,18 @@ const SettingsWorkspace = lazy(() =>
})),
);
+const SettingsDomains = lazy(() =>
+ import('~/pages/settings/domains').then((module) => ({
+ default: module.SettingsDomains,
+ })),
+);
+
+const SettingsApiWebhooks = lazy(() =>
+ import('~/pages/settings/workspace/SettingsApiWebhooks').then((module) => ({
+ default: module.SettingsApiWebhooks,
+ })),
+);
+
const SettingsAI = lazy(() =>
import('~/pages/settings/ai/SettingsAI').then((module) => ({
default: module.SettingsAI,
@@ -347,9 +337,9 @@ const SettingsAdminConfigVariableDetails = lazy(() =>
})),
);
-const SettingsLab = lazy(() =>
- import('~/pages/settings/lab/SettingsLab').then((module) => ({
- default: module.SettingsLab,
+const SettingsReleases = lazy(() =>
+ import('~/pages/settings/releases/SettingsReleases').then((module) => ({
+ default: module.SettingsReleases,
})),
);
@@ -428,6 +418,11 @@ export const SettingsRoutes = ({
}
>
} />
+ } />
+ }
+ />
} />
}
>
- } />
- } />
}
@@ -578,7 +571,7 @@ export const SettingsRoutes = ({
/>
>
)}
- } />
+
}
>
- } />
+ } />
diff --git a/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookForm.tsx b/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookForm.tsx
index b026e2674a..b1ef94c7d9 100644
--- a/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookForm.tsx
+++ b/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookForm.tsx
@@ -131,8 +131,8 @@ export const SettingsDevelopersWebhookForm = ({
href: getSettingsPath(SettingsPath.Workspace),
},
{
- children: t`Webhooks`,
- href: getSettingsPath(SettingsPath.Webhooks),
+ children: t`APIs & Webhooks`,
+ href: getSettingsPath(SettingsPath.ApiWebhooks),
},
{ children: isCreationMode ? t`New` : getTitle() },
]}
@@ -140,7 +140,7 @@ export const SettingsDevelopersWebhookForm = ({
navigate(SettingsPath.Webhooks)}
+ onCancel={() => navigate(SettingsPath.ApiWebhooks)}
onSave={formConfig.handleSubmit(handleSave)}
/>
}
diff --git a/packages/twenty-front/src/modules/settings/developers/hooks/useWebhookForm.ts b/packages/twenty-front/src/modules/settings/developers/hooks/useWebhookForm.ts
index d56a9cfbee..46d1242d6d 100644
--- a/packages/twenty-front/src/modules/settings/developers/hooks/useWebhookForm.ts
+++ b/packages/twenty-front/src/modules/settings/developers/hooks/useWebhookForm.ts
@@ -100,7 +100,7 @@ export const useWebhookForm = ({ webhookId, mode }: UseWebhookFormProps) => {
});
navigate(
- createdWebhook ? SettingsPath.WebhookDetail : SettingsPath.Webhooks,
+ createdWebhook ? SettingsPath.WebhookDetail : SettingsPath.ApiWebhooks,
createdWebhook ? { webhookId: createdWebhook.id } : undefined,
);
} catch (error) {
@@ -188,7 +188,7 @@ export const useWebhookForm = ({ webhookId, mode }: UseWebhookFormProps) => {
message: t`Webhook deleted successfully`,
});
- navigate(SettingsPath.Webhooks);
+ navigate(SettingsPath.ApiWebhooks);
} catch (error) {
enqueueErrorSnackBar({
apolloError: error instanceof ApolloError ? error : undefined,
diff --git a/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx b/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx
index eef4c74969..90a6fa5c9f 100644
--- a/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx
+++ b/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx
@@ -3,7 +3,6 @@ import { SettingsPath } from '@/types/SettingsPath';
import { useAuth } from '@/auth/hooks/useAuth';
import { currentUserState } from '@/auth/states/currentUserState';
import { billingState } from '@/client-config/states/billingState';
-import { labPublicFeatureFlagsState } from '@/client-config/states/labPublicFeatureFlagsState';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { type NavigationDrawerItemIndentationLevel } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
@@ -18,7 +17,6 @@ import {
type IconComponent,
IconCurrencyDollar,
IconDoorEnter,
- IconFlask,
IconFunction,
IconHierarchy2,
IconKey,
@@ -31,7 +29,7 @@ import {
IconSparkles,
IconUserCircle,
IconUsers,
- IconWebhook,
+ IconWorld,
} from 'twenty-ui/display';
import { FeatureFlagKey, PermissionFlagType } from '~/generated/graphql';
@@ -65,7 +63,6 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
const isAdminEnabled =
(currentUser?.canImpersonate || currentUser?.canAccessFullAdminPanel) ??
false;
- const labPublicFeatureFlags = useRecoilValue(labPublicFeatureFlagsState);
const isAIEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED);
const isPageLayoutEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_PAGE_LAYOUT_ENABLED,
@@ -116,6 +113,12 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
Icon: IconSettings,
isHidden: !permissionMap[PermissionFlagType.WORKSPACE],
},
+ {
+ label: t`Data model`,
+ path: SettingsPath.Objects,
+ Icon: IconHierarchy2,
+ isHidden: !permissionMap[PermissionFlagType.DATA_MODEL],
+ },
{
label: t`Members`,
path: SettingsPath.WorkspaceMembersPage,
@@ -128,6 +131,12 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
Icon: IconLock,
isHidden: !permissionMap[PermissionFlagType.ROLES],
},
+ {
+ label: t`Domains`,
+ path: SettingsPath.Domains,
+ Icon: IconWorld,
+ isHidden: !permissionMap[PermissionFlagType.WORKSPACE],
+ },
{
label: t`Billing`,
path: SettingsPath.Billing,
@@ -136,13 +145,7 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
!isBillingEnabled || !permissionMap[PermissionFlagType.WORKSPACE],
},
{
- label: t`Data model`,
- path: SettingsPath.Objects,
- Icon: IconHierarchy2,
- isHidden: !permissionMap[PermissionFlagType.DATA_MODEL],
- },
- {
- label: t`Page Layout`,
+ label: t`Layout`,
path: SettingsPath.PageLayoutEdition,
Icon: IconLayout,
isHidden:
@@ -150,6 +153,12 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
!permissionMap[PermissionFlagType.WORKSPACE],
isNew: true,
},
+ {
+ label: t`APIs & Webhooks`,
+ path: SettingsPath.ApiWebhooks,
+ Icon: IconApi,
+ isHidden: !permissionMap[PermissionFlagType.API_KEYS_AND_WEBHOOKS],
+ },
{
label: t`Integrations`,
path: SettingsPath.Integrations,
@@ -164,33 +173,6 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
!isAIEnabled || !permissionMap[PermissionFlagType.WORKSPACE],
isNew: true,
},
- {
- label: t`Security`,
- path: SettingsPath.Security,
- Icon: IconKey,
- isAdvanced: true,
- isHidden: !permissionMap[PermissionFlagType.SECURITY],
- },
- ],
- },
- {
- label: t`Developers`,
- isAdvanced: false,
- items: [
- {
- label: t`APIs`,
- path: SettingsPath.APIs,
- Icon: IconApi,
- isAdvanced: false,
- isHidden: !permissionMap[PermissionFlagType.API_KEYS_AND_WEBHOOKS],
- },
- {
- label: t`Webhooks`,
- path: SettingsPath.Webhooks,
- Icon: IconWebhook,
- isAdvanced: false,
- isHidden: !permissionMap[PermissionFlagType.API_KEYS_AND_WEBHOOKS],
- },
{
label: t`Functions`,
path: SettingsPath.ServerlessFunctions,
@@ -198,6 +180,13 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
isHidden: !isFunctionSettingsEnabled,
isAdvanced: true,
},
+ {
+ label: t`Security`,
+ path: SettingsPath.Security,
+ Icon: IconKey,
+ isAdvanced: true,
+ isHidden: !permissionMap[PermissionFlagType.SECURITY],
+ },
],
},
{
@@ -209,18 +198,11 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
Icon: IconServer,
isHidden: !isAdminEnabled,
},
- {
- label: t`Lab`,
- path: SettingsPath.Lab,
- Icon: IconFlask,
- isHidden:
- !labPublicFeatureFlags.length ||
- !permissionMap[PermissionFlagType.WORKSPACE],
- },
{
label: t`Releases`,
path: SettingsPath.Releases,
Icon: IconRocket,
+ isHidden: !permissionMap[PermissionFlagType.WORKSPACE],
},
{
label: t`Logout`,
diff --git a/packages/twenty-front/src/modules/types/SettingsPath.ts b/packages/twenty-front/src/modules/types/SettingsPath.ts
index 07d348716d..9b3d974a94 100644
--- a/packages/twenty-front/src/modules/types/SettingsPath.ts
+++ b/packages/twenty-front/src/modules/types/SettingsPath.ts
@@ -22,14 +22,18 @@ export enum SettingsPath {
WorkspaceMembersPage = 'members',
Workspace = 'general',
Domain = 'general/domain',
+ Domains = 'domains',
+ Releases = 'releases',
AI = 'ai',
AINewAgent = 'ai/new-agent',
AIAgentDetail = 'ai/agents/:agentId',
- APIs = 'apis',
+ ApiWebhooks = 'api-webhooks',
RestPlayground = 'playground/rest/:schema',
GraphQLPlayground = 'playground/graphql/:schema',
- NewApiKey = 'apis/new',
- ApiKeyDetail = 'apis/:apiKeyId',
+ NewApiKey = 'api-webhooks/apis/new',
+ ApiKeyDetail = 'api-webhooks/apis/:apiKeyId',
+ NewWebhook = 'api-webhooks/webhooks/new',
+ WebhookDetail = 'api-webhooks/webhooks/:webhookId',
Integrations = 'integrations',
IntegrationMCP = 'integrations/mcp',
IntegrationDatabase = 'integrations/:databaseKey',
@@ -39,15 +43,12 @@ export enum SettingsPath {
Security = 'security',
NewSSOIdentityProvider = 'security/sso/new',
NewApprovedAccessDomain = 'security/approved-access-domain/new',
- Webhooks = 'webhooks',
- NewWebhook = 'webhooks/new',
- WebhookDetail = 'webhooks/:webhookId',
- Releases = 'releases',
+
AdminPanel = 'admin-panel',
AdminPanelHealthStatus = 'admin-panel#health-status',
AdminPanelIndicatorHealthStatus = 'admin-panel/health-status/:indicatorId',
AdminPanelConfigVariableDetails = 'admin-panel/config-variables/:variableName',
- Lab = 'lab',
+
Roles = 'roles',
RoleCreate = 'roles/create',
RoleDetail = 'roles/:roleId',
diff --git a/packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx b/packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx
index c1a744b360..8f576dc179 100644
--- a/packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx
+++ b/packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx
@@ -1,24 +1,17 @@
import { useLingui } from '@lingui/react/macro';
-import { useRecoilValue } from 'recoil';
-import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
-import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
-import { SettingsCard } from '@/settings/components/SettingsCard';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { DeleteWorkspace } from '@/settings/profile/components/DeleteWorkspace';
import { NameField } from '@/settings/workspace/components/NameField';
import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
-import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
-import { H2Title, IconWorld, Status } from 'twenty-ui/display';
+import { H2Title } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
-import { UndecoratedLink } from 'twenty-ui/navigation';
+import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
export const SettingsWorkspace = () => {
- const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
const { t } = useLingui();
- const currentWorkspace = useRecoilValue(currentWorkspaceState);
return (
{
- {isMultiWorkspaceEnabled && (
- <>
-
-
-
- }
- Status={
- currentWorkspace?.customDomain &&
- currentWorkspace?.isCustomDomainEnabled ? (
-
- ) : currentWorkspace?.customDomain ? (
-
- ) : undefined
- }
- />
-
-
- >
- )}
+
diff --git a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
index 026a9af4ba..abe439ee24 100644
--- a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
+++ b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
@@ -143,7 +143,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
},
});
if (redirect) {
- navigate(SettingsPath.APIs);
+ navigate(SettingsPath.ApiWebhooks);
}
} catch {
enqueueErrorSnackBar({ message: t`Error deleting api key.` });
@@ -242,8 +242,8 @@ export const SettingsDevelopersApiKeyDetail = () => {
href: getSettingsPath(SettingsPath.Workspace),
},
{
- children: t`APIs`,
- href: getSettingsPath(SettingsPath.APIs),
+ children: t`APIs & Webhooks`,
+ href: getSettingsPath(SettingsPath.ApiWebhooks),
},
{ children: apiKey?.name },
]}
diff --git a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx
index f2694db776..88d26654fa 100644
--- a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx
+++ b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx
@@ -139,8 +139,8 @@ export const SettingsDevelopersApiKeysNew = () => {
href: getSettingsPath(SettingsPath.Workspace),
},
{
- children: t`APIs`,
- href: getSettingsPath(SettingsPath.APIs),
+ children: t`APIs & Webhooks`,
+ href: getSettingsPath(SettingsPath.ApiWebhooks),
},
{ children: t`New Key` },
]}
@@ -148,7 +148,7 @@ export const SettingsDevelopersApiKeysNew = () => {
{
- navigateSettings(SettingsPath.APIs);
+ navigateSettings(SettingsPath.ApiWebhooks);
}}
onSave={handleSave}
/>
diff --git a/packages/twenty-front/src/pages/settings/developers/playground/SettingsGraphQLPlayground.tsx b/packages/twenty-front/src/pages/settings/developers/playground/SettingsGraphQLPlayground.tsx
index 11c475930b..410f632426 100644
--- a/packages/twenty-front/src/pages/settings/developers/playground/SettingsGraphQLPlayground.tsx
+++ b/packages/twenty-front/src/pages/settings/developers/playground/SettingsGraphQLPlayground.tsx
@@ -19,7 +19,7 @@ export const SettingsGraphQLPlayground = () => {
: PlaygroundSchemas.CORE;
const handleExitFullScreen = () => {
- navigateSettings(SettingsPath.APIs);
+ navigateSettings(SettingsPath.ApiWebhooks);
};
const handleOnError = () => {
@@ -35,8 +35,8 @@ export const SettingsGraphQLPlayground = () => {
href: getSettingsPath(SettingsPath.Workspace),
},
{
- children: APIs,
- href: getSettingsPath(SettingsPath.APIs),
+ children: APIs & Webhooks,
+ href: getSettingsPath(SettingsPath.ApiWebhooks),
},
{ children: GraphQL API Playground },
]}
diff --git a/packages/twenty-front/src/pages/settings/developers/playground/SettingsRestPlayground.tsx b/packages/twenty-front/src/pages/settings/developers/playground/SettingsRestPlayground.tsx
index 54654b28b9..55a2c70eac 100644
--- a/packages/twenty-front/src/pages/settings/developers/playground/SettingsRestPlayground.tsx
+++ b/packages/twenty-front/src/pages/settings/developers/playground/SettingsRestPlayground.tsx
@@ -14,7 +14,7 @@ export const SettingsRestPlayground = () => {
}>();
const handleExitFullScreen = () => {
- navigateSettings(SettingsPath.APIs);
+ navigateSettings(SettingsPath.ApiWebhooks);
};
return (
@@ -26,15 +26,15 @@ export const SettingsRestPlayground = () => {
href: getSettingsPath(SettingsPath.Workspace),
},
{
- children: APIs,
- href: getSettingsPath(SettingsPath.APIs),
+ children: APIs & Webhooks,
+ href: getSettingsPath(SettingsPath.ApiWebhooks),
},
{ children: REST },
]}
>
navigateSettings(SettingsPath.APIs)}
+ onError={() => navigateSettings(SettingsPath.ApiWebhooks)}
/>
);
diff --git a/packages/twenty-front/src/pages/settings/domains/SettingsDomains.tsx b/packages/twenty-front/src/pages/settings/domains/SettingsDomains.tsx
new file mode 100644
index 0000000000..96a4fdbd37
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/domains/SettingsDomains.tsx
@@ -0,0 +1,58 @@
+import styled from '@emotion/styled';
+import { Trans, useLingui } from '@lingui/react/macro';
+
+import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
+import { SettingsApprovedAccessDomainsListCard } from '@/settings/security/components/approvedAccessDomains/SettingsApprovedAccessDomainsListCard';
+import { SettingsPath } from '@/types/SettingsPath';
+import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
+import { H2Title } from 'twenty-ui/display';
+import { Section } from 'twenty-ui/layout';
+import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
+import { SettingsWorkspaceDomainCard } from './components';
+
+const StyledMainContent = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: ${({ theme }) => theme.spacing(10)};
+ min-height: 200px;
+`;
+
+const StyledSection = styled(Section)`
+ flex-shrink: 0;
+`;
+
+export const SettingsDomains = () => {
+ const { t } = useLingui();
+
+ return (
+ Workspace,
+ href: getSettingsPath(SettingsPath.Workspace),
+ },
+ { children: Domains },
+ ]}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/pages/settings/domains/components/SettingsWorkspaceDomainCard.tsx b/packages/twenty-front/src/pages/settings/domains/components/SettingsWorkspaceDomainCard.tsx
new file mode 100644
index 0000000000..43f1122c71
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/domains/components/SettingsWorkspaceDomainCard.tsx
@@ -0,0 +1,36 @@
+import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
+import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
+import { SettingsCard } from '@/settings/components/SettingsCard';
+import { SettingsPath } from '@/types/SettingsPath';
+import { useLingui } from '@lingui/react/macro';
+import { useRecoilValue } from 'recoil';
+import { IconWorld, Status } from 'twenty-ui/display';
+import { UndecoratedLink } from 'twenty-ui/navigation';
+import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
+
+export const SettingsWorkspaceDomainCard = () => {
+ const { t } = useLingui();
+ const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
+ const currentWorkspace = useRecoilValue(currentWorkspaceState);
+
+ if (!isMultiWorkspaceEnabled) {
+ return null;
+ }
+
+ return (
+
+ }
+ Status={
+ currentWorkspace?.customDomain &&
+ currentWorkspace?.isCustomDomainEnabled ? (
+
+ ) : currentWorkspace?.customDomain ? (
+
+ ) : undefined
+ }
+ />
+
+ );
+};
diff --git a/packages/twenty-front/src/pages/settings/domains/components/index.ts b/packages/twenty-front/src/pages/settings/domains/components/index.ts
new file mode 100644
index 0000000000..1d79856ba4
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/domains/components/index.ts
@@ -0,0 +1 @@
+export { SettingsWorkspaceDomainCard } from './SettingsWorkspaceDomainCard';
diff --git a/packages/twenty-front/src/pages/settings/domains/index.ts b/packages/twenty-front/src/pages/settings/domains/index.ts
new file mode 100644
index 0000000000..b2acb9460c
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/domains/index.ts
@@ -0,0 +1 @@
+export { SettingsDomains } from './SettingsDomains';
diff --git a/packages/twenty-front/src/pages/settings/lab/SettingsLab.tsx b/packages/twenty-front/src/pages/settings/lab/SettingsLab.tsx
deleted file mode 100644
index e98810565f..0000000000
--- a/packages/twenty-front/src/pages/settings/lab/SettingsLab.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
-import { SettingsLabContent } from '@/settings/lab/components/SettingsLabContent';
-import { SettingsPath } from '@/types/SettingsPath';
-import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
-import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
-import { t } from '@lingui/core/macro';
-
-export const SettingsLab = () => {
- return (
-
-
-
-
-
- );
-};
diff --git a/packages/twenty-front/src/pages/settings/releases/SettingsReleases.tsx b/packages/twenty-front/src/pages/settings/releases/SettingsReleases.tsx
new file mode 100644
index 0000000000..6ec2284630
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/releases/SettingsReleases.tsx
@@ -0,0 +1,46 @@
+import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
+import { SettingsPath } from '@/types/SettingsPath';
+import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
+import { TabList } from '@/ui/layout/tab-list/components/TabList';
+import { t } from '@lingui/core/macro';
+import { IconFlask, IconRocket } from 'twenty-ui/display';
+import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
+import { SettingsReleasesTabContent } from './components';
+import { SETTINGS_RELEASES_TABS, SETTINGS_RELEASES_TABS_ID } from './constants';
+
+export const SettingsReleases = () => {
+ const tabs = [
+ {
+ id: SETTINGS_RELEASES_TABS.CHANGELOG,
+ title: t`Changelog`,
+ Icon: IconRocket,
+ },
+ {
+ id: SETTINGS_RELEASES_TABS.LAB,
+ title: t`Lab`,
+ Icon: IconFlask,
+ },
+ ];
+
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/pages/settings/Releases.tsx b/packages/twenty-front/src/pages/settings/releases/components/SettingsReleasesChangelogContent.tsx
similarity index 67%
rename from packages/twenty-front/src/pages/settings/Releases.tsx
rename to packages/twenty-front/src/pages/settings/releases/components/SettingsReleasesChangelogContent.tsx
index 5f7be7c3f3..61191bb2ab 100644
--- a/packages/twenty-front/src/pages/settings/Releases.tsx
+++ b/packages/twenty-front/src/pages/settings/releases/components/SettingsReleasesChangelogContent.tsx
@@ -1,5 +1,4 @@
import styled from '@emotion/styled';
-import { Trans, useLingui } from '@lingui/react/macro';
import React, { useEffect, useState } from 'react';
import rehypeStringify from 'rehype-stringify';
import remarkParse from 'remark-parse';
@@ -7,11 +6,7 @@ import remarkRehype from 'remark-rehype';
import { type PluggableList, unified } from 'unified';
import { visit } from 'unist-util-visit';
-import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
-import { SettingsPath } from '@/types/SettingsPath';
-import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
-import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
type ReleaseNote = {
slug: string;
@@ -79,8 +74,7 @@ const StyledReleaseDate = styled.span`
color: ${({ theme }) => theme.font.color.tertiary};
`;
-export const Releases = () => {
- const { t } = useLingui();
+export const SettingsReleasesChangelogContent = () => {
const [releases, setReleases] = useState([]);
useEffect(() => {
@@ -105,29 +99,16 @@ export const Releases = () => {
}, []);
return (
- Other,
- href: getSettingsPath(SettingsPath.Workspace),
- },
- { children: Releases },
- ]}
- >
-
-
-
- {releases.map((release) => (
-
- {release.release}
- {release.date}
-
-
- ))}
-
-
-
-
+
+
+ {releases.map((release) => (
+
+ {release.release}
+ {release.date}
+
+
+ ))}
+
+
);
};
diff --git a/packages/twenty-front/src/pages/settings/releases/components/SettingsReleasesTabContent.tsx b/packages/twenty-front/src/pages/settings/releases/components/SettingsReleasesTabContent.tsx
new file mode 100644
index 0000000000..7ce6e1fc8c
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/releases/components/SettingsReleasesTabContent.tsx
@@ -0,0 +1,24 @@
+import { SettingsLabContent } from '@/settings/lab/components/SettingsLabContent';
+import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
+import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
+import {
+ SETTINGS_RELEASES_TABS,
+ SETTINGS_RELEASES_TABS_ID,
+} from '../constants';
+import { SettingsReleasesChangelogContent } from './SettingsReleasesChangelogContent';
+
+export const SettingsReleasesTabContent = () => {
+ const activeTabId = useRecoilComponentValue(
+ activeTabIdComponentState,
+ SETTINGS_RELEASES_TABS_ID,
+ );
+
+ switch (activeTabId) {
+ case SETTINGS_RELEASES_TABS.CHANGELOG:
+ return ;
+ case SETTINGS_RELEASES_TABS.LAB:
+ return ;
+ default:
+ return ;
+ }
+};
diff --git a/packages/twenty-front/src/pages/settings/releases/components/index.ts b/packages/twenty-front/src/pages/settings/releases/components/index.ts
new file mode 100644
index 0000000000..c5644b31ec
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/releases/components/index.ts
@@ -0,0 +1,2 @@
+export { SettingsReleasesChangelogContent } from './SettingsReleasesChangelogContent';
+export { SettingsReleasesTabContent } from './SettingsReleasesTabContent';
diff --git a/packages/twenty-front/src/pages/settings/releases/constants/SettingsReleasesTabs.ts b/packages/twenty-front/src/pages/settings/releases/constants/SettingsReleasesTabs.ts
new file mode 100644
index 0000000000..10a877fa62
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/releases/constants/SettingsReleasesTabs.ts
@@ -0,0 +1,4 @@
+export const SETTINGS_RELEASES_TABS = {
+ CHANGELOG: 'changelog',
+ LAB: 'lab',
+};
diff --git a/packages/twenty-front/src/pages/settings/releases/constants/SettingsReleasesTabsId.ts b/packages/twenty-front/src/pages/settings/releases/constants/SettingsReleasesTabsId.ts
new file mode 100644
index 0000000000..2cd50b2c81
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/releases/constants/SettingsReleasesTabsId.ts
@@ -0,0 +1 @@
+export const SETTINGS_RELEASES_TABS_ID = 'settings-releases-tabs';
diff --git a/packages/twenty-front/src/pages/settings/releases/constants/index.ts b/packages/twenty-front/src/pages/settings/releases/constants/index.ts
new file mode 100644
index 0000000000..e03e3fecec
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/releases/constants/index.ts
@@ -0,0 +1,2 @@
+export { SETTINGS_RELEASES_TABS } from './SettingsReleasesTabs';
+export { SETTINGS_RELEASES_TABS_ID } from './SettingsReleasesTabsId';
diff --git a/packages/twenty-front/src/pages/settings/releases/index.ts b/packages/twenty-front/src/pages/settings/releases/index.ts
new file mode 100644
index 0000000000..75f0132c83
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/releases/index.ts
@@ -0,0 +1 @@
+export { SettingsReleases } from './SettingsReleases';
diff --git a/packages/twenty-front/src/pages/settings/security/SettingsSecurity.tsx b/packages/twenty-front/src/pages/settings/security/SettingsSecurity.tsx
index c193d015ae..5bd5b19f1d 100644
--- a/packages/twenty-front/src/pages/settings/security/SettingsSecurity.tsx
+++ b/packages/twenty-front/src/pages/settings/security/SettingsSecurity.tsx
@@ -5,7 +5,7 @@ import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWork
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsSSOIdentitiesProvidersListCard } from '@/settings/security/components/SSO/SettingsSSOIdentitiesProvidersListCard';
import { SettingsSecurityAuthProvidersOptionsList } from '@/settings/security/components/SettingsSecurityAuthProvidersOptionsList';
-import { SettingsApprovedAccessDomainsListCard } from '@/settings/security/components/approvedAccessDomains/SettingsApprovedAccessDomainsListCard';
+
import { ToggleImpersonate } from '@/settings/workspace/components/ToggleImpersonate';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
@@ -63,13 +63,7 @@ export const SettingsSecurity = () => {
/>
-
-
-
-
+
{
enqueueSuccessSnackBar({
message: t`Please check your email for a verification link.`,
});
- navigate(SettingsPath.Security);
+ navigate(SettingsPath.Domains);
},
onError: (error) => {
enqueueErrorSnackBar({
@@ -86,7 +86,7 @@ export const SettingsSecurityApprovedAccessDomain = () => {
title="New Approved Access Domain"
actionButton={
navigate(SettingsPath.Security)}
+ onCancel={() => navigate(SettingsPath.Domains)}
isSaveDisabled={form.formState.isSubmitting}
/>
}
@@ -96,8 +96,8 @@ export const SettingsSecurityApprovedAccessDomain = () => {
href: getSettingsPath(SettingsPath.Workspace),
},
{
- children: Security,
- href: getSettingsPath(SettingsPath.Security),
+ children: Domains,
+ href: getSettingsPath(SettingsPath.Domains),
},
{ children: New Approved Access Domain },
]}
diff --git a/packages/twenty-front/src/pages/settings/workspace/SettingsApiWebhooks.tsx b/packages/twenty-front/src/pages/settings/workspace/SettingsApiWebhooks.tsx
new file mode 100644
index 0000000000..376c9fee2d
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/workspace/SettingsApiWebhooks.tsx
@@ -0,0 +1,113 @@
+import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
+import { SettingsApiKeysTable } from '@/settings/developers/components/SettingsApiKeysTable';
+import { SettingsWebhooksTable } from '@/settings/developers/components/SettingsWebhooksTable';
+import { PlaygroundSetupForm } from '@/settings/playground/components/PlaygroundSetupForm';
+import { StyledSettingsApiPlaygroundCoverImage } from '@/settings/playground/components/SettingsPlaygroundCoverImage';
+import { SettingsPath } from '@/types/SettingsPath';
+import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
+import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
+import styled from '@emotion/styled';
+import { Trans, useLingui } from '@lingui/react/macro';
+import { H2Title, IconPlus } from 'twenty-ui/display';
+import { Button } from 'twenty-ui/input';
+import { Section } from 'twenty-ui/layout';
+import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
+import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
+
+const StyledButtonContainer = styled.div`
+ display: flex;
+ justify-content: flex-end;
+ padding-top: ${({ theme }) => theme.spacing(2)};
+ @media (max-width: ${MOBILE_VIEWPORT}px) {
+ padding-top: ${({ theme }) => theme.spacing(5)};
+ }
+`;
+
+const StyledMainContent = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: ${({ theme }) => theme.spacing(10)};
+ min-height: 200px;
+`;
+
+const StyledSection = styled(Section)`
+ flex-shrink: 0;
+`;
+
+const StyledContainer = styled.div<{ isMobile?: boolean }>`
+ display: flex;
+ flex-direction: column;
+ overflow: ${({ isMobile }) => (isMobile ? 'hidden' : 'visible')};
+ gap: ${({ theme }) => theme.spacing(2)};
+`;
+
+export const SettingsApiWebhooks = () => {
+ const isMobile = useIsMobile();
+ const { t } = useLingui();
+
+ return (
+ Workspace,
+ href: getSettingsPath(SettingsPath.Workspace),
+ },
+ { children: APIs & Webhooks },
+ ]}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/pages/settings/workspace/SettingsDomain.tsx b/packages/twenty-front/src/pages/settings/workspace/SettingsDomain.tsx
index 1b288cad64..15f92f1fd8 100644
--- a/packages/twenty-front/src/pages/settings/workspace/SettingsDomain.tsx
+++ b/packages/twenty-front/src/pages/settings/workspace/SettingsDomain.tsx
@@ -215,14 +215,14 @@ export const SettingsDomain = () => {
href: getSettingsPath(SettingsPath.Workspace),
},
{
- children: General,
- href: getSettingsPath(SettingsPath.Workspace),
+ children: Domains,
+ href: getSettingsPath(SettingsPath.Domains),
},
{ children: Domain },
]}
actionButton={
navigate(SettingsPath.Workspace)}
+ onCancel={() => navigate(SettingsPath.Domains)}
isSaveDisabled={form.formState.isSubmitting}
/>
}
diff --git a/packages/twenty-front/src/utils/title-utils.ts b/packages/twenty-front/src/utils/title-utils.ts
index 6139d1450d..d7922029cd 100644
--- a/packages/twenty-front/src/utils/title-utils.ts
+++ b/packages/twenty-front/src/utils/title-utils.ts
@@ -23,8 +23,7 @@ enum SettingsPathPrefixes {
Profile = `${AppBasePath.Settings}/${SettingsPath.ProfilePage}`,
Objects = `${AppBasePath.Settings}/${SettingsPath.Objects}`,
Members = `${AppBasePath.Settings}/${SettingsPath.WorkspaceMembersPage}`,
- ApiKeys = `${AppBasePath.Settings}/${SettingsPath.APIs}`,
- Webhooks = `${AppBasePath.Settings}/${SettingsPath.Webhooks}`,
+ ApiWebhooks = `${AppBasePath.Settings}/${SettingsPath.ApiWebhooks}`,
ServerlessFunctions = `${AppBasePath.Settings}/${SettingsPath.ServerlessFunctions}`,
Integration = `${AppBasePath.Settings}/${SettingsPath.Integrations}`,
General = `${AppBasePath.Settings}/${SettingsPath.Workspace}`,
@@ -62,10 +61,8 @@ export const getPageTitleFromPath = (pathname: string): string => {
return SettingsPageTitles.Members;
case SettingsPathPrefixes.Objects:
return SettingsPageTitles.Objects;
- case SettingsPathPrefixes.ApiKeys:
+ case SettingsPathPrefixes.ApiWebhooks:
return SettingsPageTitles.Apis;
- case SettingsPathPrefixes.Webhooks:
- return SettingsPageTitles.Webhooks;
case SettingsPathPrefixes.ServerlessFunctions:
return SettingsPageTitles.ServerlessFunctions;
case SettingsPathPrefixes.Integration:
diff --git a/packages/twenty-website/src/content/developers/api-and-webhooks/api.mdx b/packages/twenty-website/src/content/developers/api-and-webhooks/api.mdx
index 5da6b5aab2..c32fc594db 100644
--- a/packages/twenty-website/src/content/developers/api-and-webhooks/api.mdx
+++ b/packages/twenty-website/src/content/developers/api-and-webhooks/api.mdx
@@ -8,7 +8,7 @@ info: Discover how to use our APIs.
## Overview
The Twenty API allows developers to interact programmatically with the Twenty CRM platform. Using the API, you can integrate Twenty with other systems, automate data synchronization, and build custom solutions around your customer data. The API provides endpoints to **create, read, update, and delete** core CRM objects (such as people and companies) as well as access metadata configuration.
-**API Playground:** You can now access the API Playground within the app's settings. To try out API calls in real-time, log in to your Twenty workspace and navigate to **Settings > API & Webhooks** (under **Developers**). This will open the in-app API Playground and settings for API keys. _(If you don’t see the API & Webhooks section in Settings, enable “Advanced mode” in your app settings to reveal developer options.)_
+**API Playground:** You can now access the API Playground within the app's settings. To try out API calls in real-time, log in to your Twenty workspace and navigate to **Settings → APIs & Webhooks**. This opens the in-app API Playground and the settings for API keys.
**[Go to API Settings](https://app.twenty.com/settings)**
## Authentication
diff --git a/packages/twenty-website/src/content/developers/api-and-webhooks/webhooks.mdx b/packages/twenty-website/src/content/developers/api-and-webhooks/webhooks.mdx
index 76895c8074..8e2edb6e97 100644
--- a/packages/twenty-website/src/content/developers/api-and-webhooks/webhooks.mdx
+++ b/packages/twenty-website/src/content/developers/api-and-webhooks/webhooks.mdx
@@ -11,9 +11,9 @@ Webhooks in Twenty complement the API by enabling **real-time notifications** to
With webhooks, Twenty will send an HTTP POST request to a URL you specify, containing details about the event. You can then handle that data in your application (e.g., to update your external database, trigger workflows, or send alerts).
## Setting Up a Webhook
-To create a webhook in Twenty, use the **API & Webhooks** settings in your Twenty app (Developer settings):
+To create a webhook in Twenty, use the **APIs & Webhooks** settings in your Twenty app:
-1. **Navigate to Settings:** In your Twenty application, go to **Settings** (ensure Advanced mode is enabled to see developer options).
+1. **Navigate to Settings:** In your Twenty application, go to **Settings → APIs & Webhooks**.
2. **Create a Webhook:** Under **Webhooks** click on **+ Create webhook**.
3. **Enter URL:** Provide the endpoint URL on your server where you want Twenty to send webhook requests. This should be a publicly accessible URL that can handle POST requests.
4. **Save:** Click **Save** to create the webhook. The new webhook will be active immediately.
diff --git a/packages/twenty-website/src/content/developers/backend-development/zapier.mdx b/packages/twenty-website/src/content/developers/backend-development/zapier.mdx
index 1fdd32ea21..6be7e47256 100644
--- a/packages/twenty-website/src/content/developers/backend-development/zapier.mdx
+++ b/packages/twenty-website/src/content/developers/backend-development/zapier.mdx
@@ -37,7 +37,7 @@ From the `packages/twenty-zapier` folder, run:
```bash
cp .env.example .env
```
-Run the application locally, go to [http://localhost:3000/settings/developers](http://localhost:3000/settings/developers/api-keys), and generate an API key.
+Run the application locally, go to [http://localhost:3000/settings/api-webhooks](http://localhost:3000/settings/api-webhooks), and generate an API key.
Replace the **YOUR_API_KEY** value in the `.env` file with the API key you just generated.
diff --git a/packages/twenty-website/src/content/user-guide/getting-started/configure-workspace-in-three-steps.mdx b/packages/twenty-website/src/content/user-guide/getting-started/configure-workspace-in-three-steps.mdx
index 022349f7a6..fed3be62f8 100644
--- a/packages/twenty-website/src/content/user-guide/getting-started/configure-workspace-in-three-steps.mdx
+++ b/packages/twenty-website/src/content/user-guide/getting-started/configure-workspace-in-three-steps.mdx
@@ -25,7 +25,7 @@ You control which contacts are imported in Twenty and what gets shared with the
You can configure the visibility and the contact creation preferences under Settings → Email / Calendar.
### Using another provider?
-This is currently under beta: you can activate this feature under Settings → Lab.
+This is currently under beta: you can activate this feature under Settings → Releases → Lab tab.
You can add email accounts from any provider that supports IMAP and send emails with SMTP. Synchronizing calendars with CalDAV is coming soon!
diff --git a/packages/twenty-website/src/content/user-guide/getting-started/getting-around-twenty.mdx b/packages/twenty-website/src/content/user-guide/getting-started/getting-around-twenty.mdx
index ebe4abfd5d..afd0ff1473 100644
--- a/packages/twenty-website/src/content/user-guide/getting-started/getting-around-twenty.mdx
+++ b/packages/twenty-website/src/content/user-guide/getting-started/getting-around-twenty.mdx
@@ -54,7 +54,7 @@ Open your Settings on the top left to
- Invite members
- Edit your profile
- Customize your workspace: billing, user permissions
-- Discover the latest releases and the upcoming features (under the Lab)
+- Discover the latest releases and the upcoming features (under Releases → Lab tab)
If you do not see all those sections under Settings, reach out to your workspace administrator - some of them have a restricted access.
diff --git a/packages/twenty-website/src/content/user-guide/integrations/api-webhooks.mdx b/packages/twenty-website/src/content/user-guide/integrations/api-webhooks.mdx
index 3b9e3a7cdf..9bbc2117d2 100644
--- a/packages/twenty-website/src/content/user-guide/integrations/api-webhooks.mdx
+++ b/packages/twenty-website/src/content/user-guide/integrations/api-webhooks.mdx
@@ -24,7 +24,7 @@ This means you will have a dedicated endpoint for each of your custom objects an
## API Documentation
-**Twenty API documentation is accessible under your workspace Settings -> API.**
+**Twenty API documentation is accessible under your workspace Settings → APIs & Webhooks.**
As unique APIs are generated to perfectly reflect your data model, Twenty creates customized API documentation.
This is why you need to input an API key to access it.
You will also find a playground where you can test the APIs and the data returned.
@@ -39,7 +39,7 @@ For example, you can use them to retrieve details of a specific `Person` or `Com
### Generate an API key
1. Go to Settings in the sidebar on the left.
-2. In the bottom left, activate "Advanced mode." A new section for API & Webhooks will appear.
+2. Open **APIs & Webhooks**.
3. To generate a new key, click on `+ Create key` at the top right.
4. Give your API key a name, an expiration date, and a logo.
5. Hit save to see your API key.
@@ -80,7 +80,7 @@ For instance, a webhook can alert your system in real-time when someone creates
### Creating Webhooks
-1. From Settings -> Developers, click `+` Create webhook.
+1. From Settings → APIs & Webhooks → Webhooks, click `+` Create webhook.
2. Enter your URL.
3. Click Save.
@@ -105,7 +105,7 @@ For instance, a webhook can alert your system in real-time when someone creates
### Deleting Webhooks
-1. In Developers, find your webhook.
+1. In Settings → APIs & Webhooks → Webhooks, find your webhook.
2. Click on your webhook and press **Delete** to remove it. A confirmation popup will appear to confirm.
diff --git a/packages/twenty-website/src/content/user-guide/integrations/workflows.mdx b/packages/twenty-website/src/content/user-guide/integrations/workflows.mdx
index bcb27e91fe..4ac3e3f04d 100644
--- a/packages/twenty-website/src/content/user-guide/integrations/workflows.mdx
+++ b/packages/twenty-website/src/content/user-guide/integrations/workflows.mdx
@@ -96,7 +96,7 @@ This action runs custom JavaScript within your workflow. You can use this action
Any variables you create and return here can be reused in later steps of the workflow. You can also test your code directly in this step and view the output to confirm it works as expected.
-Note: You can manage keys and access your API Playground [here](https://app.twenty.com/settings) or navigate to Settings > API & Webhooks (under Developers).
+Note: You can manage keys and access your API Playground [here](https://app.twenty.com/settings) or navigate to Settings → APIs & Webhooks.
### Form