> ## Documentation Index
> Fetch the complete documentation index at: https://docs.onlyform.com/llms.txt
> Use this file to discover all available pages before exploring further.

# JavaScript / TypeScript

> Use the official typed Onlyform SDK from npm.

`@onlyform/sdk` is the official ESM package for Node.js 18+ and server runtimes with a standards-compatible Fetch API. It includes TypeScript declarations and has no runtime dependencies.
The current release targets API contract `2026-08-01`.

## Install

```bash theme={null}
npm install @onlyform/sdk
```

## Configure the client

```ts theme={null}
import { Onlyform } from '@onlyform/sdk'

const onlyform = new Onlyform({
  apiKey: process.env.ONLYFORM_API_KEY!,
})
```

The client uses `https://api.onlyform.com` by default. Use the optional `baseUrl` constructor property only for private development environments.

## Create a complete form

The create call accepts blocks, settings, and theme tokens inside `definition`.

```ts theme={null}
const created = await onlyform.forms.create({
  name: 'Product feedback',
  workspace_id: '507f1f77bcf86cd799439012',
  type: 'MULTI',
  definition: {
    blocks: [
      {
        id: 'rating',
        type: 'rating',
        label: 'How would you rate the product?',
        description: '',
        required: true,
        properties: { max: 5, icon: 'star' },
      },
    ],
    settings: { progressBar: true },
    theme: {
      font: 'Inter',
      colors: { primary: '#3969D9' },
    },
  },
})

console.log(created.id)
```

See [Form definitions](/guides/form-definitions), [Block types](/schema/block-types), and [Theme tokens](/schema/theme) for the complete accepted schema.

## Add and remove blocks

Form and block reads return `{ data, etag }`. Pass the latest ETag as `ifMatch` when editing blocks to prevent overwriting a concurrent change.

```ts theme={null}
const current = await onlyform.forms.retrieve('form_id')

const added = await onlyform.forms.blocks.create(
  'form_id',
  {
    block: {
      id: 'email',
      type: 'email',
      label: 'Email address',
      description: 'Where should we reply?',
      required: true,
      properties: {},
    },
  },
  { ifMatch: current.etag },
)

await onlyform.forms.blocks.delete(
  'form_id',
  'email',
  { ifMatch: added.etag },
)
```

Use `replaceAll(formId, blocks, { ifMatch })` to atomically replace the complete ordered block collection.

## Update a form

```ts theme={null}
const current = await onlyform.forms.retrieve('form_id')

const updated = await onlyform.forms.update(
  'form_id',
  {
    name: 'Customer feedback',
    definition: {
      settings: { progressBar: false },
      theme: { font: 'Georgia', colors: { primary: '#3969D9' } },
    },
  },
  { ifMatch: current.etag },
)
```

## Cursor pagination

```ts theme={null}
let cursor: string | undefined

do {
  const page = await onlyform.forms.list({ limit: 100, cursor })

  for (const form of page.items) {
    console.log(form.id)
  }

  cursor = page.page.next_cursor ?? undefined
} while (cursor)
```

## Other resources

```ts theme={null}
const workspaces = await onlyform.workspaces.list({ limit: 50 })
const submissions = await onlyform.forms.submissions.list('form_id', { limit: 100 })
const metrics = await onlyform.forms.analytics.metrics('form_id', { period: '30d' })
const webhooks = await onlyform.webhooks.list()
```

## Handle errors

```ts theme={null}
import { OnlyformApiError, OnlyformNetworkError } from '@onlyform/sdk'

try {
  await onlyform.forms.retrieve('form_id')
} catch (error) {
  if (error instanceof OnlyformApiError) {
    console.error(error.status, error.code, error.requestId, error.details)
  } else if (error instanceof OnlyformNetworkError) {
    console.error(error.message)
  }
}
```

The client accepts `AbortSignal` on request options and uses a 30-second timeout by default. Set `timeoutMs` in the constructor to change it.
