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 endpointconst 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:

<FormEngineRenderer  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 operationsawait 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