Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs-terra.withunify.org/llms.txt

Use this file to discover all available pages before exploring further.

Design Philosophy

Every technical decision encodes a belief. Here’s what we believe.
Terra isn’t just a forms platform—it’s an opinionated take on how government intake infrastructure should work. This page explains the principles that guide our technical decisions.

Principle 1: Applicant Data Is Sacred

Government benefit applications contain the most sensitive information people share: Social Security numbers, income documentation, disability status, immigration records. A data breach doesn’t just expose information—it can result in identity theft, benefit denial, or deportation. This principle shapes everything:
DecisionRationale
Private storage bucketsFiles are never publicly accessible, even by accident
Signed URLs with 60s expiryLeaked URLs become useless within a minute
Submissions succeed independentlyExternal service failures don’t lose applicant data
Defense-in-depth securityMultiple layers mean single bugs don’t cause breaches
When we face a tradeoff between convenience and data protection, data protection wins. Every time.

Principle 2: Fail Secure, Not Fail Open

Security bugs are inevitable. The question is: when something goes wrong, what happens? Fail open means errors grant access. A crashed permission check lets the user through. Fail secure means errors deny access. A crashed permission check redirects to login. We chose fail secure:
// Every permission check follows this pattern
export async function requireAdmin() {
  try {
    const session = await getSession();
    const user = await getUserWithRole(session.user.id);

    if (user?.role !== 'admin' && user?.role !== 'super_admin') {
      throw new Error('Forbidden');
    }

    return user;
  } catch (error) {
    // ANY error = no access
    throw new Error('Unauthorized');
  }
}
This extends to every security decision:
  • Unknown role? Treat as applicant (lowest privilege)
  • Can’t parse redirect URL? Go to home page
  • File path looks suspicious? Reject the upload
  • Session decryption fails? Redirect to login

Principle 3: Explicit Over Magic

Frameworks love magic. Automatic code splitting, implicit data fetching, hidden configuration. Magic is convenient until it breaks—then you’re debugging something you don’t understand. Terra prefers explicit patterns: Server actions, not API routes
// Explicit: you see exactly what's happening
"use server";

export async function updateFormStatus(formId: string, status: FormStatus) {
  const user = await requireAdmin();

  await supabaseAdmin
    .from('forms')
    .update({ status })
    .eq('id', formId);

  await logAudit(user.id, 'form.status.updated', { formId, status });

  return { success: true };
}
Manual permission checks, not middleware magic
// We check permissions in every action, explicitly
export async function getSubmissions(formId: string) {
  const user = await requireFormAccess(formId); // Explicit check

  // Now we know the user has access
  const submissions = await fetchSubmissions(formId);
  return submissions;
}
Zustand stores, not Context gymnastics
// State management is explicit and inspectable
const useFormBuilder = create((set) => ({
  schema: initialSchema,
  selectedFieldId: null,

  addField: (field) => set((state) => ({
    schema: { ...state.schema, elements: [...state.schema.elements, field] }
  })),
}));
When you read Terra code, you should understand what’s happening without knowing framework internals.

Principle 4: JSON for Flexibility, Zod for Safety

Government forms are weird. Conditional sections, repeatable groups, custom validation rules, translated labels. A normalized database schema would require migrations for every new form feature. We use JSONB for flexibility:
// The entire form definition is one JSON tree
const form = {
  id: "rental-assistance",
  schema: {
    elements: [
      { type: "text", id: "name", label: { en: "Full Name" } },
      { type: "group", id: "household", elements: [...] },
      { type: "repeated", id: "income-sources", min: 1, max: 5, template: {...} }
    ]
  }
};
But JSON is stringly-typed. You can put anything in there. So we validate with Zod:
// Runtime validation catches schema errors
export const FormElementSchema = z.discriminatedUnion("type", [
  TextFieldSchema,
  ChoiceFieldSchema,
  GroupFieldSchema,
  RepeatedFieldSchema,
  // ... 20+ field types
]);

// This runs on every schema load
const validated = FormElementSchema.parse(element);
The combination gives us:
  • Flexibility: Add new field types without migrations
  • Safety: Invalid schemas fail fast with clear errors
  • Portability: Export/import forms as JSON files
  • Versioning: Store draft and published schemas side by side

Principle 5: Async by Default

External services fail. Airtable rate limits. Twilio has outages. Webhook endpoints timeout. If form submission depends on external services, applicants suffer for infrastructure problems. Our solution: submissions are synchronous, everything else is async. Benefits:
  • Applicants see success in ~300ms regardless of integration health
  • Failed operations retry automatically with exponential backoff
  • Dead-letter queue captures persistent failures for manual review
  • Integration outages don’t create support tickets

