Skip to main content

Local Development Setup

This guide walks you through setting up Terra for local development.

Prerequisites

Node.js 18+ installed
pnpm 9+ installed (npm install -g pnpm)
Git configured
A Supabase project (free tier works)
A WorkOS account (free tier: 1M MAU)
A Doppler account (recommended, for secrets management)
A Resend account (optional, for email testing)
A DeepL account (optional, for auto-translation)
A Smarty Streets account (optional, for address verification)

Quick Start

1

Clone the Repository

git clone https://github.com/mrcoven94/unify-platform.git
cd unify-platform
2

Install Dependencies

pnpm install
This installs dependencies for the entire monorepo including:
  • apps/terra - The main Next.js application
  • packages/ui - Shared UI components (@unify/ui)
3

Configure Environment Variables

You have two options for managing environment variables:Doppler provides centralized secrets management with automatic syncing.Setup:
# Install Doppler CLI
brew install dopplerhq/cli/doppler

# Login
doppler login

# Setup project (run from apps/terra directory)
cd apps/terra
doppler setup
Running with Doppler:
# From the monorepo root - the dev script auto-injects Doppler secrets
pnpm dev
The dev script in apps/terra/package.json is configured as doppler run -- next dev, so secrets are automatically injected.
Benefits:
  • No .env.local file to manage
  • Automatic sync across team members
  • Environment branching (dev/staging/prod)
  • Audit logs for secret access
  • Automatic secret rotation

Option B: Local .env.local File

Create a .env.local file in apps/terra/:
# =============================================================================
# SUPABASE
# =============================================================================
# Get these from: Supabase Dashboard → Settings → API
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...your-anon-key
SUPABASE_SERVICE_ROLE_KEY=eyJ...your-service-role-key

# =============================================================================
# WORKOS
# =============================================================================
# Get these from: WorkOS Dashboard → API Keys
WORKOS_API_KEY=sk_test_...
WORKOS_CLIENT_ID=client_...

# Must match exactly what's configured in WorkOS Dashboard → Redirects
WORKOS_REDIRECT_URI=http://localhost:3000/auth/callback

# Session encryption key (must be 32+ characters)
WORKOS_COOKIE_PASSWORD=your-secret-key-at-least-32-characters-long

# =============================================================================
# RESEND (Optional)
# =============================================================================
# Get from: Resend Dashboard → API Keys
RESEND_API_KEY=re_...

# In dev mode, all emails are sent to this address (prevents accidental sends)
TEST_EMAIL=your-email@example.com

# =============================================================================
# APP
# =============================================================================
NEXT_PUBLIC_APP_URL=http://localhost:3000

# =============================================================================
# DEEPL (Optional - for auto-translation)
# =============================================================================
# Get from: DeepL Dashboard → Account → API Keys
DEEPL_API_KEY=your-deepl-api-key:fx

# =============================================================================
# SMARTY STREETS (Optional - for address verification)
# =============================================================================
# Get from: Smarty Dashboard → API Keys
SMARTY_AUTH_ID=your-auth-id
SMARTY_AUTH_TOKEN=your-auth-token

# =============================================================================
# VERCEL (Optional - for custom domains)
# =============================================================================
# Get from: Vercel Dashboard → Settings → Tokens
VERCEL_API_TOKEN=your-vercel-token
VERCEL_PROJECT_ID=prj_...
VERCEL_TEAM_ID=team_...

# =============================================================================
# POSTHOG (Optional)
# =============================================================================
NEXT_PUBLIC_POSTHOG_KEY=phc_...
NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com
Never commit .env.local to version control. It contains secrets.
4

Run Database Migrations

Terra uses SQL migration files. Run them in order against your Supabase database.Option A: Supabase Dashboard
  1. Go to Supabase Dashboard → SQL Editor
  2. Run each file in the apps/terra/migrations/ folder in numerical order
  3. Start with 002_add_form_settings.sql, then 003_..., etc.
Option B: Supabase CLI
# If you have Supabase CLI installed
cd apps/terra
supabase db push
Migration files:
apps/terra/migrations/
├── 002_add_form_settings.sql
├── 003_add_branding_settings.sql
├── 004_storage_bucket.sql        # Creates private file bucket
├── 005_program_details.sql
├── 006_program_members.sql       # RBAC for forms
├── 007_system_settings.sql
├── 008_localization_dictionary.sql
├── 009_footer_branding.sql
├── 010_form_footer_overrides.sql
├── 011_form_templates.sql
├── 012_notification_field_mapping.sql
├── 013_email_sender_settings.sql
├── 014_folders.sql
├── 015_folder_custom_domains.sql
├── 016_agencies.sql
├── 017_webhooks.sql
├── 018_scheduled_publishing.sql
├── 019_agencies_rls.sql
└── 020_security_fixes.sql
5

Configure WorkOS Redirect

