Quickstart
Build forms and collect responses directly in your application.
Overview
The form fragment built on top of Fragno provides a complete form management and response collection system built on open standards: JSON Schema and JSON Forms.
Rendering forms can be done by using one of the official or community-made JSON Forms renderers or our very own shadcn/ui renderer (React only).
Installation
Install the Form fragment and Fragno db package:
npm install @fragno-dev/forms @fragno-dev/dbServer Setup
1. Initialize the database adapter
You must initialize the appropriate database adapter for use with the form fragment. If you already have other fragments configured, you can reuse the same adapter here. For additional details on setting the adapter see our Fragno DB docs.
The example below uses DrizzleORM in combination with PostgreSQL, but adapters for other databases and ORMs are also available.
import { DrizzleAdapter } from "@fragno-dev/db/adapters/drizzle";
import { PostgresDialect } from "@fragno-dev/db/dialects";
import { NodePostgresDriverConfig } from "@fragno-dev/db/drivers";
import { Pool } from "pg";
const dialect = new PostgresDialect({
pool: new Pool({ connectionString: process.env.DATABASE_URL }),
});
export const fragmentDbAdapter = new DrizzleAdapter({
dialect,
driverConfig: new NodePostgresDriverConfig(),
});2. Create the Fragment Instance
Create a file to instantiate the fragment server:
import { createFormsFragment } from "@fragno-dev/forms";
import { fragmentDbAdapter } from "./db";
export const formsFragment = createFormsFragment(
{
onFormCreated: async (form) => {
console.log(`Form created: ${form.title}`);
},
onResponseSubmitted: async (response) => {
// Send notifications, trigger workflows, etc.
console.log(`New submission for form ${response.formId}`);
},
// Optional: Define forms in code
staticForms: [],
},
{ databaseAdapter: fragmentDbAdapter },
);Static forms are useful when you want to version control your form
definitions alongside application code. All other forms are created dynamically through the API (see
useCreateForm hook).
3. Mount the Fragment Routes
The fragment provides HTTP routes that need to be mounted in your application. How you do this depends on your framework. See Frameworks for supported frameworks.
import { formsFragment } from "@/lib/forms";
export const { GET, POST, PUT, PATCH, DELETE } = formsFragment.handlersFor("next-js");4. Run Database Migrations
The fragment adds form and response tables to your database. Generate the schema using the
fragno-cli:
# Generate schema file
npx fragno-cli db generate lib/forms.ts -o db/forms.schema.ts5. Secure admin routes
Use middleware to secure the administrative routes.
import { createFormsFragment } from "@fragno-dev/forms";
export const formsFragment = createFormsFragment(
{
// ... your config
},
{ databaseAdapter },
).withMiddleware(async ({ path, headers }, { error }) => {
const isAdmin = getUser(headers).role === "admin"; // Using your authentication system
if (path.startsWith("/admin") && !isAdmin) {
return error({ message: "Not authorized", code: "NOT_AUTHORIZED" }, 401);
}
});Client Setup
Create a client instance to use the fragment in your frontend:
import { createFormsClient } from "@fragno-dev/forms/react";
export const formsClient = createFormsClient();The client exposes hooks for both public and admin operations:
Public hooks:
useForm: Fetch a form by sluguseSubmitForm: Submit a response to a form
Admin hooks:
useForms: List all formsuseCreateForm: Create a new formuseUpdateForm: Update an existing formuseDeleteForm: Delete a formuseSubmissions: List submissions for a formuseSubmission: Get a single submissionuseDeleteSubmission: Delete a submission
Creating a Form
Forms can be created dynamically at runtime using the admin hooks, or defined statically in code (see Static Forms).
We do not provide a visual form builder at this moment. Let us know if that is something you'd like to see!
LLMs are very good at generating JSON Schema and JSON Forms UI Schema definitions.
To create a form using the useCreateForm hook:
import { formsClient } from "@/lib/forms-client";
export function FormBuilder() {
const { mutate: createForm, loading, error } = formsClient.useCreateForm();
const handleCreate = async () => {
const result = await createForm({
body: {
title: "Contact Us",
slug: "contact",
status: "open",
dataSchema: {
type: "object",
properties: {
name: { type: "string", minLength: 1 },
email: { type: "string", format: "email" },
message: { type: "string", minLength: 10 },
},
required: ["name", "email", "message"],
},
uiSchema: {
type: "VerticalLayout",
elements: [
{ type: "Control", scope: "#/properties/name" },
{ type: "Control", scope: "#/properties/email" },
{ type: "Control", scope: "#/properties/message", options: { multi: true } },
],
},
},
});
if (result.success) {
console.log("Form created with ID:", result.data);
}
};
return (
<button onClick={handleCreate} disabled={loading}>
{loading ? "Creating..." : "Create Form"}
</button>
);
}Submitting a Form
Here's how to fetch a form and submit a response:
import { formsClient } from "@/lib/forms-client";
export function ContactForm() {
const { data: form, loading: formLoading } = formsClient.useForm({
pathParams: { slug: "contact" },
});
const { mutate: submit, loading: submitting } = formsClient.useSubmitForm();
const handleSubmit = async (formData: Record<string, unknown>) => {
const result = await submit({
pathParams: { slug: "contact" },
body: { data: formData },
});
if (result.success) {
// Submission successful, result.data contains the response ID
console.log("Submitted:", result.data);
}
};
if (formLoading || !form) return <div>Loading...</div>;
// Render form using form.dataSchema and form.uiSchema
// See "Rendering Forms" section below
return <div>{/* Your form UI */}</div>;
}For public-facing forms, consider adding bot protection. The submission body accepts an optional
securityToken field that you can use for services like
Cloudflare Turnstile. Validate the token
in middleware before processing the submission.
Rendering Forms
The Form fragment stores forms using JSON Schema for data validation and JSONForms UI Schema for layout. This gives you flexibility in how you render forms.
JSONForms is a library that renders forms from these schemas. It supports multiple renderer sets including Material UI, Vanilla, and custom implementations.
For shadcn/ui projects, we provide a dedicated renderer package. See the ShadCN Renderer page for setup instructions.