Headless Form
You can embed a FlowGenie form inside your own application and handle the UI yourself. The steps are straightforward:
- Build the form using the visual form builder inside FlowGenie.
- Fetch the configuration with a GET request to
https://app.flowgenie.pro/api/headless/forms/{slug} - Render the form in your app by looping through this configuration. Use the
typeof each question to decide which input to render. Use the questionkeyas the field name when collecting values. - Submit responses back to
https://app.flowgenie.pro/api/headless/forms/{slug}/submitwith a POST request.
The response contains a JSON object similar to the structure used in the form builder. It has an array of pages, each containing sections, which in turn contain an array of rows of questions.
API Endpoints
- GET
https://app.flowgenie.pro/api/headless/forms/{slug}- Fetch form configuration - POST
https://app.flowgenie.pro/api/headless/forms/{slug}/submit- Submit form data
Both endpoints require an Authorization: Bearer YOUR_API_KEY header if the form is not public.
Framework Examples
React (JavaScript)
import { useState, useEffect } from 'react';
function FlowGenieForm({ slug }) {
const [formConfig, setFormConfig] = useState(null);
const [answers, setAnswers] = useState({});
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(`https://app.flowgenie.pro/api/headless/forms/${slug}`, {
headers: {
Authorization: 'Bearer YOUR_API_KEY'
}
})
.then(res => res.json())
.then(data => {
setFormConfig(data);
setLoading(false);
});
}, [slug]);
const handleSubmit = async (e) => {
e.preventDefault();
const response = await fetch(`https://app.flowgenie.pro/api/headless/forms/${slug}/submit`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer YOUR_API_KEY'
},
body: JSON.stringify(answers)
});
const result = await response.json();
console.log('Form submitted:', result);
};
const renderQuestion = (question) => {
const { key, type, label, required, placeholder, options } = question;
switch (type) {
case 'text':
case 'email':
case 'password':
return (
<div key={key} className="mb-4">
<label className="block mb-1">{label} {required && '*'}</label>
<input
type={type}
placeholder={placeholder}
required={required}
value={answers[key] || ''}
onChange={(e) => setAnswers({ ...answers, [key]: e.target.value })}
className="w-full px-3 py-2 border rounded"
/>
</div>
);
case 'multiline':
return (
<div key={key} className="mb-4">
<label className="block mb-1">{label} {required && '*'}</label>
<textarea
placeholder={placeholder}
required={required}
value={answers[key] || ''}
onChange={(e) => setAnswers({ ...answers, [key]: e.target.value })}
className="w-full px-3 py-2 border rounded"
rows={4}
/>
</div>
);
case 'number':
return (
<div key={key} className="mb-4">
<label className="block mb-1">{label} {required && '*'}</label>
<input
type="number"
placeholder={placeholder}
required={required}
value={answers[key] || ''}
onChange={(e) => setAnswers({ ...answers, [key]: e.target.value })}
className="w-full px-3 py-2 border rounded"
/>
</div>
);
case 'checkbox':
return (
<div key={key} className="mb-4">
<label className="flex items-center">
<input
type="checkbox"
checked={answers[key] || false}
onChange={(e) => setAnswers({ ...answers, [key]: e.target.checked })}
className="mr-2"
/>
{label}
</label>
</div>
);
case 'select':
return (
<div key={key} className="mb-4">
<label className="block mb-1">{label} {required && '*'}</label>
<select
required={required}
value={answers[key] || ''}
onChange={(e) => setAnswers({ ...answers, [key]: e.target.value })}
className="w-full px-3 py-2 border rounded"
>
<option value="">Select...</option>
{options?.map(opt => (
<option key={opt.key} value={opt.value}>{opt.label}</option>
))}
</select>
</div>
);
case 'date':
return (
<div key={key} className="mb-4">
<label className="block mb-1">{label} {required && '*'}</label>
<input
type="date"
required={required}
value={answers[key] || ''}
onChange={(e) => setAnswers({ ...answers, [key]: e.target.value })}
className="w-full px-3 py-2 border rounded"
/>
</div>
);
case 'multiselect':
return (
<div key={key} className="mb-4">
<label className="block mb-1">{label} {required && '*'}</label>
<div className="space-y-2">
{options?.map(opt => (
<label key={opt.key} className="flex items-center">
<input
type="checkbox"
checked={(answers[key] || []).includes(opt.value)}
onChange={(e) => {
const current = answers[key] || [];
const updated = e.target.checked
? [...current, opt.value]
: current.filter(v => v !== opt.value);
setAnswers({ ...answers, [key]: updated });
}}
className="mr-2"
/>
{opt.label}
</label>
))}
</div>
</div>
);
default:
return <div key={key}>Unsupported type: {type}</div>;
}
};
if (loading) return <div>Loading...</div>;
if (!formConfig) return <div>Form not found</div>;
return (
<form onSubmit={handleSubmit} className="max-w-md mx-auto p-6">
{formConfig.pages[0]?.sections?.map(section =>
section.questions?.flatMap(row =>
row.map(q => renderQuestion(q))
)
)}
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600"
>
Submit
</button>
</form>
);
}Next.js (TypeScript)
'use client';
import { useState, useEffect } from 'react';
interface Question {
id: string;
key: string;
type: string;
label?: string;
required?: boolean;
placeholder?: string;
options?: Array<{ key: string; value: string; label: string }>;
}
interface FormConfig {
pages: Array<{
sections: Array<{
questions: Question[][];
}>;
}>;
}
interface FlowGenieFormProps {
slug: string;
}
export default function FlowGenieForm({ slug }: FlowGenieFormProps) {
const [formConfig, setFormConfig] = useState<FormConfig | null>(null);
const [answers, setAnswers] = useState<Record<string, any>>({});
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(`https://app.flowgenie.pro/api/headless/forms/${slug}`, {
headers: {
Authorization: 'Bearer YOUR_API_KEY'
}
})
.then(res => res.json())
.then((data: FormConfig) => {
setFormConfig(data);
setLoading(false);
})
.catch(err => {
console.error('Error fetching form:', err);
setLoading(false);
});
}, [slug]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
const response = await fetch(`https://app.flowgenie.pro/api/headless/forms/${slug}/submit`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer YOUR_API_KEY'
},
body: JSON.stringify(answers)
});
const result = await response.json();
console.log('Form submitted:', result);
} catch (error) {
console.error('Error submitting form:', error);
}
};
const renderQuestion = (question: Question) => {
const { key, type, label, required, placeholder, options } = question;
switch (type) {
case 'text':
case 'email':
case 'password':
return (
<div key={key} className="mb-4">
<label className="block mb-1">{label} {required && '*'}</label>
<input
type={type}
placeholder={placeholder}
required={required}
value={answers[key] || ''}
onChange={(e) => setAnswers({ ...answers, [key]: e.target.value })}
className="w-full px-3 py-2 border rounded"
/>
</div>
);
case 'multiline':
return (
<div key={key} className="mb-4">
<label className="block mb-1">{label} {required && '*'}</label>
<textarea
placeholder={placeholder}
required={required}
value={answers[key] || ''}
onChange={(e) => setAnswers({ ...answers, [key]: e.target.value })}
className="w-full px-3 py-2 border rounded"
rows={4}
/>
</div>
);
case 'number':
return (
<div key={key} className="mb-4">
<label className="block mb-1">{label} {required && '*'}</label>
<input
type="number"
placeholder={placeholder}
required={required}
value={answers[key] || ''}
onChange={(e) => setAnswers({ ...answers, [key]: e.target.value })}
className="w-full px-3 py-2 border rounded"
/>
</div>
);
case 'checkbox':
return (
<div key={key} className="mb-4">
<label className="flex items-center">
<input
type="checkbox"
checked={answers[key] || false}
onChange={(e) => setAnswers({ ...answers, [key]: e.target.checked })}
className="mr-2"
/>
{label}
</label>
</div>
);
case 'select':
return (
<div key={key} className="mb-4">
<label className="block mb-1">{label} {required && '*'}</label>
<select
required={required}
value={answers[key] || ''}
onChange={(e) => setAnswers({ ...answers, [key]: e.target.value })}
className="w-full px-3 py-2 border rounded"
>
<option value="">Select...</option>
{options?.map(opt => (
<option key={opt.key} value={opt.value}>{opt.label}</option>
))}
</select>
</div>
);
case 'date':
return (
<div key={key} className="mb-4">
<label className="block mb-1">{label} {required && '*'}</label>
<input
type="date"
required={required}
value={answers[key] || ''}
onChange={(e) => setAnswers({ ...answers, [key]: e.target.value })}
className="w-full px-3 py-2 border rounded"
/>
</div>
);
case 'multiselect':
return (
<div key={key} className="mb-4">
<label className="block mb-1">{label} {required && '*'}</label>
<div className="space-y-2">
{options?.map(opt => (
<label key={opt.key} className="flex items-center">
<input
type="checkbox"
checked={(answers[key] || []).includes(opt.value)}
onChange={(e) => {
const current = answers[key] || [];
const updated = e.target.checked
? [...current, opt.value]
: current.filter(v => v !== opt.value);
setAnswers({ ...answers, [key]: updated });
}}
className="mr-2"
/>
{opt.label}
</label>
))}
</div>
</div>
);
default:
return <div key={key}>Unsupported type: {type}</div>;
}
};
if (loading) return <div>Loading...</div>;
if (!formConfig) return <div>Form not found</div>;
return (
<form onSubmit={handleSubmit} className="max-w-md mx-auto p-6">
{formConfig.pages[0]?.sections?.map(section =>
section.questions?.flatMap(row =>
row.map(q => renderQuestion(q))
)
)}
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600"
>
Submit
</button>
</form>
);
}Vanilla JavaScript
<!DOCTYPE html>
<html>
<head>
<title>FlowGenie Form</title>
<style>
.form-container { max-width: 600px; margin: 0 auto; padding: 20px; }
.form-group { margin-bottom: 20px; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input, textarea, select { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; }
button { background: #3b82f6; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background: #2563eb; }
</style>
</head>
<body>
<div class="form-container">
<form id="flowgenie-form">
<div id="form-questions"></div>
<button type="submit">Submit</button>
</form>
</div>
<script>
const slug = 'your-form-slug';
const apiKey = 'YOUR_API_KEY';
let formConfig = null;
const answers = {};
// Fetch form configuration
async function loadForm() {
const response = await fetch(`https://app.flowgenie.pro/api/headless/forms/${slug}`, {
headers: {
'Authorization': `Bearer ${apiKey}`
}
});
formConfig = await response.json();
renderForm();
}
// Render form questions
function renderForm() {
const container = document.getElementById('form-questions');
container.innerHTML = '';
formConfig.pages[0].sections.forEach(section => {
section.questions.forEach(row => {
row.forEach(question => {
const div = document.createElement('div');
div.className = 'form-group';
const label = document.createElement('label');
label.textContent = question.label + (question.required ? ' *' : '');
div.appendChild(label);
let input;
switch (question.type) {
case 'text':
case 'email':
case 'password':
input = document.createElement('input');
input.type = question.type;
input.placeholder = question.placeholder || '';
break;
case 'multiline':
input = document.createElement('textarea');
input.placeholder = question.placeholder || '';
break;
case 'number':
input = document.createElement('input');
input.type = 'number';
input.placeholder = question.placeholder || '';
break;
case 'checkbox':
input = document.createElement('input');
input.type = 'checkbox';
break;
case 'select':
input = document.createElement('select');
const defaultOption = document.createElement('option');
defaultOption.value = '';
defaultOption.textContent = 'Select...';
input.appendChild(defaultOption);
question.options?.forEach(opt => {
const option = document.createElement('option');
option.value = opt.value;
option.textContent = opt.label;
input.appendChild(option);
});
break;
case 'date':
input = document.createElement('input');
input.type = 'date';
break;
default:
return;
}
input.id = question.key;
input.required = question.required || false;
input.addEventListener('change', (e) => {
if (question.type === 'checkbox') {
answers[question.key] = e.target.checked;
} else {
answers[question.key] = e.target.value;
}
});
div.appendChild(input);
container.appendChild(div);
});
});
});
}
// Handle form submission
document.getElementById('flowgenie-form').addEventListener('submit', async (e) => {
e.preventDefault();
const response = await fetch(`https://app.flowgenie.pro/api/headless/forms/${slug}/submit`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify(answers)
});
const result = await response.json();
console.log('Form submitted:', result);
alert('Form submitted successfully!');
});
// Load form on page load
loadForm();
</script>
</body>
</html>This approach lets you keep complete control over the look and feel while relying on FlowGenie for form management and submission handling.
Last updated on