Embedding

@vlozi/forms SDK

Headless TypeScript SDK for custom React and Next.js form integrations.

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/forms

Headless 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>
  );
}
Forms · EmbeddingEdit on GitHub