From d477a7a429a4cf9fd27da8b88a39b860c54b4d72 Mon Sep 17 00:00:00 2001
From: Arnas Donauskas
Date: Thu, 11 Jun 2026 15:23:49 +0300
Subject: [PATCH] feat(automations): support custom contact fields in Update
Contact Field action
The "Update Contact Field" automation action could only write the built-in
name/email/company columns. This wires up the existing custom_fields /
contact_custom_values tables so automations can read and update arbitrary
custom fields, and adds the missing UI to define them.
- Add a "Custom fields" manager dialog on the Contacts page (admin+ gated)
to create / rename / delete field definitions. Inserts carry account_id.
- Load account custom fields into the automation builder's ResourcesContext
and offer them in the Field dropdown, encoded as `custom:`. A saved
field that's since been deleted is preserved as an "(unknown field)"
option so editing doesn't silently drop it.
- Engine: interpolate the value ({{ vars.* }} / {{ message.text }}) for all
fields, and for `custom:` verify the field belongs to the automation's
account (defense in depth) before upserting into contact_custom_values.
- Tests: cover the custom-value upsert, variable interpolation, and
cross-account rejection.
Closes #235
Co-Authored-By: Claude Opus 4.8 (1M context)
---
src/app/(dashboard)/contacts/page.tsx | 22 ++
.../automations/automation-builder.tsx | 70 ++++-
.../contacts/custom-fields-manager.tsx | 275 ++++++++++++++++++
src/lib/automations/engine.test.ts | 88 ++++++
src/lib/automations/engine.ts | 36 ++-
src/types/index.ts | 10 +
6 files changed, 486 insertions(+), 15 deletions(-)
create mode 100644 src/components/contacts/custom-fields-manager.tsx
diff --git a/src/app/(dashboard)/contacts/page.tsx b/src/app/(dashboard)/contacts/page.tsx
index 20c685f..50182ba 100644
--- a/src/app/(dashboard)/contacts/page.tsx
+++ b/src/app/(dashboard)/contacts/page.tsx
@@ -40,10 +40,12 @@ import {
Users,
ChevronLeft,
ChevronRight,
+ SlidersHorizontal,
} from 'lucide-react';
import { ContactForm } from '@/components/contacts/contact-form';
import { ContactDetailView } from '@/components/contacts/contact-detail-view';
import { ImportModal } from '@/components/contacts/import-modal';
+import { CustomFieldsManager } from '@/components/contacts/custom-fields-manager';
import { useCan } from '@/hooks/use-can';
import { GatedButton } from '@/components/ui/gated-button';
@@ -56,6 +58,7 @@ interface ContactWithTags extends Contact {
export default function ContactsPage() {
const supabase = createClient();
const canEdit = useCan('send-messages');
+ const canEditSettings = useCan('edit-settings');
const [contacts, setContacts] = useState([]);
const [loading, setLoading] = useState(true);
@@ -70,6 +73,7 @@ export default function ContactsPage() {
const [detailOpen, setDetailOpen] = useState(false);
const [detailContactId, setDetailContactId] = useState(null);
const [importOpen, setImportOpen] = useState(false);
+ const [customFieldsOpen, setCustomFieldsOpen] = useState(false);
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
const [deleteTarget, setDeleteTarget] = useState(null);
const [deleting, setDeleting] = useState(false);
@@ -219,6 +223,16 @@ export default function ContactsPage() {
+ {canEditSettings && (
+
+ )}
+ {/* Custom Fields Manager (admin+) */}
+ {canEditSettings && (
+
+ )}
+
{/* Delete Confirmation */}