+ {currentPlan?.name ?? 'Unknown'} Plan
+
+ )}
+
{!subscription?.usage_billing_enabled && (
{
You can review a static PDF version of our latest DPA document{' '}
track('dpa_pdf_opened', { source: 'studio' })}
>
here
diff --git a/apps/studio/components/layouts/ProjectSettingsLayout/ProjectSettings.Commands.tsx b/apps/studio/components/layouts/ProjectSettingsLayout/ProjectSettings.Commands.tsx
index 7a638bfbe79ba..5635eb1acc61d 100644
--- a/apps/studio/components/layouts/ProjectSettingsLayout/ProjectSettings.Commands.tsx
+++ b/apps/studio/components/layouts/ProjectSettingsLayout/ProjectSettings.Commands.tsx
@@ -16,12 +16,20 @@ export function useProjectSettingsGotoCommands(options?: CommandOptions) {
ref ||= '_'
const hasOrgSlug = typeof slug === 'string' && slug.length > 0 && slug !== '_'
- const { projectSettingsLogDrains, projectSettingsCustomDomains, authenticationSignInProviders } =
- useIsFeatureEnabled([
- 'project_settings:log_drains',
- 'project_settings:custom_domains',
- 'authentication:sign_in_providers',
- ])
+ const {
+ logsAll,
+ projectSettingsLogDrains,
+ projectSettingsCustomDomains,
+ authenticationSignInProviders,
+ } = useIsFeatureEnabled([
+ 'logs:all',
+ 'project_settings:log_drains',
+ 'project_settings:custom_domains',
+ 'authentication:sign_in_providers',
+ ])
+
+ // Log drains depend on the analytics backend, gated by logs:all (see SettingsMenu.utils).
+ const showLogDrains = logsAll && projectSettingsLogDrains
useRegisterCommands(
COMMAND_MENU_SECTIONS.NAVIGATE,
@@ -166,7 +174,7 @@ export function useProjectSettingsGotoCommands(options?: CommandOptions) {
route: `/project/${ref}/database/settings#banned-ips`,
defaultHidden: true,
},
- ...(projectSettingsLogDrains
+ ...(showLogDrains
? [
{
id: 'nav-project-settings-log-drains',
@@ -177,6 +185,6 @@ export function useProjectSettingsGotoCommands(options?: CommandOptions) {
]
: []),
],
- { ...options, deps: [platformWebhooksEnabled, ref, slug] }
+ { ...options, deps: [platformWebhooksEnabled, showLogDrains, ref, slug] }
)
}
diff --git a/apps/studio/components/layouts/ProjectSettingsLayout/SettingsMenu.selfhosted.test.tsx b/apps/studio/components/layouts/ProjectSettingsLayout/SettingsMenu.selfhosted.test.tsx
index f044991a6b4b8..1b5d40e403775 100644
--- a/apps/studio/components/layouts/ProjectSettingsLayout/SettingsMenu.selfhosted.test.tsx
+++ b/apps/studio/components/layouts/ProjectSettingsLayout/SettingsMenu.selfhosted.test.tsx
@@ -1,7 +1,8 @@
import { renderHook } from '@testing-library/react'
-import { describe, expect, it, vi } from 'vitest'
+import { beforeEach, describe, expect, it, vi } from 'vitest'
import { useGenerateSettingsMenu } from './SettingsMenu.utils'
+import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
const getShortcutId = (item: unknown) => (item as { shortcutId?: string } | undefined)?.shortcutId
@@ -28,9 +29,7 @@ vi.mock('@/hooks/misc/useSelectedProject', () => ({
}))
vi.mock('@/hooks/misc/useIsFeatureEnabled', () => ({
- useIsFeatureEnabled: vi
- .fn()
- .mockReturnValue({ projectSettingsLegacyJwtKeys: false, billingAll: true }),
+ useIsFeatureEnabled: vi.fn(),
}))
vi.mock('@/components/interfaces/App/FeaturePreview/FeaturePreviewContext', () => ({
@@ -38,6 +37,15 @@ vi.mock('@/components/interfaces/App/FeaturePreview/FeaturePreviewContext', () =
}))
describe('useGenerateSettingsMenu (self-hosted)', () => {
+ beforeEach(() => {
+ vi.mocked(useIsFeatureEnabled).mockReturnValue({
+ projectSettingsLegacyJwtKeys: false,
+ billingAll: true,
+ logsAll: true,
+ projectSettingsLogDrains: true,
+ } as any)
+ })
+
it('includes General, API Keys, JWT Keys, and Log Drains in self-hosted mode', () => {
const { result } = renderHook(() => useGenerateSettingsMenu())
const configGroup = result.current.find((group) => group.title === 'Configuration')
@@ -51,6 +59,34 @@ describe('useGenerateSettingsMenu (self-hosted)', () => {
)
})
+ it('hides Log Drains in self-hosted mode when logs:all is disabled', () => {
+ vi.mocked(useIsFeatureEnabled).mockReturnValue({
+ projectSettingsLegacyJwtKeys: false,
+ billingAll: true,
+ logsAll: false,
+ projectSettingsLogDrains: true,
+ } as any)
+
+ const { result } = renderHook(() => useGenerateSettingsMenu())
+ const configGroup = result.current.find((group) => group.title === 'Configuration')
+
+ expect(configGroup?.items.some((item) => item.key === 'log-drains')).toBe(false)
+ })
+
+ it('hides Log Drains in self-hosted mode when project_settings:log_drains is disabled', () => {
+ vi.mocked(useIsFeatureEnabled).mockReturnValue({
+ projectSettingsLegacyJwtKeys: false,
+ billingAll: true,
+ logsAll: true,
+ projectSettingsLogDrains: false,
+ } as any)
+
+ const { result } = renderHook(() => useGenerateSettingsMenu())
+ const configGroup = result.current.find((group) => group.title === 'Configuration')
+
+ expect(configGroup?.items.some((item) => item.key === 'log-drains')).toBe(false)
+ })
+
it('includes Data API and Vault integrations in self-hosted mode', () => {
const { result } = renderHook(() => useGenerateSettingsMenu())
const integrationsGroup = result.current.find((group) => group.title === 'Integrations')
diff --git a/apps/studio/components/layouts/ProjectSettingsLayout/SettingsMenu.utils.test.tsx b/apps/studio/components/layouts/ProjectSettingsLayout/SettingsMenu.utils.test.tsx
index 077fd882a4a0c..1c6a6b17f167a 100644
--- a/apps/studio/components/layouts/ProjectSettingsLayout/SettingsMenu.utils.test.tsx
+++ b/apps/studio/components/layouts/ProjectSettingsLayout/SettingsMenu.utils.test.tsx
@@ -4,6 +4,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { useGenerateSettingsMenu } from './SettingsMenu.utils'
import { useIsPlatformWebhooksEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
+import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
const getShortcutId = (item: unknown) => (item as { shortcutId?: string } | undefined)?.shortcutId
@@ -30,9 +31,7 @@ vi.mock('@/hooks/misc/useSelectedProject', () => ({
}))
vi.mock('@/hooks/misc/useIsFeatureEnabled', () => ({
- useIsFeatureEnabled: vi
- .fn()
- .mockReturnValue({ projectSettingsLegacyJwtKeys: false, billingAll: true }),
+ useIsFeatureEnabled: vi.fn(),
}))
vi.mock('@/components/interfaces/App/FeaturePreview/FeaturePreviewContext', () => ({
@@ -43,6 +42,12 @@ describe('useGenerateSettingsMenu', () => {
beforeEach(() => {
vi.mocked(useFlag).mockReturnValue(false)
vi.mocked(useIsPlatformWebhooksEnabled).mockReturnValue(true)
+ vi.mocked(useIsFeatureEnabled).mockReturnValue({
+ projectSettingsLegacyJwtKeys: false,
+ billingAll: true,
+ logsAll: true,
+ projectSettingsLogDrains: true,
+ } as any)
})
it('includes webhooks when platformWebhooks feature is enabled', () => {
@@ -97,6 +102,41 @@ describe('useGenerateSettingsMenu', () => {
expect(configurationGroup?.items.some((item) => item.name === 'Dashboard')).toBe(false)
})
+ it('includes log drains when logs:all and project_settings:log_drains are enabled', () => {
+ const { result } = renderHook(() => useGenerateSettingsMenu())
+ const configurationGroup = result.current.find((group) => group.title === 'Configuration')
+
+ expect(configurationGroup?.items.some((item) => item.key === 'log-drains')).toBe(true)
+ })
+
+ it('hides log drains when logs:all is disabled', () => {
+ vi.mocked(useIsFeatureEnabled).mockReturnValue({
+ projectSettingsLegacyJwtKeys: false,
+ billingAll: true,
+ logsAll: false,
+ projectSettingsLogDrains: true,
+ } as any)
+
+ const { result } = renderHook(() => useGenerateSettingsMenu())
+ const configurationGroup = result.current.find((group) => group.title === 'Configuration')
+
+ expect(configurationGroup?.items.some((item) => item.key === 'log-drains')).toBe(false)
+ })
+
+ it('hides log drains when project_settings:log_drains is disabled', () => {
+ vi.mocked(useIsFeatureEnabled).mockReturnValue({
+ projectSettingsLegacyJwtKeys: false,
+ billingAll: true,
+ logsAll: true,
+ projectSettingsLogDrains: false,
+ } as any)
+
+ const { result } = renderHook(() => useGenerateSettingsMenu())
+ const configurationGroup = result.current.find((group) => group.title === 'Configuration')
+
+ expect(configurationGroup?.items.some((item) => item.key === 'log-drains')).toBe(false)
+ })
+
it('adds shortcuts to eligible configuration settings items', () => {
vi.mocked(useFlag).mockReturnValue(true)
diff --git a/apps/studio/components/layouts/ProjectSettingsLayout/SettingsMenu.utils.tsx b/apps/studio/components/layouts/ProjectSettingsLayout/SettingsMenu.utils.tsx
index 880b84e66e93b..ef22a78dcd79b 100644
--- a/apps/studio/components/layouts/ProjectSettingsLayout/SettingsMenu.utils.tsx
+++ b/apps/studio/components/layouts/ProjectSettingsLayout/SettingsMenu.utils.tsx
@@ -16,8 +16,21 @@ export const useGenerateSettingsMenu = () => {
const platformWebhooksEnabled = useIsPlatformWebhooksEnabled()
- const { projectSettingsLegacyJwtKeys: legacyJwtKeysEnabled, billingAll: billingEnabled } =
- useIsFeatureEnabled(['project_settings:legacy_jwt_keys', 'billing:all'])
+ const {
+ projectSettingsLegacyJwtKeys: legacyJwtKeysEnabled,
+ billingAll: billingEnabled,
+ logsAll,
+ projectSettingsLogDrains,
+ } = useIsFeatureEnabled([
+ 'project_settings:legacy_jwt_keys',
+ 'billing:all',
+ 'logs:all',
+ 'project_settings:log_drains',
+ ])
+
+ // Log drains rely on the analytics backend (gated by logs:all) and on the dedicated
+ // log_drains flag. Keep this in sync with ProjectSettings.Commands.tsx.
+ const showLogDrains = logsAll && projectSettingsLogDrains
const isProjectActive = project?.status === PROJECT_STATUS.ACTIVE_HEALTHY
@@ -46,13 +59,17 @@ export const useGenerateSettingsMenu = () => {
: `/project/${ref}/settings/jwt/signing-keys`,
items: [],
},
- {
- name: `Log Drains`,
- key: `log-drains`,
- url: `/project/${ref}/settings/log-drains`,
- items: [],
- shortcutId: SHORTCUT_IDS.NAV_PROJECT_SETTINGS_LOG_DRAINS,
- },
+ ...(showLogDrains
+ ? [
+ {
+ name: `Log Drains`,
+ key: `log-drains`,
+ url: `/project/${ref}/settings/log-drains`,
+ items: [],
+ shortcutId: SHORTCUT_IDS.NAV_PROJECT_SETTINGS_LOG_DRAINS,
+ },
+ ]
+ : []),
],
},
{
@@ -146,14 +163,18 @@ export const useGenerateSettingsMenu = () => {
shortcutId: SHORTCUT_IDS.NAV_PROJECT_SETTINGS_JWT_KEYS,
},
- {
- name: `Log Drains`,
- key: `log-drains`,
- url: `/project/${ref}/settings/log-drains`,
- items: [],
- disabled: !isProjectActive,
- shortcutId: SHORTCUT_IDS.NAV_PROJECT_SETTINGS_LOG_DRAINS,
- },
+ ...(showLogDrains
+ ? [
+ {
+ name: `Log Drains`,
+ key: `log-drains`,
+ url: `/project/${ref}/settings/log-drains`,
+ items: [],
+ disabled: !isProjectActive,
+ shortcutId: SHORTCUT_IDS.NAV_PROJECT_SETTINGS_LOG_DRAINS,
+ },
+ ]
+ : []),
{
name: 'Add-ons',
key: 'addons',
diff --git a/apps/studio/pages/project/[ref]/logs/explorer/index.tsx b/apps/studio/pages/project/[ref]/logs/explorer/index.tsx
index 04eae9839a402..99bd536b04e61 100644
--- a/apps/studio/pages/project/[ref]/logs/explorer/index.tsx
+++ b/apps/studio/pages/project/[ref]/logs/explorer/index.tsx
@@ -203,7 +203,11 @@ export const LogsExplorerPage: NextPageWithLayout = () => {
const handleRun = (value?: string | React.MouseEvent) => {
track('log_explorer_query_run_button_clicked', { is_saved_query: !!queryId })
- const query = typeof value === 'string' ? value || editorValue : editorValue
+ // Read the latest value straight from the editor instance rather than from
+ // `editorValue` state, which can lag behind the most recent keystroke. This
+ // keeps the Run button consistent with the Cmd+Enter keybinding.
+ const liveValue = editorRef.current?.getValue()
+ const query = typeof value === 'string' ? value || editorValue : (liveValue ?? editorValue)
const resolvedParams = buildLogQueryParams(datePickerValue, query)
setSelectedLog(null)
diff --git a/apps/www/_events/2026-06-25-supabase-perplexity-small-businesses.mdx b/apps/www/_events/2026-06-25-supabase-perplexity-small-businesses.mdx
new file mode 100644
index 0000000000000..ae0a02682c92a
--- /dev/null
+++ b/apps/www/_events/2026-06-25-supabase-perplexity-small-businesses.mdx
@@ -0,0 +1,58 @@
+---
+title: 'Workflows That Scale: Supabase + Perplexity Computer for small businesses'
+meta_title: 'Workflows That Scale: Supabase + Perplexity Computer for small businesses'
+subtitle: >-
+ See what a lean, high-leverage business looks like when it runs on Supabase
+ and Perplexity Computer together.
+meta_description: >-
+ Join Supabase and Perplexity to see how growing teams use Perplexity Computer
+ workflows on top of their Supabase data: outbound, market research, feedback,
+ and more. Registered attendees get complimentary Perplexity Enterprise Pro and
+ Computer credits.
+type: webinar
+onDemand: false
+date: '2026-06-25T09:00:00.000-07:00'
+timezone: America/Los_Angeles
+duration: 45 mins
+company:
+ name: Perplexity
+ website_url: 'https://www.perplexity.ai/'
+ logo: >-
+ /images/events/webinars/supabase-perplexity-small-businesses/perplexity-white.png
+ logo_light: >-
+ /images/events/webinars/supabase-perplexity-small-businesses/perplexity.png
+categories:
+ - webinar
+main_cta:
+ url: 'https://attendee.gotowebinar.com/register/2836535946334786656'
+ target: _blank
+ label: Register now
+speakers: 'natalie_roberge,david_dalmaso'
+---
+
+Founders are juggling outbound, market research, customer meetings, product feedback, investor updates, and operations. The tools exist, but the manual work between them is what slows growing teams down. And when the work does get done, it disappears into a Slack thread, a doc nobody finds, a tab someone closed.
+
+Small businesses are solving both problems at once: using AI to do the work between the tools, and keeping everything in one place so the whole team can build on it.
+
+Supabase is where a lot of growing businesses already keep their most important data: customers, signups, feedback, revenue. The next step is putting the data to work.
+
+Perplexity Computer makes that possible: the work that used to take a small army of operators now happens in a single workflow, running directly on top of the data you already have in Supabase.
+
+In this session, Natalie Roberge, Customer Solutions Architect from Supabase and David Dalmaso, Member of Technical Staff, from Perplexity will walk through what a growing business actually looks like when it runs on Perplexity Computer and Supabase together.
+
+**Registered attendees will receive complimentary Perplexity Enterprise Pro and Computer credits.**
+
+### What we'll cover
+
+- Using Supabase as the backend for all AI / Computer workflows to help democratize data
+- How anyone on your team can run Perplexity Computer workflows autonomously, with Supabase as the consistent data layer that ties everything together across tools, teams, and systems.
+
+**Live demos**
+
+- Pull your top accounts by growth, enrich them, and draft personalized outreach, with every record saved back into Supabase
+- Turn a week of customer feedback into a roadmap summary and weekly update your team can act on
+- Go from market signals to a campaign brief, stored in Supabase so your marketing team can find and build on it
+
+You'll leave with a clear picture of what it looks like to run a lean, high-leverage business on Supabase and Perplexity Computer and the first workflows to try with your own data.
+
+Plus, bring your questions for the live Q&A. Can't make it? We'll send you the recording.
diff --git a/apps/www/lib/authors.json b/apps/www/lib/authors.json
index f4b76624b61ab..5ba696e803f54 100644
--- a/apps/www/lib/authors.json
+++ b/apps/www/lib/authors.json
@@ -969,5 +969,20 @@
"position": "Engineering",
"author_url": "https://github.com/johnstonmatt",
"author_image_url": "https://github.com/johnstonmatt.png"
+ },
+ {
+ "author_id": "natalie_roberge",
+ "author": "Natalie Roberge",
+ "position": "Customer Solutions Architect",
+ "author_url": "https://www.linkedin.com/in/natalieroberge/",
+ "author_image_url": "/images/blog/avatars/natalie-roberge.png"
+ },
+ {
+ "author_id": "david_dalmaso",
+ "author": "David Dalmaso",
+ "position": "Member of Technical Staff",
+ "company": "Perplexity",
+ "author_url": "https://www.linkedin.com/in/david-dalmaso-359791101/",
+ "author_image_url": "/images/blog/avatars/david-dalmaso.png"
}
]
diff --git a/apps/www/pages/legal/dpa.tsx b/apps/www/pages/legal/dpa.tsx
index 0898360591f85..1d2506851e020 100644
--- a/apps/www/pages/legal/dpa.tsx
+++ b/apps/www/pages/legal/dpa.tsx
@@ -1,7 +1,7 @@
-import CTABanner from 'components/CTABanner/index'
import Layout from '~/components/Layouts/Default'
import SectionContainer from '~/components/Layouts/SectionContainer'
import { useSendTelemetryEvent } from '~/lib/telemetry'
+import CTABanner from 'components/CTABanner/index'
const DPA = () => {
const sendTelemetryEvent = useSendTelemetryEvent()
@@ -19,7 +19,7 @@ const DPA = () => {
part of this commitment, we have prepared a Data Processing Addendum ("DPA"). You
can review a static PDF version of our latest DPA document{' '}
+}
+
/**
* User clicked on the CTA button on a plan in the pricing side panel in studio.
*
@@ -3341,6 +3359,7 @@ export type TelemetryEvent =
| StoragePublicBucketSelectPolicyRemovedEvent
| StoragePublicBucketSelectPolicyWarningDismissButtonClickedEvent
| StudioPricingPlanCtaClickedEvent
+ | StudioBillingCancelSubscriptionClickedEvent
| StudioPricingSidePanelOpenedEvent
| ReportsDatabaseGrafanaBannerClickedEvent
| MetricsAPIBannerCtaButtonClickedEvent