How Instatic Form Builder Stores Submissions: Complete Technical Guide
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 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, the form module definition specifies where submissions are written:
// 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 collects form data and POSTs it to the public endpoint:
// 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 manages validation and persistence through a four-step process:
- Request Validation – Confirms the form belongs to the current site, verifies payload structure, and checks rate-limit/anti-spam challenges
- Table Resolution – Maps the
formIdto its configuredtargetTableIdusingresolveFormTargetTable() - Row Creation – Inserts a new record into the
data_rowstable via the generic data-row repository - JSON Persistence – Stores the submitted fields as JSON in the
data_jsoncolumn
// 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, 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 assert that created rows associate with the correct target table:
// 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
targetTableIdinsrc/modules/base/forms/index.ts - Client Pipeline: The runtime in
formRuntimeJs.tsPOSTs data to/forms/<formId> - Server Validation:
server/forms/handler.tsverifies requests against site context, rate limits, and spam challenges - Storage Mechanism: Valid submissions insert into
data_rowswithtable_idreferencing the target table and payload stored as JSON indata_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.
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. 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, 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.
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 →