Technical Reference
Tags

Tags — Technical Reference

This page covers the data models, uniqueness constraints, and security policies behind the Tags feature.

Data Models

tags

ColumnTypeDescription
iduuidPrimary key
workspace_iduuidFK → workspaces.id — tenant isolation (ON DELETE CASCADE)
nametextUser-visible tag label
colortextHex color code for visual identification
created_byuuidFK → user_profiles.id — who created the tag
created_attimestamptzWhen the tag was created

Unique constraint: UNIQUE(workspace_id, LOWER(name)) — enforces case-insensitive uniqueness per workspace.

contact_tags (Join Table)

ColumnTypeDescription
iduuidPrimary key
workspace_iduuidFK → workspaces.id — tenant isolation (ON DELETE CASCADE)
contact_iduuidFK → contacts.id (ON DELETE CASCADE)
tag_iduuidFK → tags.id (ON DELETE CASCADE)
created_attimestamptzWhen the tag was applied to the contact

Unique constraint: UNIQUE(contact_id, tag_id) — prevents duplicate tag assignments.

Architecture

Case-Insensitive Deduplication

Tag name uniqueness is enforced at the database level with a functional unique index on LOWER(name) scoped to workspace_id. This means:

  • Creating a tag named VIP when vip already exists will match the existing tag
  • During CSV import, seedTagsFromImport() uses ILIKE matching to find existing tags
  • The first-seen casing is preserved; subsequent imports with different casing reuse the existing record without renaming it

Automation Cascade Deletion

When a tag is deleted, all automation rules that reference it are cascade-deleted from the automations table. The tag deletion flow:

  1. Retrieve the count of rules in automations where trigger_id or action_id equals the tag ID (for the frontend warning dialog).
  2. On confirmation, delete all matching rules from the automations table.
  3. Delete the tag record from the tags table (cascading foreign keys on contact_tags handle removal from contacts).

Automation Dependency Tracking

The Tags list page queries automation rules to count how many reference each tag, powering the ⚡ N badge and the dependency drawer. The check is performed via the shared getComponentDependencies server action, which filters rules based on the trigger_id and action_id columns in the automations table.

Broadcast Audience Targeting

Broadcasts use tags for audience selection. The calculateAudienceCount() function filters contacts by:

  1. Include tags — contacts must have at least one of the included tags
  2. Exclude tags — contacts must not have any of the excluded tags
  3. Subscription eligibility — contacts must be subscribed with no active suppressions

CSV Import Tag Handling

During CSV import (seedTagsFromImport()):

  1. Parse tag1, tag2, tag3 columns from each row
  2. For each unique tag name, check for existing tags using ILIKE matching
  3. Create new tags for any that don't exist, using the first-seen casing
  4. Apply the global tag (if set) to all contacts in the batch
  5. Insert contact_tags records, skipping duplicates with ON CONFLICT DO NOTHING

Tag Color Picker UI Accessibility

The color picker UI in CreateTagDialog, EditTagDialog, and TagSelectDialog provides enhanced accessibility:

  • Selected State Contrast: Shifts styling to bold, dark text and applies a dynamic border matching the active tag color.
  • Background Color Tint: Adds a matching 7% opacity background tint behind the selected active option.
  • Accessibility Checkmark: Renders a solid white checkmark inside the selected color circle to ensure clarity without relying solely on color hue.

Security

RLS Policies

OperationPolicy
SELECTWorkspace members can view tags in their workspace
INSERTWorkspace members can create tags in their workspace
UPDATEWorkspace members can edit tags in their workspace
DELETEWorkspace members can delete tags in their workspace

The same policies apply to the contact_tags join table.


Performance & Hydration Optimization

Server/Client Page Split & Lazy Loading

  • Server Component (pages/tags/page.tsx): Pre-fetches active workspace, tags list, and automation dependency counts on the server.
  • Client Component (tags-client.tsx): Manages the tag grid rendering, contact counts, delete triggers, and local search.
  • Dynamic imports: Lazily loads CreateTagDialog and EditTagDialog with ssr: false to defer chunk loading until user interaction.
  • Sync Gates: Integrates isFirstQueryRef mount guards and initialWorkspaceIdRef state sync gates.

Performance & Optimistic UI Updates

Optimistic Tag Additions

To provide an instant response when managing tags on a contact, the CRM implements optimistic UI updates:

  • Instant Dialog Dismissal: The tag selection dialog (TagSelectDialog) closes immediately when a user selects a tag, removing async blocking states.
  • Callbacks: Uses onAddTagOptimistic and onRollbackTag callbacks to update local UI states instantly while execution runs in the background.
  • Error Rollback: If the background API save fails, the system rolls back the client-side state to the previous snapshot and displays an error toast notification.
  • Badge Order Preservation: Tag badge derivations are sorted alphabetically using localeCompare to prevent badges from dynamically shifting or "jumping" when transitioning from the optimistic client state to the synced database state.