logicspike/docs

Forms

Forms Service — Embedding & SDK

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

Features:

  • Themed card (max-width 520px) using settings.theme.accent and settings.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.js for 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

  1. Scans the page for [data-vlozi-form] elements (or uses data-form on the script tag itself)
  2. Fetches the form schema from GET /f/:form_id/schema (unless window.__VLOZI_FORM__ is already set — the hosted page pre-inlines it to skip this round-trip)
  3. Dynamically renders all fields based on schema.fields + settings theme
  4. Injects Cloudflare Turnstile widget if captchaRequired=true and settings.turnstileSiteKey is set
  5. Submits as multipart/form-data when file inputs are present, otherwise as JSON
  6. On 3xx response from the server: treats it as success (Web3Forms redirect parity)
  7. Displays settings.successMessage on success or settings.redirectUrl redirect
  8. Shows field-level validation errors from the server on failure
  9. 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/forms

Usage

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.

Forms