import { zodResolver } from '@hookform/resolvers/zod' import { PermissionAction } from '@supabase/shared-types/out/constants' import dayjs from 'dayjs' import { ExternalLink } from 'lucide-react' import Link from 'next/link' import { useRouter } from 'next/router' import { useEffect, useMemo, useState } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import { toast } from 'sonner' import z from 'zod' import { useParams } from 'common' import { useApiKeysVisibility } from 'components/interfaces/APIKeys/hooks/useApiKeysVisibility' import AlertError from 'components/ui/AlertError' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query' import { useEdgeFunctionQuery } from 'data/edge-functions/edge-function-query' import { useEdgeFunctionDeleteMutation } from 'data/edge-functions/edge-functions-delete-mutation' import { useEdgeFunctionUpdateMutation } from 'data/edge-functions/edge-functions-update-mutation' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { DOCS_URL } from 'lib/constants' import { Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_, Button, Card, CardContent, CardFooter, cn, CodeBlock, copyToClipboard, CriticalIcon, Form_Shadcn_, FormControl_Shadcn_, FormField_Shadcn_, Input, Input_Shadcn_, Switch, Tabs_Shadcn_ as Tabs, TabsContent_Shadcn_ as TabsContent, TabsList_Shadcn_ as TabsList, TabsTrigger_Shadcn_ as TabsTrigger, } from 'ui' import { GenericSkeletonLoader } from 'ui-patterns' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { PageContainer } from 'ui-patterns/PageContainer' import { PageSection, PageSectionContent, PageSectionSummary, PageSectionTitle, } from 'ui-patterns/PageSection' import CommandRender from '../CommandRender' import { INVOCATION_TABS } from './EdgeFunctionDetails.constants' import { generateCLICommands } from './EdgeFunctionDetails.utils' const FormSchema = z.object({ name: z.string().min(0, 'Name is required'), verify_jwt: z.boolean(), }) export const EdgeFunctionDetails = () => { const router = useRouter() const { ref: projectRef, functionSlug } = useParams() const showAllEdgeFunctionInvocationExamples = useIsFeatureEnabled( 'edge_functions:show_all_edge_function_invocation_examples' ) const invocationTabs = useMemo(() => { if (showAllEdgeFunctionInvocationExamples) return INVOCATION_TABS return INVOCATION_TABS.filter((tab) => tab.id === 'curl' || tab.id === 'supabase-js') }, [showAllEdgeFunctionInvocationExamples]) const [showKey, setShowKey] = useState(false) const [selectedTab, setSelectedTab] = useState(invocationTabs[0].id) const [showDeleteModal, setShowDeleteModal] = useState(false) const { can: canUpdateEdgeFunction } = useAsyncCheckPermissions( PermissionAction.FUNCTIONS_WRITE, '*' ) const { canReadAPIKeys } = useApiKeysVisibility() const { data: apiKeys } = useAPIKeysQuery( { projectRef, }, { enabled: canReadAPIKeys } ) const { data: settings } = useProjectSettingsV2Query({ projectRef }) const { data: customDomainData } = useCustomDomainsQuery({ projectRef }) const { data: selectedFunction, error, isLoading, isError, isSuccess, } = useEdgeFunctionQuery({ projectRef, slug: functionSlug, }) const { mutate: updateEdgeFunction, isPending: isUpdating } = useEdgeFunctionUpdateMutation() const { mutate: deleteEdgeFunction, isPending: isDeleting } = useEdgeFunctionDeleteMutation({ onSuccess: () => { toast.success(`Successfully deleted "${selectedFunction?.name}"`) router.push(`/project/${projectRef}/functions`) }, }) const form = useForm({ resolver: zodResolver(FormSchema), defaultValues: { name: '', verify_jwt: false }, }) const { anonKey, publishableKey } = getKeys(apiKeys) const apiKey = publishableKey?.api_key ?? anonKey?.api_key ?? '[YOUR ANON KEY]' const protocol = settings?.app_config?.protocol ?? 'https' const endpoint = settings?.app_config?.endpoint ?? '' const functionUrl = customDomainData?.customDomain?.status === 'active' ? `https://${customDomainData.customDomain.hostname}/functions/v1/${selectedFunction?.slug}` : `${protocol}://${endpoint}/functions/v1/${selectedFunction?.slug}` const hasImportMap = useMemo( () => selectedFunction?.import_map || selectedFunction?.import_map_path, [selectedFunction] ) const { managementCommands } = generateCLICommands({ selectedFunction, functionUrl, anonKey: apiKey, }) const onUpdateFunction: SubmitHandler> = async (values: any) => { if (!projectRef) return console.error('Project ref is required') if (selectedFunction === undefined) return console.error('No edge function selected') updateEdgeFunction( { projectRef, slug: selectedFunction.slug, payload: values, }, { onSuccess: () => { toast.success(`Successfully updated edge function`) }, } ) } const onConfirmDelete = async () => { if (!projectRef) return console.error('Project ref is required') if (selectedFunction === undefined) return console.error('No edge function selected') deleteEdgeFunction({ projectRef, slug: selectedFunction.slug }) } useEffect(() => { if (selectedFunction) { form.reset({ name: selectedFunction.name, verify_jwt: selectedFunction.verify_jwt, }) } }, [selectedFunction]) return ( Details {isLoading && } {isError && ( )} {isSuccess && (
Slug
{selectedFunction?.slug}
Endpoint URL
Region
All functions are deployed globally
Created at
{dayjs(selectedFunction?.created_at ?? 0).format('dddd, MMMM D, YYYY h:mm A')}
Last updated at
{dayjs(selectedFunction?.updated_at ?? 0).format('dddd, MMMM D, YYYY h:mm A')}
Deployments
{selectedFunction?.version ?? 0}
Import Maps

Import maps are{' '} {hasImportMap ? 'used' : 'not used'} {' '} for this function

Import maps allow the use of bare specifiers in functions instead of explicit import URLs

)}
Function Configuration
( )} /> ( Requires that a JWT signed{' '} only by the legacy JWT secret {' '} is present in the Authorization header. The easy to obtain anon key can be used to satisfy this requirement. Recommendation: OFF with JWT and additional authorization logic implemented inside your function's code. } > )} /> {form.formState.isDirty && ( )}
Invoke function {invocationTabs.map((tab) => ( {tab.label} ))} {selectedTab === 'curl' && ( )} {invocationTabs.map((tab) => { const code = tab.code({ showKey, functionUrl, functionName: selectedFunction?.name ?? '', apiKey, }) return ( code]:!whitespace-pre-wrap', showKey ? '[&>code]:break-all' : '[&>code]:break-words' )} language={tab.language} wrapLines={true} hideLineNumbers={tab.hideLineNumbers} handleCopy={() => { copyToClipboard( tab.code({ showKey: true, functionUrl, functionName: selectedFunction?.name ?? '', apiKey, }) ) }} /> ) })} Develop locally
( <> supabase functions download{' '} {selectedFunction?.slug} ), comment: '1. Download the function', }, ]} />
Delete function Once your function is deleted, it can no longer be restored Make sure you have made a backup if you want to restore your edge function setShowDeleteModal(false)} onConfirm={onConfirmDelete} alert={{ base: { variant: 'destructive' }, title: 'This action cannot be undone', description: 'Ensure that you have made a backup if you want to restore your edge function', }} />
) }