The SDK gives you full control over form rendering while handling the API protocol for you. Use it in React, Next.js, or any TypeScript environment.
Installation
npm install @vlozi/forms
# or
pnpm add @vlozi/formsHeadless client
import { createFormClient } from "@vlozi/forms";
const form = createFormClient({
formId: "YOUR_FORM_ID",
baseUrl: "https://api.vlozi.app/forms", // optional, this is the default
});form.getSchema()
Fetches the public form definition. Returns the schema object (field list, settings, CAPTCHA config).
const schema = await form.getSchema();
// { id, name, schema: { fields }, successMessage, captchaRequired, settings }form.submit(data)
Submits the form. Accepts a plain object or a FormData instance.
// JSON submission
const result = await form.submit({
email: "visitor@example.com",
message: "Hello!",
});
// File upload — use FormData
const fd = new FormData();
fd.append("email", "visitor@example.com");
fd.append("resume", fileInput.files[0]);
const result = await form.submit(fd);submit() never throws on 4xx/5xx — it always returns a SubmitResult. Only network-level errors throw.
SubmitResult shape:
// Success
{ success: true, message: "Thank you!", data: { email: "...", ... } }
// Validation failure
{
success: false,
message: "Validation failed",
errors: {
fieldErrors: { email: ["Invalid email address"] },
formErrors: []
}
}
// Other failure (402, 429, etc.)
{ success: false, message: "Out of credits" }React component
import { VloziForm } from "@vlozi/forms/react";
export function ContactSection() {
return (
<VloziForm
formId="YOUR_FORM_ID"
onSuccess={(result) => console.log("Submitted:", result)}
onError={(message) => console.error("Failed:", message)}
className="my-contact-form"
/>
);
}The <VloziForm> component:
- Fetches the schema on mount and shows a loading state
- Renders fields dynamically (respects field order, labels, required markers)
- Shows inline validation errors from the server
- Handles Turnstile CAPTCHA if the form has it enabled
- Handles file uploads automatically
NOTE
The React subpackage (@vlozi/forms/react) is built and available in the monorepo but has not been published to npm as a separate package yet. If you are using it from the Vlozi platform directly, import from the workspace alias @vlozi/forms/react.
TypeScript types
interface FormField {
name: string;
type: "text" | "email" | "tel" | "url" | "number" | "textarea"
| "select" | "checkbox" | "date" | "file";
label?: string;
placeholder?: string;
help?: string;
required?: boolean;
min?: number;
max?: number;
pattern?: string;
options?: string[]; // select fields only
}
interface SubmitResult {
success: boolean;
message: string;
data?: Record<string, unknown>;
errors?: {
fieldErrors: Record<string, string[]>;
formErrors: string[];
};
}Redirect handling
Both createFormClient and <VloziForm> follow server-issued 3xx redirects transparently. A redirect is treated as a successful submission and the resolved body is returned. This matches the behavior of the hosted page and embed.js.
Next.js example (Server Component fetch + client form)
// app/contact/page.tsx
import { createFormClient } from "@vlozi/forms";
export default async function ContactPage() {
const form = createFormClient({ formId: "YOUR_FORM_ID" });
const schema = await form.getSchema(); // runs on the server
return <ContactForm schema={schema} formId="YOUR_FORM_ID" />;
}// components/contact-form.tsx — "use client"
"use client";
import { createFormClient } from "@vlozi/forms";
import { useState } from "react";
export function ContactForm({ schema, formId }) {
const [result, setResult] = useState(null);
const form = createFormClient({ formId });
async function handleSubmit(e) {
e.preventDefault();
const data = Object.fromEntries(new FormData(e.target));
const res = await form.submit(data);
setResult(res);
}
if (result?.success) return <p>{result.message}</p>;
return (
<form onSubmit={handleSubmit}>
{schema.fields.map((field) => (
<input key={field.name} name={field.name} type={field.type} required={field.required} />
))}
<button type="submit">Send</button>
</form>
);
}