@forms.expert/sdk
v0.4.10
Published
Forms SDK for submitting and rendering forms
Maintainers
Readme
Forms Expert SDK
Embeddable forms SDK for submitting forms via the Forms Expert API.
Installation
npm install @forms.expert/sdkQuick Start
Vanilla JavaScript (CDN)
<!-- Include the SDK -->
<script src="https://unpkg.com/@forms.expert/sdk@latest/dist/vanilla/index.global.js"></script>
<!-- Create a container with data attributes -->
<div
data-forms-expert="contact"
data-api-key="pk_live_xxxxxxxxxxxx"
data-resource-id="your-resource-id"
data-lang="en"
></div>
<!-- Forms will auto-initialize on page load -->Vanilla JavaScript (Module)
import { FormWidget } from '@forms.expert/sdk/vanilla';
const widget = new FormWidget(
{
apiKey: 'pk_live_xxxxxxxxxxxx',
resourceId: 'your-resource-id',
},
{
target: '#my-form',
slug: 'contact',
lang: 'en',
onSuccess: (response) => {
console.log('Form submitted:', response.submissionId);
},
onError: (error) => {
console.error('Submission failed:', error);
},
}
);
widget.init();React
import { FormsExpertForm } from '@forms.expert/sdk/react';
function ContactPage() {
return (
<FormsExpertForm
config={{
apiKey: 'pk_live_xxxxxxxxxxxx',
resourceId: 'your-resource-id',
}}
slug="contact"
lang="en"
submitText="Send Message"
onSuccess={(response) => {
console.log('Submitted:', response.submissionId);
}}
/>
);
}React Hook
import { useForm, FormsProvider } from '@forms.expert/sdk/react';
// With provider
function App() {
return (
<FormsProvider
config={{
apiKey: 'pk_live_xxxxxxxxxxxx',
resourceId: 'your-resource-id',
}}
>
<ContactForm />
</FormsProvider>
);
}
function ContactForm() {
const form = useForm({
slug: 'contact',
lang: 'en',
onSuccess: (response) => alert('Thanks!'),
});
const handleSubmit = async (e) => {
e.preventDefault();
await form.submit();
};
if (form.isSubmitted) {
return <p>Thank you for your submission!</p>;
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={form.values.email || ''}
onChange={(e) => form.setValue('email', e.target.value)}
/>
{form.errors.email && <span>{form.errors.email}</span>}
<textarea
value={form.values.message || ''}
onChange={(e) => form.setValue('message', e.target.value)}
/>
{form.errors.message && <span>{form.errors.message}</span>}
<button type="submit" disabled={form.isLoading}>
{form.isLoading ? 'Sending...' : 'Submit'}
</button>
</form>
);
}Vue 3
<script setup>
import { useForm } from '@forms.expert/sdk/vue';
const form = useForm({
slug: 'contact',
lang: 'en',
config: {
apiKey: 'pk_live_xxxxxxxxxxxx',
resourceId: 'your-resource-id',
},
onSuccess: (response) => {
alert('Form submitted!');
},
});
const handleSubmit = async () => {
await form.submit();
};
</script>
<template>
<form @submit.prevent="handleSubmit">
<div v-if="form.isSubmitted.value">
Thank you for your submission!
</div>
<template v-else>
<input
type="email"
:value="form.values.value.email"
@input="form.setValue('email', $event.target.value)"
/>
<span v-if="form.errors.value.email">{{ form.errors.value.email }}</span>
<button type="submit" :disabled="form.isLoading.value">
{{ form.isLoading.value ? 'Sending...' : 'Submit' }}
</button>
</template>
</form>
</template>Core SDK
For programmatic form submission without UI:
import { FormsSDK } from '@forms.expert/sdk';
const sdk = new FormsSDK({
apiKey: 'pk_live_xxxxxxxxxxxx',
resourceId: 'your-resource-id',
});
// Check if form is active (optionally pass language code)
const status = await sdk.isActive('contact', 'en');
if (!status.active) {
console.error('Form not available');
}
// Validate data
const validation = await sdk.validate('contact', {
email: '[email protected]',
message: 'Hello!',
});
if (!validation.valid) {
console.error('Validation errors:', validation.errors);
}
// Submit form
const response = await sdk.submit('contact', {
email: '[email protected]',
message: 'Hello!',
});
console.log('Submission ID:', response.submissionId);Form Handler
For more control, use the FormHandler:
const handler = sdk.form('contact', {
onSubmitStart: () => console.log('Submitting...'),
onSubmitSuccess: (response) => console.log('Success!', response),
onSubmitError: (error) => console.error('Error:', error),
onValidationError: (errors) => console.error('Validation:', errors),
});
// Initialize to get form config
await handler.initialize();
// Check captcha requirement
if (handler.requiresCaptcha()) {
const provider = handler.getCaptchaProvider(); // 'turnstile' | 'recaptcha' | 'hcaptcha'
// Initialize captcha widget...
}
// Submit with validation
await handler.submit({ email: '[email protected]' });Configuration
SDK Config
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| apiKey | string | Yes | Publishable API key (pk_live_* or pk_test_*) |
| resourceId | string | Yes | Resource ID containing the form |
| baseUrl | string | No | API base URL (default: https://api.forms.expert/api/v1) |
Widget Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| target | string \| HTMLElement | - | Target element or selector |
| slug | string | - | Form slug |
| submitText | string | 'Submit' | Submit button text |
| showBranding | boolean | true | Show "Powered by forms.expert" branding |
| resetOnSuccess | boolean | false | Reset form after successful submission |
| lang | string | - | Language code (e.g. 'en', 'uk') passed as ?lang=CODE to the backend |
| redirectUrl | string | - | Redirect URL after success |
| onSuccess | function | - | Success callback |
| onError | function | - | Error callback |
| onValidationError | function | - | Validation error callback |
Data Attributes (Auto-init)
| Attribute | Required | Description |
|-----------|----------|-------------|
| data-forms-expert | Yes | Form slug |
| data-api-key | Yes | Publishable API key |
| data-resource-id | Yes | Resource ID |
| data-base-url | No | Custom API base URL |
| data-submit-text | No | Submit button text |
| data-branding | No | Set to 'false' to hide branding |
| data-lang | No | Language code (e.g. en, uk) |
| data-reset | No | Set to 'true' to reset after submission |
Styling
The SDK automatically reads styling from the form configuration set in the builder. Styling is merged from two sources: schema.styling (basic) and the top-level styling object (full hosted config). Top-level values take precedence.
Styling Properties
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| theme | 'light' \| 'dark' \| 'system' | 'light' | Color theme |
| primaryColor | string | '#3b82f6' | Primary accent color (button background) |
| buttonColor | string | '#ffffff' | Button text color |
| backgroundColor | string | '#ffffff' | Form background color |
| textColor | string | '#1f2937' | Text color |
| errorColor | string | '#EF4444' | Validation error color |
| successColor | string | '#22c55e' | Success state color (badge, messages) |
| fontFamily | string | 'Inter, system-ui, sans-serif' | Font family |
| fontSize | 'sm' \| 'md' \| 'lg' | 'md' | Base font size |
| buttonText | string | 'Submit' | Submit button label |
| buttonStyle | 'filled' \| 'outline' | 'filled' | Button fill style |
| labelPosition | 'top' \| 'left' \| 'floating' | 'top' | Label placement |
| hideRequiredAsterisk | boolean | false | Hide * on required fields |
| transparentBackground | boolean | false | Transparent form background |
Border Radius
Field border radius (borderRadius) — applied to inputs, selects, textareas:
| Value | Pixels | Description |
|-------|--------|-------------|
| 'none' | 0 | No rounding |
| 'sm' | 2px | Subtle rounding |
| 'md' | 6px | Medium rounding (default) |
| 'lg' | 8px | Large rounding |
Button border radius (buttonRadius) — applied to the submit button independently:
| Value | Pixels | Description |
|-------|--------|-------------|
| 'none' | 0 | Square corners |
| 'small' | 4px | Subtle rounding |
| 'medium' | 8px | Medium rounding (default) |
| 'large' | 12px | Large rounding |
| 'full' | 9999px | Fully rounded / pill shape |
Layout & Spacing
| Property | Values | Default | Description |
|----------|--------|---------|-------------|
| formWidth | 'narrow' \| 'medium' \| 'wide' \| 'full' | 'full' | Form container width |
| formPadding | 'compact' \| 'normal' \| 'relaxed' \| 'spacious' | 'normal' | Inner form padding |
| fieldLayout | 'stacked' \| 'inline' | 'stacked' | Field arrangement |
| fieldSpacing | 'compact' \| 'normal' \| 'relaxed' \| 'spacious' | 'normal' | Vertical gap between fields |
| labelSpacing | 'compact' \| 'normal' \| 'relaxed' | 'normal' | Gap between label and input |
| buttonAlign | 'left' \| 'center' \| 'right' | 'left' | Submit button alignment |
Layout Fields (Heading & Paragraph)
Heading size (headingSize) — controls <h2> heading fields:
| Value | Font Size | Description |
|-------|-----------|-------------|
| 'small' | 18px | Small heading |
| 'medium' | 22px | Medium heading (default) |
| 'large' | 28px | Large heading |
| 'extra-large' | 34px | Extra-large heading |
Paragraph size (paragraphSize) — controls paragraph layout fields:
| Value | Font Size | Description |
|-------|-----------|-------------|
| 'small' | 12px | Small text |
| 'medium' | 14px | Medium text (default) |
| 'large' | 16px | Large text |
Individual paragraph fields can override the global size via the paragraphFontSize field property (in pixels).
Form Name Display
The form name is rendered as an <h1> above the form fields. Visibility is controlled by hostedConfig.showFormName (defaults to true). This can be toggled in the form builder under Page Settings → "Show form name as title".
Logo & Background
| Property | Type | Description |
|----------|------|-------------|
| logoUrl | string | URL for a logo image above the form |
| logoPosition | 'top-left' \| 'top-center' \| 'top-right' | Logo alignment |
| coverImageUrl | string | Cover image URL |
| backgroundImageUrl | string | Background image URL |
| backgroundOverlay | number | Background overlay opacity (0–100) |
Error Handling
import { FormsError, FormValidationError } from '@forms.expert/sdk';
try {
await sdk.submit('contact', data);
} catch (error) {
if (error instanceof FormValidationError) {
// Handle validation errors
error.errors.forEach(({ field, message }) => {
console.log(`${field}: ${message}`);
});
} else if (error instanceof FormsError) {
// Handle API errors
console.error(`${error.code}: ${error.message}`);
if (error.code === 'FORM_RATE_LIMIT_EXCEEDED') {
// Retry after delay
setTimeout(() => retry(), error.retryAfter * 1000);
}
}
}Error Codes
| Code | Description |
|------|-------------|
| FORM_NOT_FOUND | Form does not exist |
| FORM_NOT_PUBLISHED | Form is not published |
| VALIDATION_ERROR | Form data validation failed |
| CAPTCHA_REQUIRED | CAPTCHA token missing |
| CAPTCHA_FAILED | CAPTCHA verification failed |
| FORM_RATE_LIMIT_EXCEEDED | Form-specific rate limit exceeded |
| GLOBAL_RATE_LIMIT_EXCEEDED | IP rate limit exceeded |
| ORIGIN_NOT_ALLOWED | Request origin not in whitelist |
TypeScript
Full TypeScript support with exported types:
import type {
FormField,
FormSchema,
FormStyling,
FormStatusResponse,
ValidationResponse,
SubmissionResponse,
FormsSDKConfig,
} from '@forms.expert/sdk';License
MIT
Website: forms.expert
