Fetch a form’s definition over REST to render it in a headless frontend
(React, Vue, Next.js). Submission, however, does not go through REST.
> Submission is AJAX-only. POST /formforge/v1/forms/{id}/submit is not
> a public submission endpoint — it returns HTTP 501 Not Implemented. Real
> submissions go through the AJAX action formforge_submit (and its nopriv
> twin), which enforces a WordPress nonce, structural anti-spam (honeypot,
> timing, HMAC token), payment intents, and file uploads. A tokenless JSON POST
> cannot satisfy those guards, so it is intentionally rejected rather than
> silently accepting an unprotected submission. To submit programmatically,
> render the form with the Form not found. shortcode (which embeds the
> nonce + anti-spam tokens) and let its bundled script post to AJAX.
Fetching Form Data
// Fetch form definition from REST API (admin auth required)
const response = await fetch( 'https://example.com/wp-json/formforge/v1/forms/1', {
headers: { 'Authorization': 'Basic ' + btoa( 'admin:XXXX XXXX' ) }
} );
const form = await response.json();
// Render fields dynamically in React/Vue
form.fields.forEach( field => {
// Build your own UI components based on field.type, field.label, etc.
} );Submitting
Embed the shortcode so the rendered markup carries the nonce + anti-spam tokens,
then let Form Forge’s script handle the AJAX POST:
echo do_shortcode( 'Form not found.
' );If you must drive submission from JavaScript, POST to admin-ajax.php with the
formforge_submit action and the nonce that the shortcode rendered into the
page — do not POST JSON to the REST /submit route (it returns 501).
Building a React Form Component (read-only render)
function FormForgeHeadless({ formId, apiBase }) {
const [form, setForm] = React.useState(null);
const [values, setValues] = React.useState({});
const [message, setMessage] = React.useState('');
React.useEffect(() => {
fetch(`${apiBase}/wp-json/formforge/v1/forms/${formId}`, {
headers: { 'Authorization': 'Basic ' + btoa('admin:XXXX') }
})
.then(r => r.json())
.then(setForm);
}, [formId]);
const handleSubmit = async (e) => {
e.preventDefault();
// Submission goes through admin-ajax.php (action "formforge_submit")
// with the nonce rendered by the Form not found.
shortcode — NOT the REST
// /submit route, which returns 501. See the note above.
setMessage('Submit via the Form not found.
shortcode / AJAX endpoint.');
};
if (!form) return <p>Loading...</p>;
return (
<form onSubmit={handleSubmit}>
{form.fields.map(field => (
<div key={field.id}>
<label>{field.label}</label>
<input
type={field.type === 'textarea' ? undefined : field.type}
required={field.required}
placeholder={field.placeholder}
onChange={e => setValues({...values, [field.id]: e.target.value})}
/>
</div>
))}
<button type="submit">{form.settings.submit_text}</button>
{message && <p>{message}</p>}
</form>
);
}—