@@ -65,6 +65,7 @@ import {
6565 AI_SUGGESTION_WATCHER_KEYS ,
6666 stripConditionalSystemValues ,
6767} from "../../shared/conditional-system-watchers" ;
68+ import { resolveRuntimeContextString } from "../../shared/runtime-context" ;
6869import { FormActionsProvider } from "../context/FormActionsContext" ;
6970import type { FormReturnIntent } from "../context/FormActionsContext" ;
7071import { FormAttributesProvider } from "../context/FormAttributesContext" ;
@@ -94,6 +95,10 @@ interface SubmissionMetaRuntime {
9495 aiSourcesUsed ?: boolean ;
9596}
9697
98+ function isAbsoluteHttpUrl ( value : string ) : boolean {
99+ return / ^ h t t p s ? : \/ \/ / i. test ( value ) ;
100+ }
101+
97102function hasNamedValueField (
98103 field : FieldConfig ,
99104) : field is Extract < FieldConfig , { name : string } > {
@@ -795,6 +800,17 @@ export function FormShell({
795800 } , [ fieldDefaultValuesFromStore , initialValues , reducerState . values ] ) ;
796801
797802 const frontendApiBaseUrl = getFrontendApiBaseUrl ( ) ;
803+ const resolvedEndpointPath = useMemo ( ( ) => {
804+ const rawValue = form . endpointPath ?. trim ( ) ;
805+ if ( ! rawValue ) {
806+ return undefined ;
807+ }
808+
809+ const resolvedValue = resolveRuntimeContextString ( rawValue , form . wpContext )
810+ . trim ( ) ;
811+
812+ return resolvedValue || undefined ;
813+ } , [ form . endpointPath , form . wpContext ] ) ;
798814 const fileFields = useMemo ( ( ) => collectFileFields ( fields ) , [ fields ] ) ;
799815
800816 const emitFormEvent = useCallback (
@@ -863,6 +879,28 @@ export function FormShell({
863879
864880 const dispatchFrontendRequest = useCallback (
865881 async < TResponse , > ( path : string , body : unknown ) : Promise < TResponse > => {
882+ const normalizedPath = path . trim ( ) ;
883+
884+ if ( isAbsoluteHttpUrl ( normalizedPath ) ) {
885+ const recaptchaHeaders = await getRecaptchaHeaders ( ) ;
886+ const response = await fetch ( normalizedPath , {
887+ method : "POST" ,
888+ headers : {
889+ "Content-Type" : "application/json" ,
890+ ...recaptchaHeaders ,
891+ } ,
892+ body : JSON . stringify ( body ?? { } ) ,
893+ credentials : "omit" ,
894+ } ) ;
895+
896+ if ( ! response . ok ) {
897+ const text = await response . text ( ) ;
898+ throw new Error ( text || `Request failed (${ response . status } )` ) ;
899+ }
900+
901+ return ( await response . json ( ) ) as TResponse ;
902+ }
903+
866904 const backend = await resolveBackend ( ) ;
867905
868906 if ( backend . available ) {
@@ -875,7 +913,7 @@ export function FormShell({
875913 reason : "Form submission" ,
876914 } ,
877915 "frontend" ,
878- path ,
916+ normalizedPath ,
879917 "POST" ,
880918 body ,
881919 { } ,
@@ -891,7 +929,7 @@ export function FormShell({
891929
892930 const recaptchaHeaders = await getRecaptchaHeaders ( ) ;
893931
894- const response = await fetch ( `${ frontendApiBaseUrl } ${ path } ` , {
932+ const response = await fetch ( `${ frontendApiBaseUrl } ${ normalizedPath } ` , {
895933 method : "POST" ,
896934 headers : {
897935 "Content-Type" : "application/json" ,
@@ -1411,9 +1449,9 @@ export function FormShell({
14111449
14121450 let response : FormSubmitResponse ;
14131451
1414- if ( form . endpointPath ) {
1452+ if ( resolvedEndpointPath ) {
14151453 response = await dispatchFrontendRequest < FormSubmitResponse > (
1416- form . endpointPath ,
1454+ resolvedEndpointPath ,
14171455 submitRequest ,
14181456 ) ;
14191457 } else {
@@ -1497,6 +1535,7 @@ export function FormShell({
14971535 currentLanguage ,
14981536 prepareSerializableValues ,
14991537 requestViewScrollReset ,
1538+ resolvedEndpointPath ,
15001539 resumeDraftIdInput ,
15011540 resumePasswordInput ,
15021541 state . aiSuggestions ,
0 commit comments