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".
Use the BetterForms React component to render forms natively in your application.
npm install @betterforms/react
import { FormRenderer } from '@betterforms/react';
function ContactForm() {
return (
<FormRenderer
endpointUrl="https://your-n8n.com/webhook/abc123/contact"
apiKey="your-api-key"
showTitle={true}
/>
);
}Publish forms directly from your Payload CMS admin panel.
Add the BetterForms block to your Payload config.
Available in the page editor under layout blocks.
Set the endpoint URL, API key, display options, and hidden fields.
The form renders natively on your site.
Pull live data from Salesforce into your forms using SOQL queries. Data sources execute server-side — the client never sees your SOQL queries or Salesforce credentials.
{
"dataSources": {
"event": {
"soql": "SELECT Id, Name, StartDate, Registration_Price__r.Full_Price__c FROM Campaign WHERE Id = '{{params.id}}'",
"single": true,
"cache": 300
}
}
}Use template expressions like {{event.Name}} or {{event.Registration_Price__r.Full_Price__c}} in field labels, descriptions, and default values. They resolve server-side before the form renders.
Pre-populate form fields from URL parameters. Useful for pre-populating from email links, CRM campaigns, or QR codes.
/form?firstName=John&email=john@example.com
/form?v=eyJmaXJzdE5hbWUiOiJKb2huIn0=
Fields matching form field names populate visible inputs. Unmatched keys are included as hidden submission data.
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": [...]
}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 '@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 @betterforms/types:
import type {
FormSchema,
FormField,
FormFieldType,
FormStep,
FieldCondition,
PricingInfo,
PaymentData,
PaymentServerActions,
} from '@betterforms/types';import { slugify, isFieldVisible, getLayoutClass } from '@betterforms/types';
slugify('First Name'); // 'first_name'
isFieldVisible(field.condition, formData); // true/false
getLayoutClass('half'); // 'w-full sm:w-[calc(50%-0.5rem)]'