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

Field Types

Basic Inputs

textemailnumbertelurlpasswordtextarea

Selection

selectmultiselectradiocheckboxcheckboxGroupranking

Date & Time

datetimedatetimedateRange

Special

currencyaddresssignaturefileratingrangetoggleintlPhone

Layout

headingparagraphdividerstep

Payment

payment

Other

fullNamezipcolorhidden

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

React Component

Use our React component library to render forms in your own applications.

1. Configure GitHub Packages

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.

2. Install packages

pnpm add @ctfries/betterforms-react @ctfries/betterforms-types

3. Use in your app

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

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

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 @ctfries/betterforms-types:

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

Utility Functions

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