Skip to main content

Tideswell vs Terra

Terra is a modern reimagining of Tideswell, built with lessons learned from years of operating government intake infrastructure.
Terra isn’t a fork of Tideswell—it’s a clean-slate rebuild that achieves feature parity while fundamentally rethinking the architecture. This page explains the relationship, the differences, and why we made the choices we did.

The Relationship

Tideswell was the original government intake platform, built to handle benefit applications at scale. It served its purpose well, processing hundreds of thousands of applications. But like all software, it accumulated technical debt. Terra is the successor. Same mission, new architecture. We took what worked in Tideswell, fixed what didn’t, and rebuilt from first principles with modern tools.

Feature Parity Status

Terra has achieved full feature parity with Tideswell. Here’s how the features map:

Core Intake Features

FeatureTideswellTerraNotes
Form builderDrag-dropDrag-dropTerra uses dnd-kit, modern UX
Conditional logicJSON rulesJSON conditionsSame power, cleaner syntax
Multi-languageTerra has auto-translation
File uploadsS3Supabase + DriveTerra adds Google Drive option
SubmissionsMongoDBPostgreSQLTerra adds encryption at rest
Status trackingSame statuses
Applicant portalTerra rebuilt with RSC

Form Lifecycle

FeatureTideswellTerraNotes
Draft/Published versionsTerra stores both schemas
Scheduled publishingautoPublishAtpublished_atSame feature, clearer naming
Scheduled closingautoCloseAtcloses_atSame feature, clearer naming
Form freezingfrozen flagfrozen flagPrevents edits after first submission
Outcome fieldApplicant-visible result text

Identity & Verification

FeatureTideswellTerraNotes
ID verificationPlaidPlaidSame integration
Bank verificationPlaidPlaidSame integration
Address lookupSmartySmartySame integration
Anonymous formsis_anonymous flag
Applicant identityAccount modelapplicants tableTerra normalizes across forms

Integrations

FeatureTideswellTerraNotes
WebhooksCustom triggersWebhook configsTerra adds event history
Airtable syncTerra adds connection testing
Email notificationsCustomResendTerra uses React Email templates
SMS notificationsTwilioTwilioSame integration
Google DriveFile uploads to shared drives

Compliance & Audit

FeatureTideswellTerraNotes
Audit loggingMongoDB eventsaudit_logs tableTerra is more comprehensive
Geolocation captureIP + CloudflareIP + Vercel/CFSame data, anonymized
PII encryptionPartialField-levelTerra encrypts at submission
Data retentionManualSoft delete + cleanupTerra has automated cleanup

Architectural Differences

Database: MongoDB → PostgreSQL

Why we changed:
MongoDB (Tideswell)PostgreSQL (Terra)
Flexible schemaFlexible via JSONB
Document queriesSQL + JSONB operators
Manual relationsForeign keys
GridFS for filesSupabase Storage
Mongoose ODMRaw SQL + DAL
PostgreSQL gives us:
  • ACID transactions for multi-step operations
  • Foreign key constraints that prevent orphaned records
  • Row Level Security at the database layer
  • JSONB for schema flexibility without sacrificing structure

Auth: Custom → WorkOS

Why we changed: Tideswell rolled its own authentication. It worked, but required ongoing maintenance for security patches, session management, and OAuth providers. Terra uses WorkOS because:
  • SSO (SAML, OIDC) is built-in for enterprise clients
  • MFA is handled by the provider
  • Security patches are their responsibility
  • We focus on intake, not auth

Queue: File + Memory → Database

Why we changed: Tideswell used a file-based queue with in-memory fallback. This caused problems:
  • Jobs lost on server restart
  • No visibility into queue state
  • Hard to debug failed operations
Terra uses a database-backed queue:
-- Every async operation is a row
CREATE TABLE async_operations (
  id UUID PRIMARY KEY,
  operation_type TEXT,
  status TEXT DEFAULT 'pending',
  payload JSONB,
  attempts INTEGER DEFAULT 0,
  max_attempts INTEGER DEFAULT 3,
  next_attempt_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ DEFAULT NOW()
);
Benefits:
  • Durable: Jobs survive restarts
  • Visible: Query the table to see queue health
  • Debuggable: Failed jobs have error messages
  • Scalable: Multiple workers can claim jobs

Frontend: Express + EJS → Next.js + RSC

Why we changed:
Express/EJS (Tideswell)Next.js/RSC (Terra)
Server-rendered HTMLServer Components
jQuery for interactivityReact for interactivity
Manual bundlingAutomatic code splitting
API routes for dataServer Actions
Separate admin appUnified app
React Server Components give us:
  • Type safety across client/server boundary
  • Automatic code splitting per route
  • Streaming for large pages
  • Server Actions that are just function calls

What We Kept

Not everything changed. These patterns worked well in Tideswell and survived into Terra:

JSON Form Schema

Both platforms store form definitions as JSON. The schema structure is similar:
// Works in both platforms (with minor differences)
{
  elements: [
    { type: "text", id: "name", label: { en: "Full Name" } },
    { type: "choice", id: "program", options: [...] },
    { type: "group", id: "address", elements: [...] }
  ]
}

Async-First Submission Flow

Both platforms follow the same pattern:
  1. Validate and save submission (sync)
  2. Queue integrations (async)
  3. Process queue (background worker)
This ensures submissions never fail due to integration issues.

Multi-Language as Core

Both platforms treat translations as first-class citizens. Every label, error message, and piece of content supports multiple languages through the I18nString pattern.

Audit Everything

Both platforms log every significant action. The implementation differs (MongoDB events vs PostgreSQL table), but the philosophy is identical.

Migration Path

Moving from Tideswell to Terra isn’t automatic—the data models are different enough that a migration script is required. Key mappings:
TideswellTerra
Form documentforms row + JSONB schema
Submission documentsubmissions row + encrypted JSONB data
Account documentapplicants row
WebhookTrigger documentwebhook_configs row
WebhookEvent documentwebhook_events row
Question embeddedElement in schema JSONB
The form schema itself is largely compatible—Terra can import Tideswell form definitions with minor transformations.

Why Not Fork?

We could have forked Tideswell and incrementally improved it. We didn’t because:
  1. Tech debt compounds: Tideswell’s architecture made certain improvements expensive. Changing the database, auth system, or queue would have required touching every file.
  2. Clean slate enables experimentation: We could try RSC, Zustand, Zod, and other modern tools without worrying about breaking existing code.
  3. Documentation opportunity: A fresh codebase meant we could document everything from day one, rather than trying to document organic growth.
  4. Type safety throughout: TypeScript in a greenfield project is trivial. TypeScript retrofitted onto a JavaScript codebase is painful.
The tradeoff: we lost Tideswell’s battle-tested edge cases. But we gained a codebase that’s maintainable for the next decade.

Going Forward

Terra is now the primary platform. Tideswell remains operational for existing deployments, but new features land in Terra first. Our commitment:
  • Feature parity: Anything Tideswell can do, Terra can do
  • Migration support: We’ll help teams move from Tideswell to Terra
  • Shared learnings: Bugs found in Tideswell inform Terra’s design

Summary

AspectTideswellTerra
DatabaseMongoDBPostgreSQL
FrameworkExpress + EJSNext.js + RSC
AuthCustomWorkOS
QueueFile + MemoryDatabase
LanguageJavaScriptTypeScript
StateReduxZustand
FormsFormikreact-hook-form
StylingSCSSTailwind
Same mission. Modern architecture. Lessons learned.

Next Steps