@formerie/client
JavaScript and TypeScript helpers for the Formerie public API and public runtime.
The package builds URLs for public API routes, clean runtime routes, hosted pages, and embed scripts. It submits form payloads, validates partial form data, fetches public schemas, maps API error codes to default messages, and exposes helpers for multiple validation issues per field.
Packages
This repository contains the public Formerie JavaScript SDK packages:
@formerie/client: framework-neutral JavaScript and TypeScript client.@formerie/client/element: the<formerie-form>custom element, for any framework or plain HTML.@formerie/react: React hooks.@formerie/vue: Vue composables.@formerie/svelte: Svelte stores, and@formerie/svelte/kitfor SvelteKit form actions.@formerie/solid: Solid primitives.@formerie/angular: Angular service and injection token.@formerie/astro: Astro helpers for Formerie form attributes and client setup.@formerie/next: Next.js server actions and React hooks.@formerie/nuxt: Nuxt server handlers and Vue composables.@formerie/react-router: React Router route actions and React hooks.
Framework packages are thin adapters over @formerie/client. They do not duplicate request routing, response parsing, validation error handling, or API security behavior.
Target objects point at a form by its ID: { form: "fm_t77txbvg43nmar" }. To send through your own hostname, add the endpoint option and keep the same ID.
Framework Entry Points
| Project type | Submit from the browser | Submit from your server |
|---|---|---|
| Plain HTML or browser JavaScript | @formerie/client/element or @formerie/client | Direct HTTP API or @formerie/client |
| React | @formerie/react | submitFormerieRequest from @formerie/client |
| Next.js | @formerie/next in client components | createFormerieNextAction from @formerie/next |
| React Router | @formerie/react-router hooks | createFormerieReactRouterAction from @formerie/react-router |
| Vue | @formerie/vue | submitFormerieRequest from @formerie/client |
| Nuxt | @formerie/nuxt in components | createFormerieNuxtSubmitHandler from @formerie/nuxt |
| Svelte, SvelteKit | @formerie/svelte | createFormerieAction from @formerie/svelte/kit |
| Solid, SolidStart | @formerie/solid | submitFormerieFormData from @formerie/client |
| Angular | @formerie/angular | submitFormerieRequest from @formerie/client |
| Astro | @formerie/astro for attributes, @formerie/client/element in a page | submitFormerieRequest from @formerie/client |
| Qwik, Preact, Lit, Web Components | @formerie/client/element | submitFormerieFormData from @formerie/client |
For frameworks without a dedicated Formerie adapter, use @formerie/client
directly in server routes, actions, browser scripts, or custom UI components.
Custom Element
<formerie-form> renders a form from its public schema, in any framework or plain HTML:
<script type="module">
import "@formerie/client/element";
</script>
<formerie-form form="fm_t77txbvg43nmar"></formerie-form>
The form renders in the light DOM, so your site's CSS styles it, and its submit goes straight from
the browser, so your site must be in the form's allowed origins. It fires formerie:success and
formerie:error, with the response as detail. Add mode="iframe" to show the hosted page instead,
with its own branding and steps and no allowed origins to set.
Without a bundler, load dist/browser/formerie-element.js.
In a React TypeScript project, declare the tag once:
import type { FormerieFormAttributes } from "@formerie/client/element";
declare module "react" {
namespace JSX {
interface IntrinsicElements {
"formerie-form": FormerieFormAttributes;
}
}
}
Submitting From Your Server
submitFormerieRequest() and submitFormerieFormData() send a form your server received. They move
a Turnstile or reCAPTCHA token out of the answers and into verificationToken, and send the page's
Origin with it, because the server compares the token's hostname with Origin. A form with
verification therefore needs your site in its allowed origins. With no token they send no Origin,
so no allowed origins are needed.
Your server sends the request, so every visitor shares your server's address. The default limit of
30 submissions per visitor every 10 minutes then applies to all of them together. Raise it, or use a
deviceToken rate limit and pass metadata: { deviceToken } for each visitor.
With a server key, pass apiKey, from a secret on your server, and visitorIp. They work only on a server: anywhere
with a page they throw and send nothing. See Server keys.
Two Languages On One Form
<FormerieForm form="fm_t77txbvg43nmar" locale="fr-CA" />
The server picks the closest language the form offers. Change locale, or call setLocale() on the controller, and the
visitor's answers stay. On a server, pass locale to whatever draws the form, since a server can't read <html lang>. In
a browser with nothing from a server, the form goes by <html lang>. See
Forms in two languages.
Changelog
See CHANGELOG.md for public release notes across the Formerie JavaScript SDK packages.
Install
npm install @formerie/client
pnpm add @formerie/client
yarn add @formerie/client
bun add @formerie/client
Use the same package-manager command shape for framework adapters:
npm install @formerie/react
npm install @formerie/vue
npm install @formerie/svelte
npm install @formerie/astro
npm install @formerie/next
npm install @formerie/nuxt
npm install @formerie/react-router
API Usage
import { FormerieClient } from "@formerie/client";
const forms = new FormerieClient();
const result = await forms.submit("fm_t77txbvg43nmar", {
fields: {
name: "Jane Example",
email: "[email protected]",
message: "Hello",
},
verificationToken: captchaToken,
});
FormerieClient defaults to https://forms.formerie.com, where a form's address is its ID.
Runtime Usage
Use FormerieClient for hosted runtime routes on forms.formerie.com
or verified customer hostnames.
Default Formerie runtime host:
import { FormerieClient } from "@formerie/client";
const forms = new FormerieClient();
const hostedUrl = forms.hostedUrl("fm_t77txbvg43nmar");
const embedScriptUrl = forms.embedScriptUrl();
await forms.submit("fm_t77txbvg43nmar", {
fields: {
email: "[email protected]",
},
});
Sending through your own verified hostname takes the same ID:
const forms = new FormerieClient({
endpoint: "https://forms.customer.com",
});
await forms.submit("fm_t77txbvg43nmar", {
fields: {
email: "[email protected]",
},
});
Schema And Form Helpers
The SDK includes small runtime helpers for custom renderers, native forms, and framework integrations. These helpers are for display, payload shaping, and error presentation; the Formerie runtime API remains authoritative for visibility enforcement, validation, spam checks, routing, and delivery.
Use describeFormerieRenderModel when you want to build your own renderer from
a public schema while keeping Formerie's public field metadata, visibility, and
step grouping in one place:
import {
describeFormerieRenderModel,
FormerieClient,
} from "@formerie/client";
const forms = new FormerieClient();
const schema = await forms.schema("fm_t77txbvg43nmar");
if (schema.ok) {
const renderModel = describeFormerieRenderModel(schema, {
preferredContactMethod: "email",
});
for (const field of renderModel.fields) {
console.log(field.path, field.control, field.visibility);
}
}
The render model exposes fields, fieldsByPath, public steps, and
unsteppedFields. Field descriptors include safe browser metadata such as
labels, descriptions, placeholders, options, input types, input modes,
constraints, names, current values, and visibility state.
import {
FormerieClient,
createFormerieSubmitPayload,
evaluatePublicFieldVisibility,
listPublicSchemaFields,
validationMessagesByField,
} from "@formerie/client";
const forms = new FormerieClient();
const schema = await forms.schema("fm_t77txbvg43nmar");
if (schema.ok) {
const fields = listPublicSchemaFields(schema);
const visibility = evaluatePublicFieldVisibility(schema, {
preferredContactMethod: "email",
});
}
const formElement = document.querySelector<HTMLFormElement>("form");
if (!formElement) {
throw new Error("Form is not ready.");
}
const payload = createFormerieSubmitPayload(new FormData(formElement), {
schema: schema.ok ? schema : undefined,
verificationToken: captchaToken,
});
const result = await forms.submit("fm_t77txbvg43nmar", payload);
const messages = validationMessagesByField(result);
For a plain browser mount, let the SDK load the public schema and submit through the runtime client:
import {
FormerieClient,
mountFormerieRuntimeForm,
} from "@formerie/client";
const forms = new FormerieClient();
await mountFormerieRuntimeForm("#contact-form", {
client: forms,
target: {
form: "fm_t77txbvg43nmar",
},
resetOnSuccess: true,
});
mountFormerieRuntimeForm loads the public schema for the selected runtime
target, renders public schema fields, preserves public steps and object groups
as fieldsets, keeps public visibility rules in sync as answers change, disables
hidden controls so they stay out of submit payloads, renders public verification
hooks when the schema requires them, loads the browser verification provider
script, shapes submit payloads, disables the submit button while requests are
pending, and displays server validation errors. If your app already has a
schema, use mountFormerieForm directly. If your page already renders a
<form data-formerie-form> shell, use bindFormerieForm to attach the same
submit, validation, status, verification token collection, and visibility
behavior without replacing the existing HTML.
Server-rendered pages that include a public schema JSON script can use
bindFormerieHostedForm() as the one-call version for existing HTML; it reads
<script type="application/json" data-formerie-schema> and binds the rendered
form. Generated hosted shells can call mountFormerieForm() with the same
public schema plus a backend-owned submitUrl. These helpers collect
Turnstile/reCAPTCHA response tokens from browser-safe verification attributes,
reset widgets after submit attempts, and can add a safe pageUrl containing
only the current origin and path (never query parameters or fragments). They
can submit through an explicit submitUrl or a rendered
data-formerie-submit-path attribute. Use syncFormerieFormVisibility when
building a custom renderer around Formerie data attributes. Use
collectFormerieVerificationToken,
applyFormerieVerificationError, and resetFormerieVerificationWidget when a
custom renderer wants to own verification UI directly. Use
renderFormerieVerificationHtml when a custom hosted page needs browser-safe
Turnstile or reCAPTCHA widget markup from the public schema. Generated form
shells accept public render options for heading output, CSS class hooks, and an
optional honeypot field when your server is configured to check one. Pass an
explicit verificationToken provider only when your custom UI does not expose
the standard Formerie verification attributes and response fields. The public
schema includes only browser-safe verification data such as the provider and
site key. Final validation, spam checks, verification, delivery, and acceptance
stay with the Formerie runtime API.
CDN Usage
The package publishes browser bundles in dist/browser. Browser snippets can
load Formerie from immutable Formerie CDN URLs or from npm CDNs such as jsDelivr.
Classic script tag:
<script src="https://cdn.formerie.com/client/v0.1.0-alpha.15/formerie.iife.js"></script>
<script>
const forms = new Formerie.FormerieClient({
endpoint: "https://forms.customer.com",
});
forms.submit("fm_t77txbvg43nmar", {
fields: {
email: "[email protected]",
},
});
</script>
Browser ESM:
<script type="module">
import { FormerieClient } from "https://cdn.formerie.com/client/v0.1.0-alpha.15/formerie.es.js";
const forms = new FormerieClient({
endpoint: "https://forms.customer.com",
});
</script>
Custom iframe mount:
<script src="https://cdn.formerie.com/client/v0.1.0-alpha.15/formerie.iife.js"></script>
<div id="contact-frame"></div>
<script>
Formerie.mountFormerieEmbed({
endpoint: "https://forms.customer.com",
form: "fm_t77txbvg43nmar",
target: "#contact-frame",
});
</script>
Formerie.mountFormerieEmbed(...) reads the same public data-formerie-form,
data-formerie-target, data-formerie-title, and data-formerie-height
attributes used by Formerie runtime embed scripts when it receives a script
element.
Pinned snippets should use immutable versioned paths:
<script src="https://cdn.formerie.com/client/vX.Y.Z/formerie.iife.js"></script>
The /client/latest/ alias is a moving convenience alias for demos and manual
testing. Production pages should pin an immutable versioned path.
npm CDN fallback:
<script src="https://cdn.jsdelivr.net/npm/@formerie/client/dist/browser/formerie.iife.js"></script>
CLI And AI Skills
Installing @formerie/client exposes the formerie CLI command in package-manager scripts and local binary resolution.
pnpm exec formerie ai detect
pnpm exec formerie ai setup
For npm projects:
npm exec -- formerie ai setup
To run the installer without adding the package first:
npx --package @formerie/client formerie ai setup
The CLI can detect supported AI tooling and install the bundled Formerie skill for Codex or Claude Code:
pnpm exec formerie skills install codex
pnpm exec formerie skills install codex --project
pnpm exec formerie skills install codex --path .codex/skills/formerie
pnpm exec formerie skills install claude
pnpm exec formerie skills install claude --project
The installed skill teaches AI coding agents the Formerie security boundary: public clients submit fields, verification tokens, and safe metadata only; recipients, provider credentials, templates, and delivery rules stay server-side.
Framework Examples
import { FormerieForm } from "@formerie/react"; // Also @formerie/next and @formerie/react-router
<FormerieForm form="fm_t77txbvg43nmar" />
<script setup>
import { FormerieForm } from "@formerie/vue"; // Auto-imported with the @formerie/nuxt/module module
</script>
<template><FormerieForm form="fm_t77txbvg43nmar" /></template>
<script>
import { FormerieForm } from "@formerie/svelte";
</script>
<FormerieForm form="fm_t77txbvg43nmar" />
import { FormerieForm } from "@formerie/solid";
<FormerieForm form="fm_t77txbvg43nmar" />
import { FORMERIE_COMPONENTS } from "@formerie/angular";
@Component({ imports: [FORMERIE_COMPONENTS], template: `<formerie-form form="fm_t77txbvg43nmar" />` })
export class ContactPage {}
---
import FormerieForm from "@formerie/astro/FormerieForm.astro";
---
<FormerieForm form="fm_t77txbvg43nmar" />
Each package's README shows replacing a part of the form, and keeping your own markup.
---
import { createFormerieFormAttributes } from "@formerie/astro";
const attrs = createFormerieFormAttributes({
form: "fm_t77txbvg43nmar",
});
---
<form {...attrs}>
<input name="email" type="email" />
</form>
License
MIT.