# How Does the Form Builder in Instatic Store Submissions in Data Tables?

> Discover how Instatic's form builder stores submissions in data tables. Learn about validation, auto-generated tables, and the data repository layer at CoreBunch/Instatic.

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

---

**Instatic persists form submissions by validating the payload in [`server/forms/handler.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/forms/handler.ts) and inserting it into a dedicated data table—auto-generated from the form slug—via the shared CMS data-repository layer that writes to the unified `data_rows` collection.**

In the open-source CMS **Instatic** (`CoreBunch/Instatic`), the built-in form builder does not rely on email-only delivery or external services to capture user input. Instead, it routes every submission through the core data layer and writes it to a structured **data table** that editors can query alongside standard CMS content. Understanding how the form builder stores submissions in data tables shows why Instatic treats user-generated data as first-class content.

## The Submission Flow

### 1. Form Configuration and Target Table Assignment

The built-in form module defined in [`src/modules/base/forms/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/forms/index.ts) exposes a `targetTableId` property that binds a form to its storage destination. When an editor omits this value, Instatic automatically produces a table identifier from the form slug; for instance, a form keyed to `contact` receives the table name `contact_submissions`. The generic row shape governing all data tables, including submission tables, is declared in [`src/core/data/schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/data/schemas.ts).

```typescript
// In the visual editor, drop a “Form” module and set its Form ID.
{
  id: 'base.form',
  props: {
    formId: 'contact',           // form identifier used in the URL
    // targetTableId is omitted → Instatic creates “contact_submissions”
  },
}

```

### 2. Client-Side POST in the Form Runtime

When a visitor clicks submit, the browser-side runtime in [`src/modules/base/forms/formRuntimeJs.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/forms/formRuntimeJs.ts) serializes the field values and issues a `POST` request to the `/forms/:formId` endpoint.

```javascript
// The generated form element posts to /forms/contact
fetch('/forms/contact', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Alice', email: 'alice@example.com' })
})
  .then(r => r.json())
  .then(data => console.log(data.success)) // → “Thanks. Your submission was received.”

```

The runtime awaits a JSON response so it can surface the configured confirmation message or error state to the user.

### 3. Server-Side Validation and Persistence

The handler in [`server/forms/handler.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/forms/handler.ts) receives the request, resolves the destination table, and persists the entry using the CMS-wide data-row creation logic. The insertion targets the central `data_rows` store defined by the core schema.

```typescript
// server/forms/handler.ts
export async function handleFormSubmission(req) {
  const { formId } = req.params;
  const payload = await req.json();               // validated against Form schema
  const targetTable = await getTargetTableId(formId); // e.g. “contact_submissions”

  // Insert a new row into the data‑table
  await db.insert('data_rows', {
    table_id: targetTable,
    data: payload,               // stored as JSONB / TEXT depending on DB dialect
    created_at: new Date(),
  });

  return jsonResponse({ success: true });
}

```

Because the handler delegates storage to the generic repository rather than a one-off form processor, the submission inherits the same durability and schema rules as any other CMS record.

## Why Submissions Are First-Class CMS Data

Storing form entries inside the shared `data_rows` abstraction means editors do not need a separate plugin to view leads or survey results. As implemented in [`src/core/data/schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/data/schemas.ts), every row carries a uniform envelope—`table_id`, `data`, and timestamps—so submissions naturally appear inside the **Data** workspace. They can be filtered, sorted, and exported using the same UI and API surfaces that manage regular content tables.

## Summary

- Instatic maps every form to a **target data table** via the `targetTableId` property in [`src/modules/base/forms/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/forms/index.ts), falling back to an auto-generated name such as `contact_submissions`.
- The browser runtime in [`src/modules/base/forms/formRuntimeJs.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/forms/formRuntimeJs.ts) transmits field values as JSON to the `/forms/:formId` endpoint.
- The server handler in [`server/forms/handler.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/forms/handler.ts) validates the payload and inserts a new row into the unified `data_rows` collection through the generic CMS repository.
- Because [`src/core/data/schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/data/schemas.ts) governs the underlying schema, form submissions are queryable, filterable records that live alongside standard CMS content.

## Frequently Asked Questions

### What table name does Instatic use when no target table is specified?

When the `targetTableId` property is omitted from the form definition in [`src/modules/base/forms/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/forms/index.ts), Instatic derives the table name automatically from the form slug. A form identified as `contact` stores its entries in a table named `contact_submissions`.

### Can form submissions be queried like regular CMS content?

Yes. The handler in [`server/forms/handler.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/forms/handler.ts) writes submissions into the shared `data_rows` abstraction defined in [`src/core/data/schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/data/schemas.ts). This makes them first-class CMS records that editors can query, filter, and export directly from the Data workspace.

### Which file handles the incoming form POST request?

The server-side entry point is [`server/forms/handler.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/forms/handler.ts). This module parses the JSON body, resolves the correct destination table, and inserts the submission into the database before returning a success response to the client runtime.

### How does the browser transmit form data to the Instatic backend?

The client-side module [`src/modules/base/forms/formRuntimeJs.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/forms/formRuntimeJs.ts) collects the serialized field values and sends a `POST` request to `/forms/:formId` with a JSON payload. It then reads the JSON response to confirm the row was stored and to display the appropriate success message.