Getting Started

Quick Start

1

Build your form

Use our visual form builder to create your form with drag-and-drop ease.

2

Export JSON config

Click "Export JSON" to copy your form configuration.

3

Add to n8n

Install the BetterForms Trigger node and paste your config.

Installing the n8n Node

Install the BetterForms Trigger node in your n8n instance:

npm install n8n-nodes-betterforms

Or through the n8n community nodes interface, search for "BetterForms".

React Component

Use the BetterForms React component to render forms natively in your application.

Install

npm install @betterforms/react

Usage

import { FormRenderer } from '@betterforms/react';

function ContactForm() {
  return (
    <FormRenderer
      endpointUrl="https://your-n8n.com/webhook/abc123/contact"
      apiKey="your-api-key"
      showTitle={true}
    />
  );
}

Payload CMS

Publish forms directly from your Payload CMS admin panel.

1

Install the BetterForms block

Add the BetterForms block to your Payload config.

2

Add an "n8n Form" block to any page

Available in the page editor under layout blocks.

3

Configure

Set the endpoint URL, API key, display options, and hidden fields.

4

Publish

The form renders natively on your site.

Data Sources

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.

URL Prefill

Pre-populate form fields from URL parameters. Useful for pre-populating from email links, CRM campaigns, or QR codes.

Individual parameters

/form?firstName=John&email=john@example.com

Base64 encoded

/form?v=eyJmaXJzdE5hbWUiOiJKb2huIn0=

Fields matching form field names populate visible inputs. Unmatched keys are included as hidden submission data.

Field Types

Basic Inputs

textemailnumbertelurlpasswordtextareacurrencyzip

Selection

selectmultiselectradiocheckboxcheckboxGrouprankingtoggleratingrange

Date & Time

datetimedatetimedateRange

Composite

fullNameaddressintlPhoneguestListsubEventRsvp

Special

filemultiFilesignaturecolorpayment

Layout

headingparagraphdividerstephidden

Form Configuration

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
    }
  ]
}

Conditional Logic

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"
  }
}

Available operators:

  • eq - equals
  • neq - not equals
  • contains - contains substring
  • notEmpty - field is not empty
  • isEmpty - field is empty
  • gt, lt, gte, lte - numeric comparisons

Multi-Step Forms

Split 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": [...]
}

Payment Integration

BetterForms includes Stripe integration for payment forms. Use the EventPaymentBlock component for context-aware payment flows.

EventPaymentBlock

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 }
  }}
/>

Individual Payment Components

For more control, use the individual components:

  • PricingSummary - Displays pricing with original/discounted amounts
  • DiscountCodeInput - Input with validation for discount codes
  • StripePaymentForm - Stripe Elements payment form
  • PaymentStep - Combined pricing + payment form

Server Actions Interface

Implement 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;
  }>;
}

TypeScript Types

Import types from @betterforms/types:

import type {
  FormSchema,
  FormField,
  FormFieldType,
  FormStep,
  FieldCondition,
  PricingInfo,
  PaymentData,
  PaymentServerActions,
} from '@betterforms/types';

Utility Functions

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)]'