npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@forms.expert/sdk

v0.4.10

Published

Forms SDK for submitting and rendering forms

Readme

Forms Expert SDK

Embeddable forms SDK for submitting forms via the Forms Expert API.

Installation

npm install @forms.expert/sdk

Quick 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