Docs
v1.3.14
GitHubSite
Submission / Adapters overview

Adapters overview

Four adapter types — Submit, Draft, Schema, Analytics — plus built-in implementations for HTTP, Supabase, Postgres, and webhooks.

Adapter types

AdapterPurposeInterface
SubmitAdapterSend form responses to a backendsubmit(response), onError?(error)
DraftAdapterSave/load/delete draft statesave(draft), load(schemaId, token), delete(schemaId, token)
SchemaAdapterCRUD for schema storagesave(schema), load(id), delete(id), list(params)
AnalyticsAdapterTrack form interactionstrackView(), trackStart(), trackFieldInteraction(), trackSubmit(), trackAbandon()

Submit adapters

HTTP adapter

Built into @squaredr/fieldcraft-core:

import { createHttpAdapter } from '@squaredr/fieldcraft-core'

const adapter = createHttpAdapter({
  url: 'https://api.example.com/submissions',
  method: 'POST',
  headers: {
    'Authorization': 'Bearer your-token',
    'Content-Type': 'application/json',
  },
  timeout: 30000,       // 30 seconds (default)
  retries: 3,           // Retries on 5xx or network errors (default)
  transform: (response) => ({
    // Optional: reshape the payload before sending
    form_id: response.schemaId,
    data: response.values,
  }),
})

Supabase adapter

From @squaredr/fieldcraft-adapters:

import { createSupabaseAdapter } from '@squaredr/fieldcraft-adapters'
import { supabase } from './supabase'

const adapter = createSupabaseAdapter({
  client: supabase,
  table: 'form_submissions',              // Default: 'form_submissions'
  encryptFields: ['ssn', 'date_of_birth'], // Fields to encrypt at rest
  encryptionKey: process.env.ENCRYPTION_KEY!,
})

Postgres adapter

import { createPostgresAdapter } from '@squaredr/fieldcraft-adapters'

const adapter = createPostgresAdapter({
  connectionString: process.env.DATABASE_URL!,
  table: 'submissions',
  encryptFields: ['ssn'],
  encryptionKey: process.env.ENCRYPTION_KEY!,
  ssl: true,
})

Webhook adapter

import { createWebhookAdapter } from '@squaredr/fieldcraft-adapters'

const adapter = createWebhookAdapter({
  url: 'https://hooks.example.com/form-submit',
  secret: process.env.WEBHOOK_SECRET!,    // HMAC-SHA256 signing
  retries: 3,
  retryDelayMs: 1000,
  retryBackoff: 'exponential',            // 'linear' | 'exponential'
  timeoutMs: 10000,
  headers: { 'X-Source': 'fieldcraft' },
  onRetry: (attempt, error) => {
    console.log(`Webhook retry ${attempt}: ${error.message}`)
  },
})

The webhook adapter signs the payload with HMAC-SHA256. Verify the signature on your server:

import { signPayload } from '@squaredr/fieldcraft-adapters'

// On your webhook endpoint
const signature = req.headers['x-fieldcraft-signature']
const expected = signPayload(JSON.stringify(req.body), process.env.WEBHOOK_SECRET!)
const valid = signature === expected

Multiple adapters

Pass an array to run multiple adapters in parallel:

<FormRenderer
  schema={schema}
  adapters={[httpAdapter, supabaseAdapter, webhookAdapter]}
  onSubmit={handleSubmit}
/>

Each adapter runs independently. If one fails, the others still execute. The SubmitResult reports per-adapter success:

type SubmitResult = {
  success: boolean
  adapterResults: Array<{
    adapterName: string
    success: boolean
    error?: string
  }>
}

Building a custom adapter

Implement the SubmitAdapter interface:

import type { SubmitAdapter, FormResponse } from '@squaredr/fieldcraft-core'

const slackAdapter: SubmitAdapter = {
  name: 'slack',
  async submit(response: FormResponse) {
    await fetch(process.env.SLACK_WEBHOOK_URL!, {
      method: 'POST',
      body: JSON.stringify({
        text: `New submission: ${response.schemaId} (${Object.keys(response.values).length} fields)`,
      }),
    })
  },
  onError(error) {
    console.error('Slack notification failed:', error)
  },
}

Schema adapter

For storing and loading schemas from a database:

import { createHttpSchemaAdapter } from '@squaredr/fieldcraft-core'

const schemaAdapter = createHttpSchemaAdapter({
  baseUrl: 'https://api.example.com/schemas',
  headers: { 'Authorization': 'Bearer token' },
  timeout: 30000,
  cacheTtl: 60000,        // Cache schemas for 60 seconds
})

// CRUD operations
await schemaAdapter.save(schema)
const loaded = await schemaAdapter.load('contact-form')
await schemaAdapter.delete('old-form')
const { items, total } = await schemaAdapter.list({ page: 1, pageSize: 20, search: 'intake' })

Supabase also has a schema adapter:

import { createSupabaseSchemaAdapter } from '@squaredr/fieldcraft-adapters'

const schemaAdapter = createSupabaseSchemaAdapter({
  client: supabase,
  table: 'form_schemas',
})

Field-level encryption

Both Supabase and Postgres adapters support encrypting specific fields before storage:

const adapter = createSupabaseAdapter({
  client: supabase,
  table: 'submissions',
  encryptFields: ['ssn', 'date_of_birth', 'medical_id'],
  encryptionKey: process.env.ENCRYPTION_KEY!,
})

Encryption utilities are also exported for manual use:

import { encrypt, decrypt } from '@squaredr/fieldcraft-adapters'

const encrypted = encrypt('sensitive-value', key)
const decrypted = decrypt(encrypted, key)

Next steps

On this page