chore(studio): add custom instance type input to project creation GENCOMP-76 (#45660)

## Summary
- Adds a "Custom instance type" input on the new-project modal, rendered
directly below the existing custom Postgres version field and gated
behind the same non-prod check.
- Wires the value through
`custom_supabase_internal_requests.ami.instance_type`, merged with the
existing AMI search-tag payload so both can be set independently.

<img width="312" height="133" alt="Screenshot 2026-05-07 at 12 32 41 PM"
src="https://github.com/user-attachments/assets/d4190a0f-0a54-46e6-ac0b-967548a3903f"
/>

## Test plan
- [x] On a non-prod build, open the new-project modal and confirm the
"Custom instance type" field appears below "Custom Postgres version".
- [ ] Submit with only an instance type set and verify the request body
includes `custom_supabase_internal_requests.instance_type` and no `ami`
block.
- [x] Submit with both fields set and verify both `ami.search_tags` and
`instance_type` are sent.
- [x] Submit with neither set and verify
`custom_supabase_internal_requests` is omitted.
- [x] Verify the field is hidden in prod builds.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added instance type field to project-creation wizard.
* Added an internal-only configuration panel for advanced customization.

* **Refactor**
  * Simplified Advanced Configuration panel layout and behavior.

* **Documentation**
  * Updated documentation links to use internal reference URLs.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-05-07 14:51:21 +08:00
1 parent 5ce163fd69
commit 08efb963f9
6 files changed
+186 -155

No files matched your search

@@ -26,121 +26,111 @@ import { DOCS_URL } from '@/lib/constants'
interface AdvancedConfigurationProps {
form: UseFormReturn<CreateProjectForm>
layout?: 'vertical' | 'horizontal'
collapsible?: boolean
}
export const AdvancedConfiguration = ({
form,
layout = 'horizontal',
collapsible = true,
}: AdvancedConfigurationProps) => {
export const AdvancedConfiguration = ({ form }: AdvancedConfigurationProps) => {
const disableOrioleProjectCreation = useFlag('disableOrioleProjectCreation')
const content = (
<>
<FormField
name="useOrioleDb"
control={form.control}
render={({ field }) => (
<>
<FormItemLayout
layout={layout}
label="Postgres Type"
className="[&>div>label]:break-normal!"
>
<FormControl>
<RadioGroupStacked
// Due to radio group not supporting boolean values
// value is converted to boolean
onValueChange={(value) => field.onChange(value === 'true')}
defaultValue={field.value.toString()}
return (
<Panel.Content>
<Collapsible_Shadcn_>
<CollapsibleTrigger_Shadcn_ className="group/advanced-trigger font-mono uppercase tracking-widest text-xs flex items-center gap-1 text-foreground-lighter/75 hover:text-foreground-light transition data-open:text-foreground-light">
Advanced Configuration
<ChevronRight
size={16}
strokeWidth={1}
className="mr-2 group-data-open/advanced-trigger:rotate-90 group-hover/advanced-trigger:text-foreground-light transition"
/>
</CollapsibleTrigger_Shadcn_>
<CollapsibleContent_Shadcn_
className={cn(
'pt-2 data-closed:animate-collapsible-up data-open:animate-collapsible-down'
)}
>
<p className="text-xs text-foreground-lighter mb-6">
These settings cannot be changed after the project is created
</p>
<FormField
name="useOrioleDb"
control={form.control}
render={({ field }) => (
<>
<FormItemLayout
layout="horizontal"
label="Postgres Type"
className="[&>div>label]:break-normal!"
>
<FormItem asChild>
<FormControl>
<RadioGroupStackedItem
value="false"
// @ts-ignore
label={
<>
Postgres
<Badge>Default</Badge>
</>
}
description="Recommended for production workloads"
className="[&>div>div>p]:text-left [&>div>div>p]:text-xs [&>div>div>label]:flex [&>div>div>label]:items-center [&>div>div>label]:gap-x-2"
/>
</FormControl>
</FormItem>
<FormItem asChild>
<FormControl>
<Tooltip>
<TooltipTrigger asChild>
<FormControl>
<RadioGroupStacked
// Due to radio group not supporting boolean values
// value is converted to boolean
onValueChange={(value) => field.onChange(value === 'true')}
defaultValue={field.value.toString()}
>
<FormItem asChild>
<FormControl>
<RadioGroupStackedItem
value="true"
value="false"
// @ts-ignore
label={
<>
Postgres with OrioleDB
<Badge variant="warning">Alpha</Badge>
Postgres
<Badge>Default</Badge>
</>
}
description="Not recommended for production workloads"
className={cn(
'[&>div>div>p]:text-left [&>div>div>p]:text-xs [&>div>div>label]:flex [&>div>div>label]:items-center [&>div>div>label]:gap-x-2',
form.getValues('useOrioleDb') ? 'rounded-b-none!' : ''
)}
disabled={disableOrioleProjectCreation}
description="Recommended for production workloads"
className="[&>div>div>p]:text-left [&>div>div>p]:text-xs [&>div>div>label]:flex [&>div>div>label]:items-center [&>div>div>label]:gap-x-2"
/>
</TooltipTrigger>
{disableOrioleProjectCreation && (
<TooltipContent side="right" className="w-60 text-center">
OrioleDB is temporarily disabled for new projects. Please try again
later.
</TooltipContent>
)}
</Tooltip>
</FormControl>
</FormItem>
</RadioGroupStacked>
</FormControl>
{form.getValues('useOrioleDb') && (
<Admonition
type="warning"
className="rounded-t-none [&>div]:text-xs"
title="OrioleDB is not production ready"
description="Postgres with OrioleDB extension is currently in Public Alpha and not recommended for production usage yet."
>
<DocsButton className="mt-2" href={`${DOCS_URL}/guides/database/orioledb`} />
</Admonition>
)}
</FormItemLayout>
</>
)}
/>
<p className="text-xs text-foreground-lighter mt-3">
These settings cannot be changed after the project is created
</p>
</>
</FormControl>
</FormItem>
<FormItem asChild>
<FormControl>
<Tooltip>
<TooltipTrigger asChild>
<RadioGroupStackedItem
value="true"
// @ts-ignore
label={
<>
Postgres with OrioleDB
<Badge variant="warning">Alpha</Badge>
</>
}
description="Not recommended for production workloads"
className={cn(
'[&>div>div>p]:text-left [&>div>div>p]:text-xs [&>div>div>label]:flex [&>div>div>label]:items-center [&>div>div>label]:gap-x-2',
form.getValues('useOrioleDb') ? 'rounded-b-none!' : ''
)}
disabled={disableOrioleProjectCreation}
/>
</TooltipTrigger>
{disableOrioleProjectCreation && (
<TooltipContent side="right" className="w-60 text-center">
OrioleDB is temporarily disabled for new projects. Please try again
later.
</TooltipContent>
)}
</Tooltip>
</FormControl>
</FormItem>
</RadioGroupStacked>
</FormControl>
{form.getValues('useOrioleDb') && (
<Admonition
type="warning"
className="rounded-t-none [&>div]:text-xs"
title="OrioleDB is not production ready"
description="Postgres with OrioleDB extension is currently in Public Alpha and not recommended for production usage yet."
>
<DocsButton className="mt-2" href={`${DOCS_URL}/guides/database/orioledb`} />
</Admonition>
)}
</FormItemLayout>
</>
)}
/>
</CollapsibleContent_Shadcn_>
</Collapsible_Shadcn_>
</Panel.Content>
)
const collapsibleContent = (
<Collapsible_Shadcn_>
<CollapsibleTrigger_Shadcn_ className="group/advanced-trigger font-mono uppercase tracking-widest text-xs flex items-center gap-1 text-foreground-lighter/75 hover:text-foreground-light transition data-open:text-foreground-light">
Advanced Configuration
<ChevronRight
size={16}
strokeWidth={1}
className="mr-2 group-data-open/advanced-trigger:rotate-90 group-hover/advanced-trigger:text-foreground-light transition"
/>
</CollapsibleTrigger_Shadcn_>
<CollapsibleContent_Shadcn_
className={cn('pt-5 data-closed:animate-collapsible-up data-open:animate-collapsible-down')}
>
{content}
</CollapsibleContent_Shadcn_>
</Collapsible_Shadcn_>
)
return <Panel.Content>{collapsible ? collapsibleContent : content}</Panel.Content>
}
@@ -1,32 +0,0 @@
import { UseFormReturn } from 'react-hook-form'
import { FormControl, FormField, Input_Shadcn_ } from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { CreateProjectForm } from './ProjectCreation.schema'
import Panel from '@/components/ui/Panel'
interface CustomPostgresVersionInputProps {
form: UseFormReturn<CreateProjectForm>
}
export const CustomPostgresVersionInput = ({ form }: CustomPostgresVersionInputProps) => {
return (
<Panel.Content>
<FormField
control={form.control}
name="postgresVersion"
render={({ field }) => (
<FormItemLayout
label="Custom Postgres version"
layout="horizontal"
description="Specify a custom version of Postgres (defaults to the latest). This is only applicable for local/staging projects."
>
<FormControl>
<Input_Shadcn_ placeholder="Postgres version" {...field} autoComplete="off" />
</FormControl>
</FormItemLayout>
)}
/>
</Panel.Content>
)
}
@@ -0,0 +1,67 @@
import { CollapsibleContent, CollapsibleTrigger } from '@ui/components/shadcn/ui/collapsible'
import { ChevronRight } from 'lucide-react'
import { UseFormReturn } from 'react-hook-form'
import { Collapsible, FormControl, FormField, Input_Shadcn_ } from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { CreateProjectForm } from './ProjectCreation.schema'
import Panel from '@/components/ui/Panel'
interface InternalOnlyConfigurationProps {
form: UseFormReturn<CreateProjectForm>
}
export const InternalOnlyConfiguration = ({ form }: InternalOnlyConfigurationProps) => {
return (
<Panel.Content>
<Collapsible>
<CollapsibleTrigger className="group/advanced-trigger font-mono uppercase tracking-widest text-xs flex items-center gap-1 text-foreground-lighter/75 hover:text-foreground-light transition data-open:text-foreground-light">
Internal-only Configuration
<ChevronRight
size={16}
strokeWidth={1}
className="mr-2 group-data-open/advanced-trigger:rotate-90 group-hover/advanced-trigger:text-foreground-light transition"
/>
</CollapsibleTrigger>
<CollapsibleContent className="pt-2 data-closed:animate-collapsible-up data-open:animate-collapsible-down">
<p className="text-xs text-foreground-lighter mb-6">
These settings are only applicable for local/staging projects
</p>
<div className="flex flex-col gap-y-4">
<FormField
control={form.control}
name="postgresVersion"
render={({ field }) => (
<FormItemLayout
label="Custom Postgres version"
layout="horizontal"
description="Specify a custom version of Postgres (defaults to the latest)."
>
<FormControl>
<Input_Shadcn_ placeholder="e.g 17.6.1.104" {...field} autoComplete="off" />
</FormControl>
</FormItemLayout>
)}
/>
<FormField
control={form.control}
name="instanceType"
render={({ field }) => (
<FormItemLayout
label="Custom instance type"
layout="horizontal"
description="Specify a custom instance type."
>
<FormControl>
<Input_Shadcn_ placeholder="e.g t3.nano" {...field} autoComplete="off" />
</FormControl>
</FormItemLayout>
)}
/>
</div>
</CollapsibleContent>
</Collapsible>
</Panel.Content>
)
}
@@ -17,6 +17,7 @@ export const FormSchema = z
postgresVersion: z.string({
required_error: 'Please enter a Postgres version.',
}),
instanceType: z.string().optional(),
dbRegion: z.string({
required_error: 'Please select a region.',
}),
@@ -1,4 +1,3 @@
import Link from 'next/link'
import { UseFormReturn } from 'react-hook-form'
import {
Checkbox,
@@ -17,8 +16,10 @@ import { Admonition } from 'ui-patterns'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { CreateProjectForm } from './ProjectCreation.schema'
import { InlineLink } from '@/components/ui/InlineLink'
import Panel from '@/components/ui/Panel'
import { useTrackDefaultPrivilegesExposure } from '@/hooks/misc/useDataApiRevokeOnCreateDefault'
import { DOCS_URL } from '@/lib/constants'
interface SecurityOptionsProps {
form: UseFormReturn<CreateProjectForm>
@@ -51,13 +52,9 @@ export const SecurityOptions = ({ form, layout = 'horizontal' }: SecurityOptions
<FormDescription className="text-foreground-lighter">
Autogenerate a RESTful API for your public schema. Recommended if using a client
library like{' '}
<Link
href="https://supabase.com/docs/reference/javascript/introduction"
target="_blank"
className="text-link"
>
<InlineLink href={`${DOCS_URL}/reference/javascript/introduction`}>
supabase-js
</Link>
</InlineLink>
.
</FormDescription>
</div>
+20 -12
View File
@@ -18,11 +18,11 @@ import { AUTO_ENABLE_RLS_EVENT_TRIGGER_SQL } from '@/components/interfaces/Datab
import { AdvancedConfiguration } from '@/components/interfaces/ProjectCreation/AdvancedConfiguration'
import { CloudProviderSelector } from '@/components/interfaces/ProjectCreation/CloudProviderSelector'
import { ComputeSizeSelector } from '@/components/interfaces/ProjectCreation/ComputeSizeSelector'
import { CustomPostgresVersionInput } from '@/components/interfaces/ProjectCreation/CustomPostgresVersionInput'
import { DatabasePasswordInput } from '@/components/interfaces/ProjectCreation/DatabasePasswordInput'
import { DisabledWarningDueToIncident } from '@/components/interfaces/ProjectCreation/DisabledWarningDueToIncident'
import { FreeProjectLimitWarning } from '@/components/interfaces/ProjectCreation/FreeProjectLimitWarning'
import { HighAvailabilityInput } from '@/components/interfaces/ProjectCreation/HighAvailabilityInput'
import { InternalOnlyConfiguration } from '@/components/interfaces/ProjectCreation/InternalOnlyConfiguration'
import { OrganizationSelector } from '@/components/interfaces/ProjectCreation/OrganizationSelector'
import {
extractPostgresVersionDetails,
@@ -131,6 +131,7 @@ const Wizard: NextPageWithLayout = () => {
projectName: projectName || '',
highAvailability: false,
postgresVersion: '',
instanceType: '',
cloudProvider: PROVIDERS[defaultProvider].id,
dbPass: '',
dbPassStrength: 0,
@@ -311,6 +312,7 @@ const Wizard: NextPageWithLayout = () => {
dbPass,
dbRegion,
postgresVersion,
instanceType,
instanceSize,
dataApi,
dataApiDefaultPrivileges,
@@ -355,15 +357,20 @@ const Wizard: NextPageWithLayout = () => {
.join('\n') || undefined,
}
if (postgresVersion) {
if (!postgresVersion.match(/1[2-9]\..*/)) {
toast.error(
`Invalid Postgres version, should start with a number between 12-19, a dot and additional characters, i.e. 15.2 or 15.2.0-3`
)
}
if (postgresVersion && !postgresVersion.match(/1[2-9]\..*/)) {
toast.error(
`Invalid Postgres version, should start with a number between 12-19, a dot and additional characters, i.e. 15.2 or 15.2.0-3`
)
}
if (postgresVersion || instanceType) {
data['customSupabaseRequest'] = {
ami: { search_tags: { 'tag:postgresVersion': postgresVersion } },
ami: {
...(postgresVersion && {
search_tags: { 'tag:postgresVersion': postgresVersion },
}),
...(instanceType && { instance_type: instanceType }),
},
}
}
@@ -463,7 +470,7 @@ const Wizard: NextPageWithLayout = () => {
{projectCreationDisabled ? (
<DisabledWarningDueToIncident title="Project creation is currently disabled" />
) : (
<div className="divide-y divide-border-muted">
<div className="divide-y divide-border-border">
<OrganizationSelector form={form} />
{canCreateProject && (
@@ -502,16 +509,17 @@ const Wizard: NextPageWithLayout = () => {
</Panel.Content>
)}
{showNonProdFields && <CustomPostgresVersionInput form={form} />}
<SecurityOptions form={form} />
{showAdvancedConfig && !!availableOrioleVersion && (
<AdvancedConfiguration form={form} />
)}
{showNonProdFields && <InternalOnlyConfiguration form={form} />}
{shouldShowFreeProjectInfo ? (
<Admonition
className="rounded-none border-0 border-t"
className="rounded-none border-0"
type="note"
title="Need a free project?"
description={