Use our visual form builder to create your form with drag-and-drop ease.
Click "Export JSON" to copy your form configuration.
Install the BetterForms Trigger node and paste your config.
Install the BetterForms Trigger node in your n8n instance:
npm install n8n-nodes-betterforms
Or through the n8n community nodes interface, search for "BetterForms".
The form configuration is a JSON object that defines your form's structure, fields, and behavior.
{
"formTitle": "Contact Form",
"formDescription": "Get in touch with us",
"submitLabel": "Send Message",
"successMessage": "Thank you for your message!",
"theme": {
"primaryColor": "#0ea5e9",
"backgroundColor": "#ffffff",
"fontFamily": "Inter, sans-serif",
"borderRadius": 8
},
"fields": [
{
"type": "text",
"fieldName": "name",
"label": "Your Name",
"required": true
},
{
"type": "email",
"fieldName": "email",
"label": "Email Address",
"required": true
},
{
"type": "textarea",
"fieldName": "message",
"label": "Message",
"required": true
}
]
}Show or hide fields based on other field values using the condition property.
{
"type": "text",
"fieldName": "other_reason",
"label": "Please specify",
"condition": {
"dependsOn": "reason",
"operator": "eq",
"value": "other",
"mode": "show"
}
}eq - equalsneq - not equalscontains - contains substringnotEmpty - field is not emptyisEmpty - field is emptygt, lt, gte, lte - numeric comparisonsSplit your form into multiple steps using the steps property.
{
"formTitle": "Registration",
"steps": [
{
"label": "Personal Info",
"fieldNames": ["name", "email", "phone"]
},
{
"label": "Address",
"fieldNames": ["street", "city", "zip"]
},
{
"label": "Preferences",
"fieldNames": ["newsletter", "notifications"]
}
],
"fields": [...]
}Use our React component library to render forms in your own applications.
Add a .npmrc file to your project root:
@ctfries:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}Set GITHUB_TOKEN env var with a GitHub PAT that has read:packages scope.
pnpm add @ctfries/betterforms-react @ctfries/betterforms-types
import { FormRenderer } from '@ctfries/betterforms-react';
import '@ctfries/betterforms-react/styles.css';
function MyForm() {
return (
<FormRenderer
schema={{
title: 'Contact Us',
fields: [
{ name: 'email', label: 'Email', type: 'email', required: true },
{ name: 'message', label: 'Message', type: 'textarea' }
],
submitUrl: 'https://your-n8n-webhook.com/form'
}}
onSuccess={(response) => console.log('Submitted!', response)}
/>
);
}BetterForms includes Stripe integration for payment forms. Use the EventPaymentBlock component for context-aware payment flows.
A complete payment flow that automatically pulls the event ID from URL parameters and handles pricing, discount codes, and Stripe checkout.
import { EventPaymentBlock } from '@ctfries/betterforms-react';
<EventPaymentBlock
actions={{
getEventPricing: async (eventId) => {
// Fetch pricing from your API
return { fullPrice: 100, currentPrice: 100 };
},
validateDiscountCode: async (eventId, code, price) => {
// Validate discount code
return { valid: true, finalPrice: 80 };
},
createPaymentIntent: async (eventId, amount, metadata) => {
// Create Stripe PaymentIntent on your server
return { clientSecret: '...', paymentIntentId: '...', amount };
},
}}
stripePublishableKey={process.env.NEXT_PUBLIC_STRIPE_KEY!}
onPaymentComplete={(data) => {
console.log('Payment complete:', data);
// data: { paymentIntentId, amount, discountCode?, eventId }
}}
/>For more control, use the individual components:
PricingSummary - Displays pricing with original/discounted amountsDiscountCodeInput - Input with validation for discount codesStripePaymentForm - Stripe Elements payment formPaymentStep - Combined pricing + payment formImplement these server actions for payment:
interface PaymentServerActions {
getEventPricing: (eventId: string) => Promise<{
fullPrice: number;
currentPrice: number;
earlyBirdDeadline?: string;
currency?: string;
}>;
validateDiscountCode: (
eventId: string,
code: string,
basePrice: number
) => Promise<{
valid: boolean;
finalPrice?: number;
message?: string;
}>;
createPaymentIntent: (
eventId: string,
amount: number,
metadata?: Record<string, string>
) => Promise<{
clientSecret: string;
paymentIntentId: string;
amount: number;
}>;
}Import types from @ctfries/betterforms-types:
import type {
FormSchema,
FormField,
FormFieldType,
FormStep,
FieldCondition,
PricingInfo,
PaymentData,
PaymentServerActions,
} from '@ctfries/betterforms-types';import { slugify, isFieldVisible, getLayoutClass } from '@ctfries/betterforms-types';
slugify('First Name'); // 'first_name'
isFieldVisible(field.condition, formData); // true/false
getLayoutClass('half'); // 'w-full sm:w-[calc(50%-0.5rem)]'