Principle 6: Server-First, Client-Lite

React traditionally runs in the browser. The server sends HTML, then JavaScript hydrates it into an interactive app. This creates security risks: secrets can leak into client bundles, API keys can be extracted, business logic can be reverse-engineered. Terra uses React Server Components (RSC) to keep sensitive logic on the server:
// This component runs ONLY on the server
export default async function SubmissionsPage({ params }) {
  // Database query happens server-side
  const submissions = await getSubmissions(params.formId);

  // Only the rendered HTML goes to the client
  return <SubmissionsTable data={submissions} />;
}
Client components exist for interactivity (form inputs, drag-drop, modals) but they receive pre-fetched data rather than making their own API calls. What stays on the server:
  • Database credentials and queries
  • Permission checks
  • File signed URL generation
  • Integration API keys
  • Encryption/decryption
What runs on the client:
  • Form input handling (react-hook-form)
  • Drag-and-drop (dnd-kit)
  • UI state (modals, tabs, accordions)
  • File uploads (after receiving signed URL)

Principle 7: White-Label as a Feature

Government agencies have strong brand requirements. Their forms should look like “their” forms, not a third-party service. Terra is designed to be invisible. Branding is configurable at every level:
  • Organization: Logo, colors, fonts
  • Folder (workspace): Override logo, custom domain
  • Form: Override any setting, form-specific domain
Custom domains are first-class:
housing.example.gov → routes to Housing folder
benefits.example.gov → routes to Benefits folder
apply.example.gov/rental → routes to specific form
Email templates are brandable:
<SubmissionReceipt
  logo={organization.branding.logo}
  primaryColor={organization.branding.primaryColor}
  supportUrl={form.supportUrl}
/>
The applicant never sees “Terra” or “Unify”—just their agency’s brand.

Principle 8: Multi-Language by Design

Government forms serve diverse populations. Language support isn’t a plugin—it’s baked into the data model. Every user-facing string is an I18nString:
type I18nString = {
  en: string;
  es?: string;
  zh?: string;
  // ... any language code
};

// Field labels, placeholders, error messages, help text
const field = {
  type: "text",
  label: { en: "Full Name", es: "Nombre Completo" },
  placeholder: { en: "Enter your name", es: "Ingrese su nombre" },
};
Translation workflow:
  1. Create form in English
  2. Add languages to form settings
  3. Auto-translate with DeepL (or translate manually)
  4. Applicants switch languages at runtime
The form builder shows all languages simultaneously—no switching modes to edit translations.

Principle 9: Audit Everything

When something goes wrong (and it will), you need to know what happened. Terra logs every significant action:
// Audit log entry structure
{
  user_id: "usr_123",
  action: "form.schema.updated",
  resource_type: "form",
  resource_id: "form_456",
  changes: {
    before: { status: "draft" },
    after: { status: "published" }
  },
  ip_address: "192.168.1.1",
  user_agent: "Mozilla/5.0...",
  created_at: "2025-01-02T10:30:00Z"
}
Every form edit, status change, permission grant, and file access is recorded. Audit logs are immutable (append-only table) and retained indefinitely. This enables:
  • Debugging: “Who changed this field label?”
  • Compliance: “Show all accesses to this applicant’s data”
  • Security: “What did this user do before we revoked access?”

Principle 10: Boring Technology (Mostly)

Innovation is expensive. Every new technology requires learning, debugging, and maintenance. Terra uses boring, proven technologies wherever possible:
LayerTechnologyWhy
FrameworkNext.jsMassive ecosystem, Vercel support
DatabasePostgreSQL30 years of battle-testing
ValidationZodSimple, composable, TypeScript-first
StylingTailwindUtility-first, no CSS debugging
StateZustandMinimal API, great DevTools
EmailReact EmailJust React, render to HTML
We reserve complexity for problems that need it:
  • Form schema (novel problem → custom solution)
  • Async queue (specific requirements → custom table)
  • AI form import (genuinely complex → novel approach)
But auth? We use WorkOS. Storage? Supabase. Email delivery? Resend. These are solved problems—we don’t need to re-solve them.

Summary

PrincipleOne-liner
Applicant Data Is SacredProtect data above all else
Fail SecureErrors deny access, never grant it
Explicit Over MagicCode should be readable without framework knowledge
JSON + ZodFlexibility with safety
Async by DefaultSubmissions succeed, integrations retry
Server-FirstKeep secrets and logic on the server
White-LabelAgencies own their brand
Multi-Language by DesignI18n isn’t an afterthought
Audit EverythingKnow what happened
Boring TechnologyInnovate only where necessary

Next Steps

Forms Engine

How the form schema works

Local Development

Get Terra running on your machine