In your WorkOS Dashboard:
  1. Go to Redirects
  2. Add http://localhost:3000/auth/callback
  3. Save
This must exactly match WORKOS_REDIRECT_URI in your .env.local.
6

Start the Development Server

# From monorepo root
pnpm dev
Open http://localhost:3000 in your browser.
Turborepo will build dependencies (like @unify/ui) automatically before starting the dev server.

Verification Checklist

After setup, verify these features work:
FeatureHow to Test
LoginClick “Sign In” → WorkOS login → Returns to dashboard
Dashboard/ shows metric cards and activity feed
Form BuilderCreate a form, add fields, save
Public FormVisit /f/your-form-slug
SubmissionSubmit a form → Appears in admin submissions table
Email (optional)Submit a form → Check TEST_EMAIL inbox
Translations (optional)Settings → Localization → Add language → Auto-translate
Address Autocomplete (optional)Add Address field → Type “123 main” → See suggestions

Running Tests

# Unit tests (Vitest)
pnpm test

# Unit tests with UI
pnpm test:ui

# E2E tests (Playwright) - requires dev server running
pnpm test:e2e

# E2E tests with browser visible
pnpm test:e2e:headed

# Type checking
pnpm exec tsc --noEmit

# Linting
pnpm lint

# Production build (via Turborepo)
pnpm build

# Full CI check (what runs on PRs)
pnpm lint && pnpm test && pnpm build

Common Issues

Double-check your SUPABASE_SERVICE_ROLE_KEY. It should:
  • Start with eyJ...
  • Be different from the anon key
  • Come from Settings → API → service_role key (not anon)
The error “redirect_uri mismatch” means your .env.local doesn’t match WorkOS Dashboard.Check that WORKOS_REDIRECT_URI exactly matches what’s in WorkOS → Redirects, including:
  • Protocol (http:// vs https://)
  • Port (:3000)
  • Path (/auth/callback)
In development mode, emails are sent via Resend’s test domain to TEST_EMAIL.Check:
  1. RESEND_API_KEY is set
  2. TEST_EMAIL is your real email address
  3. Check spam folder
Make sure you ran migrations/004_storage_bucket.sql. This creates the private form-uploads bucket and RLS policies.

Project Structure

unify-platform/                 # Monorepo root
├── apps/
│   ├── terra/                  # Main Next.js application
│   │   ├── src/
│   │   │   ├── app/            # Next.js App Router
│   │   │   │   ├── (dashboard)/    # Admin routes (protected)
│   │   │   │   ├── (portal)/       # Applicant portal (protected)
│   │   │   │   ├── f/[slug]/       # Public form pages
│   │   │   │   ├── actions.ts      # Server actions
│   │   │   │   └── auth/           # OAuth callback
│   │   │   ├── components/
│   │   │   │   ├── engine/         # Form renderer & field components
│   │   │   │   ├── form-builder/   # Visual builder UI
│   │   │   │   └── emails/         # React Email templates
│   │   │   ├── lib/
│   │   │   │   ├── auth.ts         # WorkOS integration
│   │   │   │   ├── supabase.ts     # Database clients
│   │   │   │   ├── logic-engine.ts # Conditional visibility
│   │   │   │   └── i18n.ts         # Internationalization
│   │   │   └── types/
│   │   │       └── schema.ts       # Zod form schemas
│   │   ├── migrations/         # SQL migration files
│   │   └── tests/              # Playwright E2E tests
│   └── docs/                   # Mintlify documentation
├── packages/
│   └── ui/                     # @unify/ui - Shared UI components
│       └── src/
│           ├── components/     # Button, Card, Input, Dialog, etc.
│           ├── hooks/          # useIsMobile, etc.
│           └── lib/            # cn() utility
├── turbo.json                  # Turborepo configuration
├── pnpm-workspace.yaml         # Workspace definitions
└── .github/workflows/          # CI configuration

Previewing Documentation

The docs are built with Mintlify and live in apps/docs/. Mintlify requires Node 20 (LTS), which may differ from your main development Node version.

One-Time Setup

Install fnm (Fast Node Manager) to run Mintlify with the correct Node version:
# Install fnm via Homebrew
brew install fnm

# Add to your shell
echo 'eval "$(fnm env --use-on-cd)"' >> ~/.zshrc
source ~/.zshrc

# Install Node 20
fnm install 20

# Create a convenient alias (runs from apps/docs)
echo 'alias mint="cd apps/docs && NODE_OPTIONS=\"--max-old-space-size=4096\" fnm exec --using=20 -- npx mintlify dev"' >> ~/.zshrc
source ~/.zshrc

Running the Docs Server

# From the monorepo root
mint

# Or manually
cd apps/docs
npx mintlify dev
This starts the preview at http://localhost:3000.
The mint alias automatically uses Node 20 and allocates extra memory to prevent crashes when processing large projects.

Next: Adding Custom Fields

Learn how to extend the form engine with new field types.