FairForms - Because Typeform's pricing made me cry... and when someone special needed an afforadble form builder, I said 'Hey, I could build that for you' 💜 So here we are... 🥲
A self-hosted form builder that doesn't cost a kidney per month. Create beautiful, conversational forms without breaking the bank.
- ✨ Drag-and-drop form builder
- 📱 Responsive design
- 🎨 Multiple form elements:
- Contact Info
- Phone Number
- Address
- Website
- Multiple Choice
- Dropdown
- Picture Choice
- Long/Short Text
- Number
- Date
- File Upload
- Welcome/End Screens
- 🔒 Form publishing controls
- 🔗 Shareable form links
- 📊 Response collection
- 👥 Collaborator support
- 🎯 Custom URLs
- Framework: Next.js 14 (App Router)
- Language: TypeScript
- Database: PostgreSQL with Drizzle ORM
- Styling: Tailwind CSS
- UI Components: shadcn/ui
- Auth: Clerk
- Drag & Drop: @hello-pangea/dnd
- Deployment: Vercel
src/app/
├── / # Landing page
│ └── page.tsx # Public homepage with auth checks
├── (auth)/ # Auth-related pages
│ ├── sign-in/ # Clerk sign-in integration
│ └── sign-up/ # Clerk sign-up integration
├── (dashboard)/ # Protected dashboard routes
│ └── dashboard/
│ ├── page.tsx # Forms dashboard
│ │ # Lists all forms
│ │ # Quick actions
│ │ # Form stats
│ └── forms/
│ ├── new/ # New form creation
│ │ └── page.tsx # Form builder for new forms
│ └── [formId]/ # Existing form editor
│ └── page.tsx # Form builder with saved data
└── forms/ # Public form routes
└── [formId]/ # Public form view
├── page.tsx # Form display and submission
├── loading.tsx # Loading state
└── error.tsx # Error handling
src/app/api/
└── forms/
├── route.ts # Form collection endpoints
│ ├── GET: List forms
│ │ - Query params:
│ │ - page: Pagination
│ │ - limit: Items per page
│ │ - search: Search forms
│ │ - Returns: Paginated form list
│ └── POST: Create form
│ - Body: Initial form data
│ - Returns: Created form
│
├── [formId]/
│ ├── route.ts # Individual form operations
│ │ ├── GET: Fetch form
│ │ │ - Returns: Complete form data
│ │ ├── PATCH: Update form
│ │ │ - Body: Partial form updates
│ │ │ - Returns: Updated form
│ │ └── DELETE: Remove form
│ │ - Returns: Success status
│ │
│ ├── publish/
│ │ └── route.ts # Publishing endpoints
│ │ └── PATCH: Toggle publish
│ │ - Returns: Updated publish status
│ │
│ └── submit/
│ └── route.ts # Form submission
│ └── POST: Submit response
│ - Body: Form responses
│ - Validates: Required fields
│ - Returns: Submission confirmation
src/components/form-builder/
├── FormBuilder.tsx # Main container
│ # Manages drag-drop state
│ # Handles element CRUD
│ # Controls form settings
│
├── ElementToolbar.tsx # Element palette
│ # Lists available elements
│ # Provides drag sources
│ # Groups elements by type
│
├── Canvas.tsx # Form layout
│ # Drop target for elements
│ # Handles element ordering
│ # Preview rendering
│
└── Properties.tsx # Element settings
# Dynamic property forms
# Validation rules
# Element-specific options
src/components/forms/elements/
├── base/ # Common functionality
│ ├── BaseInput.tsx # Input foundations
│ └── BaseChoice.tsx # Choice foundations
│
├── inputs/ # Basic inputs
│ ├── TextInput.tsx # Short text
│ ├── TextArea.tsx # Long text
│ ├── EmailInput.tsx # Email
│ └── PhoneInput.tsx # Phone
│
├── choices/ # Selection elements
│ ├── MultipleChoice.tsx # Radio/Checkbox
│ ├── Dropdown.tsx # Select
│ └── PictureChoice.tsx # Image selection
│
└── special/ # Special elements
├── Welcome.tsx # Welcome screen
├── Statement.tsx # Info display
└── EndScreen.tsx # Completion screen
src/app/
├── / # Landing page
├── (auth)/ # Auth-related pages
├── (dashboard)/
│ └── dashboard/
│ ├── page.tsx # Forms dashboard
│ └── forms/
│ ├── new/ # New form creation
│ └── [formId] # Form builder/editor
└── forms/
└── [formId] # Public form view
src/app/api/
└── forms/
├── route.ts # GET: List forms, POST: Create form
├── [formId]/
│ ├── route.ts # GET, PATCH, DELETE form
│ ├── publish/
│ │ └── route.ts # PATCH: Toggle form publish state
│ └── submit/
│ └── route.ts # POST: Submit form response
FormBuilder
: Main form builder interfaceElementToolbar
: Left sidebar with available form elementsCanvas
: Main drag-and-drop area for form elementsProperties
: Right sidebar for element properties
TextInput
: Short text responsesTextArea
: Long text responsesEmailInput
: Email collectionPhoneInput
: Phone number with country codeAddressInput
: Multi-field address collectionMultipleChoiceInput
: Radio/checkbox optionsDropdownInput
: Select from optionsPictureChoice
: Image-based choicesDateInput
: Date/time pickerFileUpload
: File attachmentStatement
: Static text display
- Node.js 18+
- PostgreSQL database (We use Neon)
- Clerk account for auth
- Vercel account (optional, for deployment)
# Create a .env file with:
DATABASE_URL="your_neon_postgres_connection_string"
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="your_clerk_key"
CLERK_SECRET_KEY="your_clerk_secret"
NEXT_PUBLIC_APP_URL="http://localhost:3000"
# Clone the repo
git clone https://github.com/souravinsights/myforms.git
# Install dependencies
yarn
# Run database migrations
npm run db:push
# Start development server
npm run dev
GET /api/forms
- List all formsPOST /api/forms
- Create new formGET /api/forms/:id
- Get form by IDPATCH /api/forms/:id
- Update formDELETE /api/forms/:id
- Delete formPATCH /api/forms/:id/publish
- Toggle form publish statePOST /api/forms/:id/submit
- Submit form response
- Uses Clerk for authentication
- Protected routes for form creation/management
- Public access for form viewing/submission
- Collaborator management on forms
- Form analytics
- Response exports
- Custom themes
- Conditional logic
- Payment integration
- Webhook support
- API keys for programmatic access
- Custom domains
MIT
Built with shadcn/ui components and inspired by Typeform's UX (but not their pricing 😅)