Last Updated: 2026-06-28 Status: Active Companion docs: api-spec.md · architecture.md
Three surfaces for collecting form submissions: a hosted page, a script embed widget, and the @vlozi/forms SDK.
1. Hosted Page
Every form has a fully themed, standalone HTML page at:
https://api.vlozi.app/forms/f/:form_id/pageFeatures:
- Themed card (max-width 520px) using
settings.theme.accentandsettings.theme.buttonLabel - Includes
<meta name="robots" content="noindex">— search engines won't index it - "Powered by Vlozi" footer
- Inlines the form schema as
window.__VLOZI_FORM__to avoid a race-condition fetch - Loads
../embed.jsfor field rendering and submission
Use when: you want to link to a form rather than embed it. Share the URL directly in emails, social posts, or as a landing page.
Returns 404 if the form is deleted, archived, or paused.
2. Script Embed Widget (embed.js)
A self-contained ~180-line vanilla JS widget with no dependencies. Serve it from vlozi's CDN or load it from the forms service directly.
<!-- Option A: auto-mount via data attribute -->
<div data-vlozi-form="form_abc123"></div>
<script src="https://api.vlozi.app/forms/f/embed.js" async></script>
<!-- Option B: load script with data-form attribute (mounts to next element or a target) -->
<script
src="https://api.vlozi.app/forms/f/embed.js"
data-form="form_abc123"
async
></script>How it works
- Scans the page for
[data-vlozi-form]elements (or usesdata-formon the script tag itself) - Fetches the form schema from
GET /f/:form_id/schema(unlesswindow.__VLOZI_FORM__is already set — the hosted page pre-inlines it to skip this round-trip) - Dynamically renders all fields based on
schema.fields+ settings theme - Injects Cloudflare Turnstile widget if
captchaRequired=trueandsettings.turnstileSiteKeyis set - Submits as
multipart/form-datawhen file inputs are present, otherwise as JSON - On 3xx response from the server: treats it as success (Web3Forms redirect parity)
- Displays
settings.successMessageon success orsettings.redirectUrlredirect - Shows field-level validation errors from the server on failure
- Re-enables the submit button after any failure so the user can retry
Turnstile caveat
IMPORTANT
If a form has captchaRequired=true but settings.turnstileSiteKey is not set, the Turnstile widget is silently not rendered and the form submits without a captcha token. The server-side Turnstile check is still enforced, so submissions without a token will return 400. Set both captchaRequired=true and a valid turnstileSiteKey for captcha protection to work end-to-end.
CORS
The submit endpoint (POST /f/:form_id) is served with any-origin CORS by the gateway. Per-form allowedOrigins is an additional server-side check: if non-empty, the request Origin must match one of the listed values.
3. @vlozi/forms SDK
A zero-dependency TypeScript SDK for server-safe form interactions.
Installation
npm install @vlozi/forms
# or
pnpm add @vlozi/formsUsage
import { createFormClient } from "@vlozi/forms";
const form = createFormClient({
formId: "form_abc123",
baseUrl: "https://api.vlozi.app/forms", // optional, this is the default
});
// Fetch the public form schema
const schema = await form.getSchema();
// { id, name, schema: { fields }, successMessage, captchaRequired, settings }
// Submit data (JSON)
const result = await form.submit({ email: "a@b.com", message: "hi" });
// { success: true, message: "Thanks!", data: { email, message } }
// or: { success: false, message: "Validation failed", errors: { fieldErrors, formErrors } }
// Submit with file upload (pass FormData)
const formData = new FormData();
formData.append("email", "a@b.com");
formData.append("resume", fileInput.files[0]);
const result = await form.submit(formData);React subpackage
import { useVloziForm } from "@vlozi/forms/react";
const { submit, schema, isLoading, error } = useVloziForm({ formId: "form_abc123" });NOTE
The React subpackage exists in the package but has not been published to npm. It is available for internal use from the monorepo via the workspace alias @vlozi/forms.
Redirect handling
The SDK follows 3xx responses transparently — a redirect is treated as a successful submission and the resolved body is returned. This matches Web3Forms behavior.
Error handling
submit() never throws on 4xx/5xx — it always returns { success: false, message, errors? }. Only network errors throw.
4. Field Types
All three embedding surfaces support the same field types defined in the form schema:
| Type | HTML element | Notes |
|---|---|---|
text |
<input type="text"> |
Optional pattern (regex), min/max (length) |
email |
<input type="email"> |
Built-in email format validation |
tel |
<input type="tel"> |
Optional pattern |
url |
<input type="url"> |
Built-in URL format validation |
number |
<input type="number"> |
min/max (numeric range) |
textarea |
<textarea> |
min/max (length) |
select |
<select> |
Requires options: string[] |
checkbox |
<input type="checkbox"> |
Value is "true"/"false" string |
date |
<input type="date"> |
ISO 8601 date string |
file |
<input type="file"> |
Max 5 files per submission, 25 MB each. Stored via Media Service. |
Fields marked required: true fail submission if empty. File fields: only presence is checked (not size/type) by the form schema; size and type limits are enforced by the Media Service upload.
5. Plain HTML Form (no JS)
The forms service is fully compatible with plain <form> tags and no JavaScript:
<form action="https://api.vlozi.app/forms/f/form_abc123" method="POST">
<label>Email <input type="email" name="email" required /></label>
<label>Message <textarea name="message" required></textarea></label>
<!-- Honeypot: always include, keep hidden from humans -->
<input type="checkbox" name="botcheck" style="display:none" tabindex="-1" autocomplete="off" />
<!-- Optional: redirect after success -->
<input type="hidden" name="redirect" value="https://yoursite.com/thank-you" />
<button type="submit">Send</button>
</form>On success, the server issues a 303 redirect to the redirect field value (or settings.redirectUrl if set on the form). If neither is set, the browser receives 200 with the JSON body — not ideal for plain HTML; always set a redirect for vanilla form posts.