How Does the Form Builder in Instatic Store Submissions in Data Tables?
Instatic persists form submissions by validating the payload in 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 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.
// 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 serializes the field values and issues a POST request to the /forms/:formId endpoint.
// 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 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.
// 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, 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
targetTableIdproperty insrc/modules/base/forms/index.ts, falling back to an auto-generated name such ascontact_submissions. - The browser runtime in
src/modules/base/forms/formRuntimeJs.tstransmits field values as JSON to the/forms/:formIdendpoint. - The server handler in
server/forms/handler.tsvalidates the payload and inserts a new row into the unifieddata_rowscollection through the generic CMS repository. - Because
src/core/data/schemas.tsgoverns 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, 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 writes submissions into the shared data_rows abstraction defined in 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. 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →