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 through the automation_rules table. The tag deletion flow:

  1. Query automation_rules where trigger_config or action_config references the tag ID
  2. Count matching rules for the confirmation warning
  3. On confirm, delete the tag — ON DELETE CASCADE on contact_tags removes all assignments
  4. A separate cleanup step deletes orphaned automation rules that referenced the deleted tag

Automation Dependency Tracking

The Tags list page queries automation rules to count how many reference each tag. This powers the ⚡ N badge and the dependency drawer. The query checks both trigger_config and action_config JSONB fields for the tag ID.

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

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.