CreateBranchModal() — supabase Function Reference
Architecture documentation for the CreateBranchModal() function in CreateBranchModal.tsx from the supabase codebase.
Entity Profile
Dependency Diagram
graph TD 8505154a_957e_9e24_3406_97558e329ee1["CreateBranchModal()"] b6919f21_e0be_6a10_8a1b_c205b35292d5["estimateComputeSize()"] 8505154a_957e_9e24_3406_97558e329ee1 -->|calls| b6919f21_e0be_6a10_8a1b_c205b35292d5 64e987f3_dbda_3f4c_2573_72d778e94d78["estimateDiskCost()"] 8505154a_957e_9e24_3406_97558e329ee1 -->|calls| 64e987f3_dbda_3f4c_2573_72d778e94d78 ab314ffd_d826_229f_1799_b6b77af56fac["estimateRestoreTime()"] 8505154a_957e_9e24_3406_97558e329ee1 -->|calls| ab314ffd_d826_229f_1799_b6b77af56fac style 8505154a_957e_9e24_3406_97558e329ee1 fill:#6366f1,stroke:#818cf8,color:#fff
Relationship Graph
Source Code
apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx lines 65–651
export const CreateBranchModal = () => {
const { ref } = useParams()
const router = useRouter()
const queryClient = useQueryClient()
const { data: projectDetails } = useSelectedProjectQuery()
const { data: selectedOrg } = useSelectedOrganizationQuery()
const { showCreateBranchModal, setShowCreateBranchModal } = useAppStateSnapshot()
const gitlessBranching = useIsBranching2Enabled()
const allowDataBranching = useFlag('allowDataBranching')
const [isGitBranchValid, setIsGitBranchValid] = useState(false)
const { can: canCreateBranch } = useAsyncCheckPermissions(
PermissionAction.CREATE,
'preview_branches'
)
const { hasAccess: hasAccessToBranching, isLoading: isLoadingEntitlement } =
useCheckEntitlements('branching_limit')
const promptPlanUpgrade = IS_PLATFORM && !hasAccessToBranching
const isBranch = projectDetails?.parent_project_ref !== undefined
const projectRef =
projectDetails !== undefined ? (isBranch ? projectDetails.parent_project_ref : ref) : undefined
const formId = 'create-branch-form'
const FormSchema = z.object({
branchName: z
.string()
.min(1, 'Branch name cannot be empty')
.refine(
(val) => /^[a-zA-Z0-9\-_]+$/.test(val),
'Branch name can only contain alphanumeric characters, hyphens, and underscores.'
)
.refine(
(val) => (branches ?? []).every((branch) => branch.name !== val),
'A branch with this name already exists'
),
gitBranchName: z
.string()
.refine(
(val) => gitlessBranching || !githubConnection || (val && val.length > 0),
'Git branch name is required when GitHub is connected'
),
withData: z.boolean().default(false).optional(),
})
const form = useForm<z.infer<typeof FormSchema>>({
mode: 'onSubmit',
reValidateMode: 'onBlur',
resolver: zodResolver(FormSchema),
defaultValues: { branchName: '', gitBranchName: '', withData: false },
})
const { withData, gitBranchName } = form.watch()
const debouncedGitBranchName = useDebounce(gitBranchName, 500)
const {
data: connections,
error: connectionsError,
isPending: isLoadingConnections,
isSuccess: isSuccessConnections,
isError: isErrorConnections,
} = useGitHubConnectionsQuery(
{ organizationId: selectedOrg?.id },
{ enabled: showCreateBranchModal }
)
const { data: branches } = useBranchesQuery({ projectRef })
const { data: addons, isSuccess: isSuccessAddons } = useProjectAddonsQuery(
{ projectRef },
{ enabled: showCreateBranchModal }
)
const computeAddon = addons?.selected_addons.find((addon) => addon.type === 'compute_instance')
const computeSize = !!computeAddon
? (computeAddon.variant.identifier.split('ci_')[1] as DesiredInstanceSize)
: undefined
const hasPitrEnabled =
(addons?.selected_addons ?? []).find((addon) => addon.type === 'pitr') !== undefined
const {
data: disk,
isPending: isLoadingDiskAttr,
isError: isErrorDiskAttr,
} = useDiskAttributesQuery({ projectRef }, { enabled: showCreateBranchModal && withData })
const projectDiskAttributes = disk?.attributes ?? {
type: 'gp3',
size_gb: 0,
iops: 0,
throughput_mbps: 0,
}
// Branch disk is oversized to include backup files, it should be scaled back eventually.
const branchDiskAttributes = {
...projectDiskAttributes,
// [Joshen] JFYI for Qiao - this multiplier may eventually be dropped
size_gb: Math.round(projectDiskAttributes.size_gb * 1.5),
} as DiskAttributesData['attributes']
const branchComputeSize = estimateComputeSize(projectDiskAttributes.size_gb, computeSize)
const estimatedDiskCost = estimateDiskCost(branchDiskAttributes)
const { mutate: sendEvent } = useSendEventMutation()
const { mutate: checkGithubBranchValidity, isPending: isCheckingGHBranchValidity } =
useCheckGithubBranchValidity({
onError: () => {},
})
const { mutate: createBranch, isPending: isCreatingBranch } = useBranchCreateMutation({
onSuccess: async (data) => {
toast.success(`Successfully created preview branch "${data.name}"`)
if (projectRef) {
await Promise.all([
queryClient.invalidateQueries({ queryKey: projectKeys.detail(projectRef) }),
])
}
sendEvent({
action: 'branch_create_button_clicked',
properties: {
branchType: data.persistent ? 'persistent' : 'preview',
gitlessBranching,
},
groups: {
project: ref ?? 'Unknown',
organization: selectedOrg?.slug ?? 'Unknown',
},
})
setShowCreateBranchModal(false)
router.push(`/project/${data.project_ref}`)
},
onError: (error) => {
toast.error(`Failed to create branch: ${error.message}`)
},
})
// Fetch production/default branch to inspect git_branch linkage
const githubConnection = connections?.find((connection) => connection.project.ref === projectRef)
const prodBranch = branches?.find((branch) => branch.is_default)
const [repoOwner, repoName] = githubConnection?.repository.name.split('/') ?? []
const isFormValid = form.formState.isValid && (!gitBranchName || isGitBranchValid)
const isDisabled =
!isFormValid ||
!canCreateBranch ||
!isSuccessAddons ||
!isSuccessConnections ||
isLoadingEntitlement ||
!hasAccessToBranching ||
(!gitlessBranching && !githubConnection) ||
isCreatingBranch ||
isCheckingGHBranchValidity
const tooltipText = promptPlanUpgrade
? 'Upgrade to unlock branching'
: !gitlessBranching && !githubConnection
? 'Set up a GitHub connection first to create branches'
: undefined
const validateGitBranchName = useCallback(
(branchName: string) => {
if (!githubConnection) {
return console.error(
'[CreateBranchModal > validateGitBranchName] GitHub Connection is missing'
)
}
const repositoryId = githubConnection.repository.id
checkGithubBranchValidity(
{ repositoryId, branchName },
{
onSuccess: () => {
if (form.getValues('gitBranchName') !== branchName) return
setIsGitBranchValid(true)
form.clearErrors('gitBranchName')
},
onError: (error) => {
if (form.getValues('gitBranchName') !== branchName) return
setIsGitBranchValid(false)
form.setError('gitBranchName', {
...error,
message: `Unable to find branch "${branchName}" in ${repoOwner}/${repoName}`,
})
},
}
)
},
[githubConnection, form, checkGithubBranchValidity, repoOwner, repoName]
)
const onSubmit = (data: z.infer<typeof FormSchema>) => {
if (!projectRef) return console.error('Project ref is required')
createBranch({
projectRef,
branchName: data.branchName,
is_default: false,
...(data.withData ? { desired_instance_size: computeSize } : {}),
...(data.gitBranchName ? { gitBranch: data.gitBranchName } : {}),
...(allowDataBranching ? { withData: data.withData } : {}),
})
}
const handleGitHubClick = () => {
setShowCreateBranchModal(false)
router.push(`/project/${projectRef}/settings/integrations`)
}
useEffect(() => {
if (form && showCreateBranchModal) {
form.reset()
}
}, [form, showCreateBranchModal])
useEffect(() => {
if (!githubConnection || !debouncedGitBranchName) {
setIsGitBranchValid(gitlessBranching)
form.clearErrors('gitBranchName')
return
}
form.clearErrors('gitBranchName')
validateGitBranchName(debouncedGitBranchName)
}, [debouncedGitBranchName, validateGitBranchName, form, githubConnection, gitlessBranching])
return (
<Dialog open={showCreateBranchModal} onOpenChange={(open) => setShowCreateBranchModal(open)}>
<DialogContent
size="large"
hideClose
onOpenAutoFocus={(e) => {
if (promptPlanUpgrade) e.preventDefault()
}}
aria-describedby={undefined}
>
<DialogHeader padding="small">
<DialogTitle>Create a new preview branch</DialogTitle>
</DialogHeader>
<DialogSectionSeparator />
<Form_Shadcn_ {...form}>
<form id={formId} onSubmit={form.handleSubmit(onSubmit)}>
{promptPlanUpgrade && (
<UpgradeToPro
fullWidth
layout="vertical"
source="create-branch"
featureProposition="enable branching"
primaryText="Upgrade to unlock branching"
secondaryText="Create and test schema changes, functions, and more in a separate, temporary instance without affecting production."
className="pb-5"
/>
)}
<DialogSection
padding="medium"
className={cn('space-y-4', promptPlanUpgrade && 'opacity-25 pointer-events-none')}
>
<FormField_Shadcn_
control={form.control}
name="branchName"
render={({ field }) => (
<FormItemLayout label="Preview Branch Name">
<FormControl_Shadcn_>
<Input_Shadcn_
{...field}
placeholder="e.g. staging, dev-feature-x"
autoComplete="off"
/>
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
{githubConnection && (
<FormField_Shadcn_
control={form.control}
name="gitBranchName"
render={({ field }) => (
<FormItemLayout
label={
<div className="flex items-center justify-between w-full gap-4">
<span className="flex-1">
Sync with Git branch {gitlessBranching ? '(optional)' : ''}
</span>
<div className="flex items-center gap-2 text-sm">
<Image
className={cn('dark:invert')}
src={`${BASE_PATH}/img/icons/github-icon.svg`}
width={16}
height={16}
alt={`GitHub icon`}
/>
<Link
href={`https://github.com/${repoOwner}/${repoName}`}
target="_blank"
rel="noreferrer"
className="text-foreground hover:underline"
>
{repoOwner}/{repoName}
</Link>
</div>
</div>
}
description="Automatically deploy changes on every commit"
>
<div className="relative w-full">
<FormControl_Shadcn_>
<Input_Shadcn_
{...field}
placeholder="e.g. main, feat/some-feature"
autoComplete="off"
onChange={(e) => {
field.onChange(e)
setIsGitBranchValid(false)
}}
/>
</FormControl_Shadcn_>
<div className="absolute top-2.5 right-3 flex items-center gap-2">
{field.value ? (
isCheckingGHBranchValidity ? (
<Loader2 size={14} className="animate-spin" />
) : isGitBranchValid ? (
<Check size={14} className="text-brand" strokeWidth={2} />
) : null
) : null}
</div>
</div>
</FormItemLayout>
)}
/>
)}
{isLoadingConnections && <GenericSkeletonLoader />}
{isErrorConnections && (
<AlertError
error={connectionsError}
subject="Failed to retrieve GitHub connection information"
/>
)}
{isSuccessConnections && (
<>
{!githubConnection && (
<div className="flex items-center gap-2 justify-between">
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<Label>Sync with a GitHub branch</Label>
{!gitlessBranching && <Badge variant="warning">Required</Badge>}
</div>
<p className="text-sm text-foreground-lighter">
Keep this preview branch in sync with a chosen GitHub branch
</p>
</div>
<Button type="default" icon={<Github />} onClick={handleGitHubClick}>
Configure
</Button>
</div>
)}
</>
)}
{allowDataBranching && (
<FormField_Shadcn_
control={form.control}
name="withData"
render={({ field }) => (
<FormItemLayout
label={
<>
<Label className="mr-2">Include data</Label>
{!hasPitrEnabled && <Badge variant="warning">Requires PITR</Badge>}
</>
}
layout="flex-row-reverse"
className="[&>div>label]:mb-1"
description="Clone production data into this branch"
>
<FormControl_Shadcn_>
<Switch
disabled={!hasPitrEnabled}
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
)}
</DialogSection>
<DialogSectionSeparator />
<DialogSection
padding="medium"
className={cn(
'flex flex-col gap-4',
promptPlanUpgrade && 'opacity-25 pointer-events-none'
)}
>
{withData && (
<div className="flex flex-row gap-4">
<div>
<figure className="w-10 h-10 rounded-md bg-info-200 border border-info-400 flex items-center justify-center">
<DatabaseZap className="text-info" size={20} strokeWidth={2} />
</figure>
</div>
<div className="flex flex-col gap-y-1">
{isLoadingDiskAttr ? (
<>
<ShimmeringLoader className="w-32 h-5 py-0" />
<ShimmeringLoader className="w-72 h-8 py-0" />
</>
) : (
<>
{isErrorDiskAttr ? (
<>
<p className="text-sm text-foreground">
Branch disk size will incur additional cost per month
</p>
<p className="text-sm text-foreground-light">
The additional cost and time taken to create a data branch is relative
to the size of your database. We are unable to provide an estimate as
we were unable to retrieve your project's disk configuration
</p>
</>
) : (
<>
<p className="text-sm text-foreground">
Branch disk size is billed at ${estimatedDiskCost.total.toFixed(2)}{' '}
per month
</p>
<p className="text-sm text-foreground-light">
Creating a data branch will take about{' '}
<span className="text-foreground">
{estimateRestoreTime(branchDiskAttributes).toFixed()} minutes
</span>{' '}
and costs{' '}
<span className="text-foreground">
${estimatedDiskCost.total.toFixed(2)}
</span>{' '}
per month based on your current target database volume size of{' '}
{branchDiskAttributes.size_gb} GB and your{' '}
<Tooltip>
<TooltipTrigger>
<span className={InlineLinkClassName}>
project's disk configuration
</span>
</TooltipTrigger>
<TooltipContent side="bottom">
<div className="flex items-center gap-x-2">
<p className="w-24">Disk type:</p>
<p className="w-16">
{branchDiskAttributes.type.toUpperCase()}
</p>
</div>
<div className="flex items-center gap-x-2">
<p className="w-24">Targer disk size:</p>
<p className="w-16">{branchDiskAttributes.size_gb} GB</p>
<p>(${estimatedDiskCost.size.toFixed(2)})</p>
</div>
<div className="flex items-center gap-x-2">
<p className="w-24">IOPs:</p>
<p className="w-16">{branchDiskAttributes.iops} IOPS</p>
<p>(${estimatedDiskCost.iops.toFixed(2)})</p>
</div>
{'throughput_mbps' in branchDiskAttributes && (
<div className="flex items-center gap-x-2">
<p className="w-24">Throughput:</p>
<p className="w-16">
{branchDiskAttributes.throughput_mbps} MB/s
</p>
<p>(${estimatedDiskCost.throughput.toFixed(2)})</p>
</div>
)}
<p className="mt-2">
More info in{' '}
<InlineLink
onClick={() => setShowCreateBranchModal(false)}
className="pointer-events-auto"
href={`/project/${ref}/settings/compute-and-disk`}
>
Compute and Disk
</InlineLink>
</p>
</TooltipContent>
</Tooltip>
.
</p>
</>
)}
</>
)}
</div>
</div>
)}
{githubConnection && (
<div className="flex flex-row gap-4">
<div>
<figure className="w-10 h-10 rounded-md bg-info-200 border border-info-400 flex items-center justify-center">
<GitMerge className="text-info" size={20} strokeWidth={2} />
</figure>
</div>
<div className="flex flex-col gap-y-1">
<p className="text-sm text-foreground">
{prodBranch?.git_branch
? 'Merging to production enabled'
: 'Merging to production disabled'}
</p>
<p className="text-sm text-foreground-light">
{prodBranch?.git_branch ? (
<>
When this branch is merged to{' '}
<span className="text-foreground">{prodBranch.git_branch}</span>,
migrations will be deployed to production. Otherwise, migrations only run
on preview branches.
</>
) : (
<>
Merging this branch to production will not deploy migrations. To enable
production deployment, enable "Deploy to production" in project
integration settings.
</>
)}
</p>
</div>
</div>
)}
<div className="flex flex-row gap-4">
<div>
<figure className="w-10 h-10 rounded-md bg-info-200 border border-info-400 flex items-center justify-center">
<DollarSign className="text-info" size={20} strokeWidth={2} />
</figure>
</div>
<div className="flex flex-col gap-y-1">
<p className="text-sm text-foreground">
Branch compute is billed at $
{withData ? branchComputeSize.priceHourly : instanceSizeSpecs.micro.priceHourly}{' '}
per hour
</p>
<p className="text-sm text-foreground-light">
{withData ? (
<>
<code className="text-code-inline">{branchComputeSize.label}</code> compute
size is automatically selected to match your production branch. You may
downgrade after creation or pause the branch when not in use to save cost.
</>
) : (
<>This cost will continue for as long as the branch has not been removed.</>
)}
</p>
</div>
</div>
{!hasPitrEnabled && <BranchingPITRNotice />}
</DialogSection>
<DialogFooter className="justify-end gap-2" padding="medium">
<Button
type="default"
disabled={isCreatingBranch}
onClick={() => setShowCreateBranchModal(false)}
>
Cancel
</Button>
<ButtonTooltip
form={formId}
disabled={isDisabled}
loading={isCreatingBranch}
type={promptPlanUpgrade ? 'default' : 'primary'}
htmlType="submit"
tooltip={{
content: {
side: 'bottom',
text: tooltipText,
},
}}
>
Create branch
</ButtonTooltip>
</DialogFooter>
</form>
</Form_Shadcn_>
</DialogContent>
</Dialog>
)
}
Domain
Subdomains
Source
Frequently Asked Questions
What does CreateBranchModal() do?
CreateBranchModal() is a function in the supabase codebase.
What does CreateBranchModal() call?
CreateBranchModal() calls 3 function(s): estimateComputeSize, estimateDiskCost, estimateRestoreTime.
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free