FieldCraftDocsServicesBlogWork With Me →

Storage Adapters

Plug in Supabase, Postgres, or webhooks to persist form responses.

By default, FieldCraft calls your onSubmit callback with a FormResponse and leaves storage up to you. Storage adapters add automatic persistence to a database or external service.

Available Adapters

PackageStorageEncryptionDrafts
@squaredr/fieldcraft-supabaseSupabase tablesField-level AES-256-GCMYes
@squaredr/fieldcraft-postgresPostgreSQL + Drizzle ORMPayload AES-256-GCMYes
@squaredr/fieldcraft-webhookHTTP POSTHMAC-SHA256 signingNo

All three adapters are free and open source (MIT licensed).

Install

# Pick one (or more)
npm install @squaredr/fieldcraft-supabase
npm install @squaredr/fieldcraft-postgres
npm install @squaredr/fieldcraft-webhook

How Adapters Work

Every adapter implements the SubmitAdapter interface. When the engine submits, it runs each adapter in parallel and collects results.

interface SubmitAdapter {
name: string;
submit(response: FormResponse): Promise<void>;
onError?: (error: Error) => void;
}

FormResponse

The data object passed to every adapter on submit:

type FormResponse = {
schemaId: string;
schemaVersion: string;
submittedAt: string; // ISO 8601
sessionToken: string;
values: Record<string, unknown>;
scores?: Record<string, number>;
totalScore?: number;
metadata?: Record<string, unknown>;
completionTimeMs?: number;
};

SubmitResult

Returned by engine.submit() with per-adapter status:

type SubmitResult = {
success: boolean;
adapterResults: {
adapterName: string;
success: boolean;
error?: string;
}[];
};

Usage with FormEngineRenderer

import { FormEngineRenderer } from "@squaredr/fieldcraft-react";
import { createSupabaseAdapter } from "@squaredr/fieldcraft-supabase";

const adapter = createSupabaseAdapter({ /* config */ });

<FormEngineRenderer
schema={schema}
adapters={adapter}
onSubmit={(response) => console.log("Done:", response)}
/>

Usage with createEngine

import { createEngine } from "@squaredr/fieldcraft-core";
import { createPostgresAdapter } from "@squaredr/fieldcraft-postgres";
import { createWebhookAdapter } from "@squaredr/fieldcraft-webhook";

const engine = createEngine(schema, {
adapters: [
createPostgresAdapter({ connectionString: "..." }),
createWebhookAdapter({ url: "https://...", secret: "..." }),
],
});

const result = await engine.submit();
// result.adapterResults[0] → postgres result
// result.adapterResults[1] → webhook result

Multiple Adapters

Pass an array to adapters to write to multiple destinations. All adapters run in parallel. The submission succeeds only if all adapters succeed.

Draft Adapters

The Supabase and Postgres packages also export draft adapters for server-side draft persistence:

import { createSupabaseAdapter } from "@squaredr/fieldcraft-supabase";

const adapter = createSupabaseAdapter({ /* config */ });

const engine = createEngine(schema, {
adapters: adapter,
draftAdapter: adapter.draftAdapter, // Also handles drafts
sessionToken: "user-123",
});

Building a Custom Adapter

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

const myAdapter: SubmitAdapter = {
name: "my-api",
async submit(response: FormResponse) {
const res = await fetch("https://api.example.com/forms", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(response),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
},
onError(error) {
console.error("Adapter failed:", error);
},
};