How the Local Contact Alias and Tagging System Works in wacli

The local contact alias and tagging system in wacli uses dedicated SQLite tables (contact_aliases and contact_tags) to store user-defined nicknames and labels, exposing full CRUD functionality through methods in internal/store/chats_contacts_groups.go and CLI commands in cmd/wacli/contacts.go.

wacli is a WhatsApp CLI client that maintains a local SQLite cache of contact information. To augment the basic contact data provided by WhatsApp, wacli implements a flexible local contact alias and tagging system that allows users to assign custom nicknames and categorical labels without modifying the underlying WhatsApp data. This system operates entirely within the local database, ensuring privacy and immediate availability for search and display operations.

Database Schema for Aliases and Tags

The schema is defined in internal/store/migrations.go and creates two independent tables that reference contacts by their JID (Jabber ID).

contact_aliases Table

The contact_aliases table stores one-to-one mappings between a contact's JID and a user-defined alias:

-- internal/store/migrations.go
CREATE TABLE IF NOT EXISTS contact_aliases (
    jid TEXT PRIMARY KEY,
    alias TEXT NOT NULL,
    notes TEXT,
    updated_at INTEGER NOT NULL
);

contact_tags Table

The contact_tags table supports many-to-many relationships, allowing multiple tags per contact:

-- internal/store/migrations.go
CREATE TABLE IF NOT EXISTS contact_tags (
    jid TEXT NOT NULL,
    tag TEXT NOT NULL,
    updated_at INTEGER NOT NULL,
    PRIMARY KEY (jid, tag)
);

Core CRUD Operations

All database operations are implemented in internal/store/chats_contacts_groups.go using upsert-style SQL statements.

Managing Contact Aliases

SetAlias creates or updates an alias for a specific JID:

// internal/store/chats_contacts_groups.go (lines 43-55)
func (d *DB) SetAlias(jid, alias string) error {
    // Implementation trims whitespace and validates non-empty input
    // Uses INSERT ... ON CONFLICT (jid) DO UPDATE ...
}

RemoveAlias deletes the alias record:

// internal/store/chats_contacts_groups.go (lines 57-60)
func (d *DB) RemoveAlias(jid string) error {
    // DELETE FROM contact_aliases WHERE jid = ?
}

Managing Contact Tags

AddTag attaches a label to a contact:

// internal/store/chats_contacts_groups.go (lines 62-73)
func (d *DB) AddTag(jid, tag string) error {
    // Normalizes tag format, validates, then upserts into contact_tags
}

RemoveTag detaches a specific label:

// internal/store/chats_contacts_groups.go (lines 75-78)
func (d *DB) RemoveTag(jid, tag string) error {
    // DELETE FROM contact_tags WHERE jid = ? AND tag = ?
}

ListTags retrieves all tags for a contact in alphabetical order:

// internal/store/chats_contacts_groups.go (lines 28-44)
func (d *DB) ListTags(jid string) ([]string, error) {
    // SELECT tag FROM contact_tags WHERE jid = ? ORDER BY tag ASC
}

Retrieving Enriched Contact Data

The GetContact method hydrates the full Contact struct by joining with the alias table and invoking ListTags:

// internal/store/chats_contacts_groups.go (lines 6-26)
func (d *DB) GetContact(jid string) (Contact, error) {
    // JOINs contacts with contact_aliases
    // Calls ListTags to populate the Tags slice
}

The Contact struct definition in internal/store/types.go includes these fields:

type Contact struct {
    JID       string
    Phone     string
    Name      string
    Alias     string   // From contact_aliases
    Tags      []string // From contact_tags
    UpdatedAt time.Time
}

The SearchContacts function in internal/store/chats_contacts_groups.go treats aliases as first-class search fields. The SQL query checks the alias column alongside native contact fields:

WHERE LOWER(COALESCE(a.alias,'')) LIKE LOWER(?)
   OR LOWER(COALESCE(c.full_name,''))   LIKE LOWER(?)
   OR LOWER(COALESCE(c.push_name,''))   LIKE LOWER(?)
   OR LOWER(COALESCE(c.phone,''))       LIKE LOWER(?)
   OR LOWER(c.jid)                      LIKE LOWER(?)

