SDK

JavaScript SDK

Official TypeScript SDK for TownHall form submissions. Full type safety with React hooks and vanilla JavaScript support.

1

Installation

Choose the package that fits your stack:

React / Next.js

bash
npm install @townhall-gg/react

Vanilla JavaScript / TypeScript

bash
npm install @townhall-gg/core
Package Sizes

@townhall-gg/core is ~3.6KB and @townhall-gg/react is ~1.9KB (minified). Both are tree-shakeable with zero external dependencies.

2

React Usage

Use the useTownHallForm hook for the best developer experience with automatic state management.

Basic Contact Form

tsx
import { useTownHallForm } from '@townhall-gg/react'

function ContactForm() {
  const { submit, isSubmitting, isSuccess, error } = useTownHallForm('YOUR_FORM_ID')

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    const formData = new FormData(e.currentTarget)
    await submit(Object.fromEntries(formData))
  }

  if (isSuccess) {
    return (
      <div className="success">
        <h2>Thank you!</h2>
        <p>Your message has been sent.</p>
      </div>
    )
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" placeholder="Your name" required />
      <input name="email" type="email" placeholder="Email" required />
      <textarea name="message" placeholder="Message" required />
      
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Sending...' : 'Send Message'}
      </button>
      
      {error && <p className="error">{error.message}</p>}
    </form>
  )
}

With Reset Functionality

Allow users to submit multiple responses:

tsx
function NewsletterForm() {
  const { submit, isSubmitting, isSuccess, error, reset } = useTownHallForm('FORM_ID')

  if (isSuccess) {
    return (
      <div>
        <p>You're subscribed!</p>
        <button onClick={reset}>Subscribe another email</button>
      </div>
    )
  }

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    const formData = new FormData(e.currentTarget)
    await submit(Object.fromEntries(formData))
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" placeholder="Enter your email" required />
      <button disabled={isSubmitting}>Subscribe</button>
      {error && <span>{error.message}</span>}
    </form>
  )
}
3

Vanilla JavaScript Usage

Use @townhall-gg/core directly for non-React projects or server-side code.

Creating a Client

ts
import { createClient } from '@townhall-gg/core'

const client = createClient('YOUR_FORM_ID')

// Submit with result pattern (recommended)
const result = await client.submit({
  name: 'John Doe',
  email: 'john@example.com',
  message: 'Hello from TownHall!'
})

if (result.success) {
  console.log('Submission ID:', result.data.id)
  console.log('Message:', result.data.message)
} else {
  console.error('Error:', result.error.message)
}

One-off Submission

ts
import { submit } from '@townhall-gg/core'

// Quick one-liner for simple use cases
const result = await submit('YOUR_FORM_ID', {
  email: 'user@example.com',
  message: 'Quick submission'
})

Try/Catch Pattern

ts
import { createClient, TownHallError } from '@townhall-gg/core'

const client = createClient('YOUR_FORM_ID')

try {
  const response = await client.submitOrThrow({
    email: 'user@example.com'
  })
  console.log('Success!', response.id)
} catch (error) {
  if (error instanceof TownHallError) {
    if (error.isRateLimited) {
      console.log('Too many requests, please wait')
    } else if (error.isNotFound) {
      console.log('Form not found')
    } else {
      console.log('Error:', error.message)
    }
  }
}

Error Handling

The SDK provides typed errors with helpful properties to handle different failure scenarios:

PropertyTypeDescription
error.statusnumberHTTP status code
error.codestringError code (e.g., "RATE_LIMITED", "NOT_FOUND")
error.isRateLimitedbooleanTrue if rate limited (429)
error.isNotFoundbooleanTrue if form not found (404)
error.isFormInactivebooleanTrue if form is disabled
error.isValidationErrorbooleanTrue if validation failed (400)
tsx
// React error handling
const { error } = useTownHallForm('form-id')

if (error) {
  if (error.isRateLimited) {
    return <p>Too many submissions. Please wait a moment.</p>
  }
  if (error.isFormInactive) {
    return <p>This form is no longer accepting submissions.</p>
  }
  return <p>Error: {error.message}</p>
}

TypeScript Support

Full TypeScript support with exported types:

ts
import type {
  TownHallResponse,
  TownHallError,
  TownHallConfig,
  FormData,
  SubmitResult,
} from '@townhall-gg/core'

// Response type
interface TownHallResponse {
  success: true
  message: string      // Success message (customizable in dashboard)
  id: string           // Submission ID
  emails: {
    notifications: { enabled: boolean; count: number }
    autoReply: { enabled: boolean; willSend: boolean }
  }
  warning?: string     // Plan limit warning if applicable
}

Configuration

Customize the client behavior:

ts
// Vanilla JS
const client = createClient('YOUR_FORM_ID', {
  baseUrl: 'https://your-custom-domain.com', // Self-hosted TownHall
  timeout: 10000, // 10 second timeout (default: 30000)
})

// React - using provider for global config
import { TownHallProvider } from '@townhall-gg/react'

function App() {
  return (
    <TownHallProvider 
      baseUrl="https://your-domain.com"
      timeout={15000}
    >
      <YourApp />
    </TownHallProvider>
  )
}

API Reference

useTownHallForm(formId, config?)

React hook for form submissions.

Return ValueTypeDescription
isSubmittingbooleanTrue while submission is in progress
isSuccessbooleanTrue after successful submission
errorTownHallError | nullError from last submission
dataTownHallResponse | nullResponse data on success
submit(data) => PromiseSubmit form data
reset() => voidReset form state

createClient(formId, config?)

Create a TownHall client instance.

ParameterTypeDescription
formIdstringYour TownHall form ID
config.baseUrlstringAPI base URL (default: https://townhall.gg)
config.timeoutnumberRequest timeout in ms (default: 30000)