Skip to main content

Overview

Terra’s workspace system lets you organize forms into logical groups—by program, department, or any structure that fits your workflow. Each workspace can have its own custom domain, branding, and analytics configuration.
Workspaces are organization-wide. All team members see the same workspace structure.

Creating Folders

  1. Navigate to the Forms page
  2. Click the + button in the folder sidebar
  3. Enter a folder name (e.g., “Housing Connector”)
  4. Choose a color for visual identification
  5. Click Create Folder
Create folder dialog

Moving Forms to Folders

Drag and Drop

The fastest way to organize forms:
  1. Hover over a form row to reveal the drag handle (⋮⋮)
  2. Drag the form to the target folder in the sidebar
  3. Drop to complete the move
You can drag forms to “Uncategorized” to remove them from a folder.

Bulk Organization

For large-scale reorganization, consider:
  • Creating folders first based on your program structure
  • Filtering by “Uncategorized” to see unorganized forms
  • Dragging related forms to their folders in batches

Folder Views

All Forms

Shows every form in your workspace regardless of folder. Use this view to see your complete form inventory.

Uncategorized

Forms that haven’t been assigned to any folder. New forms start here by default.

Custom Folders

Click any folder to filter the view to only forms in that folder. The form count badge updates in real-time.

Managing Folders

Rename a Folder

  1. Hover over the folder in the sidebar
  2. Click the menu
  3. Select Rename
  4. Update the name and/or color
  5. Click Save Changes

Delete a Folder

  1. Hover over the folder in the sidebar
  2. Click the menu
  3. Select Delete
  4. Confirm deletion
Deleting a folder moves all contained forms to “Uncategorized”. Forms are never deleted when removing a folder.

Folder Colors

Choose from 8 colors to visually distinguish folders:
ColorHexSuggested Use
Indigo#6366F1Default / General
Blue#3B82F6Active programs
Emerald#10B981Approved / Complete
Amber#F59E0BNeeds attention
Rose#F43F5EUrgent / Priority
Purple#A855F7Special programs
Slate#64748BArchive / Inactive
Cyan#06B6D4Pilot programs

Best Practices

Create one folder per program (e.g., “Housing Connector”, “Emergency Rental Assistance”, “Utility Assistance”). This maps to how most government teams think about their work.
Establish naming conventions early. Consider prefixes like department codes (e.g., “HSD - Housing”, “HSD - Utilities”) for large organizations.
Create an “Archive” or “Inactive” folder for programs that are no longer accepting applications but need to be preserved for record-keeping.
Terra uses a flat folder structure (no nested folders) intentionally. This keeps navigation simple and prevents organizational complexity.

Custom Code Injection

Workspaces support custom code injection for analytics, support widgets, and other third-party integrations. Code added to a workspace is automatically injected into all public form pages within that workspace.
Custom code injection is a powerful feature intended for engineering teams. Malformed scripts can break form pages. Only add trusted, tested code.

Use Cases

IntegrationInjection PointExample
Fathom AnalyticsHead<script src="https://cdn.usefathom.com/script.js" data-site="XXXXX" defer></script>
Google AnalyticsHeadGA4 script tag
IntercomBodyIntercom messenger widget
Crisp ChatBodyCrisp chat widget
Custom meta tagsHeadOG tags, viewport settings

Configuration

  1. Navigate to the Forms page
  2. Click on a workspace in the sidebar
  3. Scroll to the Custom Code section in the workspace settings panel
  4. Add code to the appropriate field:
    • Head Code: Injected into <head> — use for analytics, meta tags, preload scripts
    • Body Code: Injected before </body> — use for chat widgets, scripts needing DOM access
  5. Click Save Custom Code

Script Loading Strategies

Terra uses Next.js Script component with optimized loading strategies:
Injection PointStrategyWhen it Loads
Head CodeafterInteractiveAfter page becomes interactive
Body CodelazyOnloadAfter all resources loaded
This ensures custom scripts don’t block initial page rendering.

Example: Fathom Analytics

<!-- Head Code -->
<script src="https://cdn.usefathom.com/script.js" data-site="ABCDEFGH" defer></script>

Example: Intercom Widget

<!-- Body Code -->
<script>
  window.intercomSettings = {
    api_base: "https://api-iam.intercom.io",
    app_id: "your-app-id"
  };
</script>
<script>
  (function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');ic('update',w.intercomSettings);}else{var d=document;var i=function(){i.c(arguments);};i.q=[];i.c=function(args){i.q.push(args);};w.Intercom=i;var l=function(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src='https://widget.intercom.io/widget/your-app-id';var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);};if(document.readyState==='complete'){l();}else if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})();
</script>

Technical Reference

Database Schema

-- Workspaces table (stored as "folders" in database)
CREATE TABLE folders (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name TEXT NOT NULL,
  color TEXT DEFAULT '#6366F1',
  custom_head_code TEXT,        -- Analytics, meta tags
  custom_body_code TEXT,        -- Chat widgets, DOM scripts
  custom_domain TEXT,           -- Optional custom domain
  root_redirect TEXT,           -- Redirect URL for domain root
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- Foreign key on forms table
ALTER TABLE forms
ADD COLUMN folder_id UUID REFERENCES folders(id) ON DELETE SET NULL;

Server Actions

ActionDescriptionParameters
getWorkspaces()Fetch all workspaces with form counts
createWorkspace(name, color)Create a new workspacename: string, color?: string
updateWorkspace(id, updates)Update workspace settingsid: string, updates: { name?, color?, custom_head_code?, custom_body_code?, root_redirect? }
deleteWorkspace(id)Delete workspace (unlinks forms)id: string
moveFormToWorkspace(formId, workspaceId)Move form to workspaceformId: string, workspaceId: string | null
getWorkspaceCustomCode(workspaceId)Fetch custom code for a workspaceworkspaceId: string

Component Architecture

app/(dashboard)/forms/
├── page.tsx          # Server component, fetches forms + folders
├── client.tsx        # DndContext provider, split view layout
└── ...

components/dashboard/
└── folder-sidebar.tsx  # Droppable folder list, CRUD dialogs

Drag and Drop

Terra uses @dnd-kit/core for drag-and-drop:
  • Draggable: Form rows in the table (useDraggable)
  • Droppable: Folder items in the sidebar (useDroppable)
  • Sensors: Mouse (8px activation) and Touch (200ms delay)
// Draggable form row
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
  id: form.id,
  data: { form },
});

// Droppable folder item
const { setNodeRef, isOver } = useDroppable({
  id: folderId ?? "uncategorized",
});

Troubleshooting

The drag handle appears on hover. Ensure you’re hovering over the form row, not just the form name link.
Folder counts update optimistically. If counts seem wrong, refresh the page to sync with the database.
Ensure you have appropriate permissions. The delete action unlinks all forms first, which requires write access to the forms table.