The ORDER BY clause prioritizes alias matches, ensuring that user-defined nicknames appear before auto-fetched WhatsApp names in search results.

CLI Interface for Alias and Tag Management

The command-line interface is implemented in cmd/wacli/contacts.go. Each sub-command wraps the corresponding database method:


# Set an alias

wacli contacts alias set --jid [email protected] --alias "Mom"

# Remove an alias

wacli contacts alias rm --jid [email protected]

# Add a tag

wacli contacts tags add --jid [email protected] --tag family

# Remove a tag

wacli contacts tags rm --jid [email protected] --tag family

Success returns "OK", while the --json flag enables JSON output for integration with external scripts.

Programmatic Usage Examples

Setting an Alias in Go

// Example: Assigning a nickname to a contact
if err := db.SetAlias("[email protected]", "Mom"); err != nil {
    log.Fatalf("failed to set alias: %v", err)
}

Source: SetAlias implementation in internal/store/chats_contacts_groups.go (lines 43-55).

Adding a Tag

// Example: Categorizing a contact
if err := db.AddTag("[email protected]", "family"); err != nil {
    log.Fatalf("failed to add tag: %v", err)
}

Source: AddTag in internal/store/chats_contacts_groups.go (lines 62-73).

Retrieving Full Contact Data

// Example: Loading contact with enriched metadata
contact, err := db.GetContact("[email protected]")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Name: %s, Alias: %s, Tags: %v\n", 
    contact.Name, contact.Alias, contact.Tags)

Source: GetContact in internal/store/chats_contacts_groups.go (lines 6-26).

Summary

  • wacli implements a local contact alias and tagging system using SQLite tables contact_aliases and contact_tags that operate independently of WhatsApp's native contact data.
  • Aliases provide one-to-one nicknames for JIDs, while tags offer many-to-many categorical labeling.
  • CRUD operations (SetAlias, RemoveAlias, AddTag, RemoveTag, ListTags) reside in internal/store/chats_contacts_groups.go and use upsert-style SQL for atomic updates.
  • Data hydration occurs in GetContact, which joins the aliases table and populates the Tags slice via ListTags, ensuring the Contact struct always contains enriched metadata.
  • Search integration treats aliases as primary fields, prioritizing user-defined nicknames over native names in result ordering.
  • CLI exposure in cmd/wacli/contacts.go provides thin wrappers for shell scripting and JSON output.

Frequently Asked Questions

How does wacli store contact aliases and tags?

wacli stores aliases and tags in separate SQLite tables named contact_aliases and contact_tags within the local database. The contact_aliases table uses the JID as a primary key for one-to-one nickname storage, while contact_tags uses a composite primary key of (jid, tag) to support multiple labels per contact. This schema is defined in internal/store/migrations.go and allows the system to enrich WhatsApp contact data without modifying the native WhatsApp fields.

Can I search contacts by their alias or tags?

Yes, the SearchContacts function in internal/store/chats_contacts_groups.go explicitly includes the alias column in its search predicate. The SQL query checks WHERE LOWER(COALESCE(a.alias,'')) LIKE LOWER(?) alongside native fields like full_name and push_name. Furthermore, the ORDER BY clause prioritizes alias matches, ensuring that contacts with user-defined nicknames appear at the top of search results. Tags can be filtered programmatically by retrieving contacts and checking their Tags slice.

What happens if I set an alias for a contact that already has one?

The SetAlias method implements an upsert pattern using SQLite's INSERT ... ON CONFLICT (jid) DO UPDATE syntax. When you call SetAlias with a JID that already exists in the contact_aliases table, the existing alias is replaced with the new value and the updated_at timestamp is refreshed. This ensures that setting an alias is always an atomic operation that either creates a new record or updates an existing one without requiring explicit deletion first.

Are contact aliases and tags available when exporting or listing contacts?

Yes, because the Contact struct in internal/store/types.go includes both Alias (string) and Tags ([]string) fields, any operation that retrieves contacts—including GetContact and SearchContacts—automatically hydrates these fields. When using CLI commands like wacli contacts list with the --json flag, the exported JSON objects include the alias and tags properties. This ensures that user-defined metadata persists across exports, backups, and integration scripts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →