Core JavaScript SDK for interacting with SwellForms from browsers and Node. It provides a tiny class for managing form state and a couple of convenience helpers for one-off calls.
-
Works in ESM and CommonJS environments
-
First-class TypeScript types
-
Pluggable fetch for Node or custom transports
⚠ This package is under active development and is not yet stable. Use at your own risk.
⚠ This package is not needed if you are using the embeddable script or iFrame from SwellForms. It is intended for use in custom applications where you need to manage form state or submit forms programmatically.
- TBC: Links to other packages such as Nuxt.js, React, etc. embeddable script docs ...
npm i @swellforms/js
# or
pnpm add @swellforms/js
# or
yarn add @swellforms/jsThe package ships a dual build.
import SwellForm, { submitForm, validateForm } from "@swellforms/js";const { submitForm, validateForm } = require("@swellforms/js");
const SwellForm = require("@swellforms/js").default; // default exportSubmit a form in one call:
import { submitForm } from "@swellforms/js";
const res = await submitForm({
formId: "sf_aZx90qLp",
fields: {
fullName: "Jane Doe",
emailAddress: "jane@example.com",
message: "Hello from the SDK",
},
});
if (res.ok) {
console.log("Submitted", res.status, res.data);
} else {
console.log("Validation errors", res.errors);
}Validate before submit:
import { validateForm } from "@swellforms/js";
const vr = await validateForm({
formId: "sf_aZx90qLp",
fields: { emailAddress: "not-an-email" },
});
if (!vr.valid) {
console.log(vr.errors); // { emailAddress: ["The email must be a valid email address."] }
}SwellForm helps you manage fields and errors between requests.
import SwellForm from "@swellforms/js";
const form = new SwellForm("sf_aZx90qLp", { emailAddress: "" });
form.setField("emailAddress", "jane@example.com");
// Validate one field
await form.validateField("emailAddress");
if (form.isValid()) {
const result = await form.submit();
if (result.ok) {
console.log("Success", result.data);
} else {
console.log("Form errors", result.errors);
}
}For cases where you want to render a form without hardcoding the HTML, you can fetch the field definitions directly from the API and build your UI dynamically.
import SwellForm from "@swellforms/js";
const formContainer = document.getElementById("form-container");
const form = new SwellForm("sf_aZx90qLp");
async function buildDynamicForm() {
try {
// 1. Fetch the field definitions from the API
const fields = await form.fetchFields();
// 2. Loop through the definitions and create HTML elements
fields.forEach(field => {
const label = document.createElement("label");
label.textContent = field.label || field.name;
label.htmlFor = field.name;
let inputElement;
// Handle different field types
if (field.type === 'textarea') {
inputElement = document.createElement('textarea');
} else if (field.type === 'select') {
inputElement = document.createElement('select');
field.options?.forEach(option => {
const opt = document.createElement('option');
opt.value = option.value;
opt.textContent = option.label;
inputElement.appendChild(opt);
});
} else {
inputElement = document.createElement('input');
inputElement.type = field.type;
}
// Assign common attributes
inputElement.id = field.name;
inputElement.name = field.name;
inputElement.placeholder = field.placeholder || '';
inputElement.required = field.required;
// Add the elements to the container
formContainer.appendChild(label);
formContainer.appendChild(inputElement);
formContainer.appendChild(document.createElement('br')); // for spacing
});
// Don't forget to add a submit button
const submitButton = document.createElement('button');
submitButton.type = 'submit';
submitButton.textContent = 'Submit';
formContainer.appendChild(submitButton);
} catch (error) {
console.error("Failed to build form:", error);
formContainer.textContent = "Sorry, the form could not be loaded.";
}
}
buildDynamicForm();
// You would then add a submit handler to the formContainer
// to collect the values and call form.submit()<!DOCTYPE html>
<html lang="en">
<body>
<form id="contact-form">
<input type="text" id="fullName" placeholder="Full Name" />
<input type="email" id="emailAddress" placeholder="Email" />
<textarea id="message" placeholder="Message"></textarea>
<button type="submit">Send</button>
</form>
<div id="errors"></div>
<script type="module">
import SwellForm from "https://cdn.skypack.dev/@swellforms/js";
const form = new SwellForm("sf_aZx90qLp");
const contactForm = document.getElementById("contact-form");
const errorBox = document.getElementById("errors");
contactForm.addEventListener("submit", async e => {
e.preventDefault();
form.setFields({
fullName: document.getElementById("fullName").value,
emailAddress: document.getElementById("emailAddress").value,
message: document.getElementById("message").value,
});
const result = await form.submit();
if (result.ok) {
alert("Form submitted successfully!");
} else {
errorBox.innerHTML = JSON.stringify(result.errors, null, 2);
}
});
</script>
</body>
</html>Json— serializable JSON primitive or structure.SubmitResult<T>{ ok: true; status: number; data: T }{ ok: false; status: 422; errors: Record<string, string[]>; data?: any }
ValidateResult{ valid: true; message?: string }{ valid: false; errors: Record<string, string[]>; message?: string }
FieldsResponse—{ formId: string; fields: FormField[] }.SwellformsErrorextendsErrorwith properties:status: number,code?: string,errors?: Record<string, string[]>.
Creates a form model bound to a SwellForms form.
setField(id: string, value: any)— set a single field.setFields(map: Record<string, any>)— shallow-merge many fields.
getField<T = any>(id: string): T | undefinedgetFields(): Record<string, any>— returns a shallow copy.
isProcessing(): boolean— true while a network call is active.isValid(): boolean— true when there are no form errors.isValid(fieldId: string): boolean— true when the field has no error.hasError(fieldId: string): booleangetFieldError(fieldId: string): string | undefined— first error message for the field.getFormErrors(): Record<string, string[]>— a copy of the current error bag.hasFormErrors(): boolean
validate(opts?: { only?: string[] }, fetchImpl?: typeof fetch): Promise<ValidateResult>- Sends
POST /api/v1/forms/:formId/validatewith the current fields. - On
200with{ valid: true }it clears errors and returns{ valid: true }. - On
422it normalizes and stores validation errors and returns{ valid: false, errors }.
- Sends
validateField(fieldId: string, fetchImpl?: typeof fetch): Promise<ValidateResult>- Shorthand for
validate({ only: [fieldId] }).
- Shorthand for
submit<T = any>(overrides?: { fields?: Record<string, Json> }, fetchImpl?: typeof fetch): Promise<SubmitResult<T>>- Sends
POST /api/v1/forms/:formId/submit. - On
422returns{ ok: false, status: 422, errors }and stores errors. - On
2xxreturns{ ok: true, status, data }and clears errors. - On
409,429, or5xxthrowsSwellformsErrorwith acodeofCONFLICT,RATE_LIMITED, orSERVER.
- Sends
fetchFields(fetchImpl?: typeof fetch): Promise<FieldsResponse>- Requests the form definition from
GET /api/v1/forms/:formId/fields. - On
404throwsSwellformsError("Form not found", 404, "NOT_FOUND"). - On
401or403throwsSwellformsError("Unauthorized", ...).
- Requests the form definition from
- The SDK serializes values through a safe converter:
Datebecomes ISO string viatoISOString().- Arrays and plain objects are mapped recursively.
- Vue refs are unwrapped if they look like
{ value: any }. undefinedis omitted from payloads.
- Each request includes basic metadata:
originUrl—window.location.hostwhen available, else empty string.fullUrl—window.location.hrefwhen available, else empty string.
Server validation errors are normalized into Record<string, string[]>.
If a key starts with fields., the prefix is stripped. For example:
{
"errors": {
"fields.emailAddress": ["The email must be a valid email address."]
}
}
becomes:
{
"emailAddress": ["The email must be a valid email address."]
}
submitForm<T>(payload: { formId: string; fields?: Record<string, Json> }, fetchImpl?: typeof fetch): Promise<SubmitResult<T>>
Creates a temporary SwellForm under the hood and calls submit. Useful for simple flows.
validateForm(payload: { formId: string; fields?: Record<string, Json>; only?: string[] }, fetchImpl?: typeof fetch): Promise<ValidateResult>
Creates a temporary SwellForm and calls validate.
If your runtime does not have fetch:
const fetch = require("node-fetch");
const { submitForm } = require("@swellforms/js");
submitForm({ formId: "sf_abc", fields: { emailAddress: "x@y.z" } }, fetch)
.then(r => console.log(r))
.catch(err => console.error(err));You can pass fetchImpl to validate, validateField, submit, and fetchFields.
All requests use an internal AbortController with a 15 second timeout. On timeout the SDK throws SwellformsError with code = "TIMEOUT" and status = 0.
The SDK targets these SwellForms endpoints by default:
POST /api/v1/forms/:formId/validatePOST /api/v1/forms/:formId/submitGET /api/v1/forms/:formId/fields
All requests send and expect JSON.
- Cannot require ESM: Use
require('@swellforms/js')only if your bundler or Node resolves the CJS entry. This package publishes CJS atdist/index.cjsand maps it viaexports.require. - No fetch in Node: Provide a fetch implementation using the optional
fetchImplparameter. - Validation errors keep showing: Call
isValid()or inspectgetFormErrors(). Errors are cleared on successfulvalidate(200) or successfulsubmit(2xx). - Cross-origin blocks: The SDK includes
originUrlandfullUrlin the body to help with origin checks. Make sure your backend CORS and origin rules allow your site.
TBC - Link to website documentation
TBC**
MIT License © 2025 Keith Mifsud and SwellAI Ltd. All rights reserved.
For any questions or issues related to SwellForms, please contact us at Support.
For issues related to this SDK, please open an issue on the GitHub repository