# How Instatic Form Builder Stores Submissions: Complete Technical Guide

> Discover how Instatic stores form submissions as JSON data rows. This technical guide explains server-side validation and data persistence in the Instatic form builder.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: deep-dive
- Published: 2026-07-31

---

**Instatic stores form submissions as JSON data rows in a target data table defined by the form module, validated server-side by [`server/forms/handler.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/forms/handler.ts) and persisted through the generic data-row repository.**

Instatic treats forms as first-party modules within its CMS architecture, enabling developers to declare specific target tables for storing visitor submissions. When a user submits a form, the data flows through a validated pipeline from client-side runtime to server-side storage, ultimately landing as structured JSON in the `data_rows` table. This approach unifies form submissions with Instatic's broader content management system, making entries accessible through the Data workspace and exportable like standard collections.

## Form Module Configuration

Every form in Instatic begins with a module definition that establishes the storage destination. The module declares a `targetTableId` property linking the form instance to a specific data table where submissions accumulate.

### Declaring the Target Table

In [`src/modules/base/forms/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/forms/index.ts), the form module definition specifies where submissions are written:

```typescript
// src/modules/base/forms/index.ts
export const FormModule: ModuleDefinition<FormProps> = {
  name: 'Form',
  category: 'Forms',
  // The table that will store submissions for this form
  targetTableId: 'newsletter_submissions',
  propsSchema: FormPropsSchema,
  defaults: Value.Create(FormPropsSchema),
  component: FormEditor,
};

```

The `targetTableId` field (e.g., `newsletter_submissions`) resolves to a row-table name during submission processing. This configuration separates form presentation from data storage, allowing multiple forms to write to the same table or maintain isolated collections.

## Submission Pipeline Architecture

The storage process involves a coordinated flow between client-side JavaScript and server-side validation handlers.

### Client-Side Runtime

When a visitor submits a form, the runtime defined in [`src/modules/base/forms/formRuntimeJs.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/forms/formRuntimeJs.ts) collects form data and POSTs it to the public endpoint:

```typescript
// src/modules/base/forms/formRuntimeJs.ts
async function submitForm(form) {
  const payload = new FormData(form);
  const response = await fetch(form.action, {
    method: 'POST',
    body: payload,
  });
  const body = await response.json();

  if (body.error) {
    setState(form, 'error', body.error);
    return;
  }
  setState(form, 'success', body.success || 'Thanks. Your submission was received.');
}

```

The runtime automatically handles the network request to `/forms/<formId>`, passing the serialized form fields as the request body.

### Server-Side Processing and Storage

The server handler in [`server/forms/handler.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/forms/handler.ts) manages validation and persistence through a four-step process:

1. **Request Validation** – Confirms the form belongs to the current site, verifies payload structure, and checks rate-limit/anti-spam challenges
2. **Table Resolution** – Maps the `formId` to its configured `targetTableId` using `resolveFormTargetTable()`
3. **Row Creation** – Inserts a new record into the `data_rows` table via the generic data-row repository
4. **JSON Persistence** – Stores the submitted fields as JSON in the `data_json` column

```typescript
// server/forms/handler.ts
export async function handlePublicFormRequest(req) {
  // …validate site, rate‑limit, anti‑spam challenge…
  const { formId } = req.params;
  const targetTable = await resolveFormTargetTable(formId); // e.g. “newsletter_submissions”

  // Insert a new row into `data_rows` with `table_id = targetTable`
  const row = await db.insert('data_rows', {
    table_id: targetTable,
    data_json: JSON.stringify(req.body), // stored as JSON
  });

  return jsonResponse({ success: formSuccessMessage }, { status: 201 });
}

```

## Data Storage Implementation

Submissions are stored within Instatic's core data layer using a generic row-based architecture.

### Core Data Schema

The storage convention is enforced by [`src/core/data/schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/data/schemas.ts), which defines the `data_rows` table structure and mandates that JSON data columns end with the `_json` suffix. This schema ensures that form submissions conform to the CMS's standard data model, enabling interoperability with Instatic's query and export tools.

### Row Structure

Each submission creates a row containing:

- **`table_id`**: References the target table (e.g., `newsletter_submissions`)
- **`data_json`**: Contains the serialized form payload as JSON

This structure allows the Data workspace to treat form submissions identically to other CMS content, supporting full-text search, filtering, and bulk export operations.

## Querying Stored Submissions

Developers can verify storage behavior and retrieve submissions using the data repository patterns. For example, tests in [`src/__tests__/server/publicForms.test.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/__tests__/server/publicForms.test.ts) assert that created rows associate with the correct target table:

```typescript
// src/__tests__/server/publicForms.test.ts
await expect(createdRows[0].table_id).toBe('newsletter_submissions');

```

Once stored, submissions remain accessible through Instatic's administrative Data workspace, where they can be managed, searched, and exported alongside other collection entries.

## Summary

- **Module Configuration**: Forms declare their storage destination via `targetTableId` in [`src/modules/base/forms/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/forms/index.ts)
- **Client Pipeline**: The runtime in [`formRuntimeJs.ts`](https://github.com/CoreBunch/Instatic/blob/main/formRuntimeJs.ts) POSTs data to `/forms/<formId>`
- **Server Validation**: [`server/forms/handler.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/forms/handler.ts) verifies requests against site context, rate limits, and spam challenges
- **Storage Mechanism**: Valid submissions insert into `data_rows` with `table_id` referencing the target table and payload stored as JSON in `data_json`
- **CMS Integration**: Submissions populate standard data tables, accessible through the Data workspace and exportable via built-in tools

## Frequently Asked Questions

### Where does Instatic store the actual form submission data?

Instatic stores submission data in the `data_rows` table, with each row containing a `table_id` column referencing the form's configured target table and a `data_json` column containing the serialized form payload. This JSON column naming convention is enforced by [`src/core/data/schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/data/schemas.ts).

### How does the system know which table to use for a specific form?

Each form module defines a `targetTableId` property in its module definition at [`src/modules/base/forms/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/forms/index.ts). When `handlePublicFormRequest` processes a submission, it calls `resolveFormTargetTable(formId)` to map the form instance to its designated storage table.

### Can form submissions be queried alongside other CMS content?

Yes. Because Instatic stores submissions as standard data rows using the generic repository pattern in [`server/forms/handler.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/forms/handler.ts), they are searchable and exportable through the Data workspace alongside other collections, leveraging the same query mechanisms available for standard content types.

### What validation occurs before a submission is stored?

The server handler performs multiple validation layers: it confirms the form belongs to the requesting site, verifies the payload structure is well-formed, checks rate-limiting rules, and validates anti-spam challenges. Only after passing these checks does the system insert the row into `data_rows`.