Skip to content

fix: Handle dots in custom field names for segments, workflows, and templates#347

Open
chamby wants to merge 1 commit intouseplunk:nextfrom
chamby:fix/dotted-custom-field-names
Open

fix: Handle dots in custom field names for segments, workflows, and templates#347
chamby wants to merge 1 commit intouseplunk:nextfrom
chamby:fix/dotted-custom-field-names

Conversation

@chamby
Copy link
Copy Markdown

@chamby chamby commented Apr 9, 2026

Summary

  • Custom fields with dots in their names (e.g., prefix.key) were silently broken across segment filtering, workflow conditions, and email template rendering because the code split on . to build path lookups, treating them as nested JSON paths instead of literal flat keys
  • Fixes SegmentService, WorkflowExecutionService, and template.ts to preserve dots in custom field names
  • Backwards-compatible: fields without dots continue to work identically

Fixes #346

Changes

SegmentService.buildJsonFieldCondition — Use [jsonPath] (single-element array) instead of jsonPath.split('.'). Contact data is always a flat JSON object, so the Prisma JSON path should always be a single key.

WorkflowExecutionService.resolveField — Split only on the first . to separate the top-level field name (data) from the custom field name (prefix.key), instead of splitting on every ..

template.ts getValue — Replace split('.').reduce() with a recursive function that tries direct key lookup at each level before descending via dot-splitting. For data.prefix.key, it enters data, then finds prefix.key as a literal key.

Test plan

  • Verify dynamic segments match contacts with dotted custom field names (e.g., data.app.plan)
  • Verify workflow CONDITION steps correctly evaluate dotted custom fields
  • Verify email templates render {{data.prefix.key}} correctly
  • Verify existing non-dotted fields (data.plan, data.firstName) continue to work

🤖 Generated with Claude Code

…emplates

Custom fields with dots in their names (e.g., "prefix.key") were broken
because the code split on "." to build path lookups, interpreting them as
nested paths instead of literal flat JSON keys.

- SegmentService: Use [jsonPath] instead of jsonPath.split('.') since
  contact data is always a flat JSON object
- WorkflowExecutionService: Split only on the first dot to separate the
  top-level field from the custom field name
- template.ts: Use recursive first-dot splitting with direct key lookup
  before descending into nested objects

Fixes useplunk#346

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes handling of custom contact fields whose names contain literal dots (e.g. app.plan) across segment filtering, workflow condition evaluation, and template rendering, where dot-splitting previously treated them as nested JSON paths.

Changes:

  • Segment filtering: adjust Prisma JSON path construction for contact.data field filters.
  • Workflow conditions: adjust field resolution logic to preserve dots in custom field names.
  • Template rendering: update variable-path resolution to prefer literal key lookup before descending into nested objects.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
packages/shared/src/template.ts Updates template variable path lookup to support dotted custom field keys while still allowing nested object traversal.
apps/api/src/services/WorkflowExecutionService.ts Changes workflow condition field resolution to preserve dots in custom field names.
apps/api/src/services/SegmentService.ts Changes JSON path handling for segment filters targeting contact.data.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1114 to +1122
// Split only on the first dot to separate the top-level field (e.g., "data")
// from the custom field name. This preserves dots in custom field names
// (e.g., "data.prefix.key" → ["data", "prefix.key"]).
const firstDotIndex = normalizedField.indexOf('.');
const parts =
firstDotIndex === -1
? [normalizedField]
: [normalizedField.substring(0, firstDotIndex), normalizedField.substring(firstDotIndex + 1)];

Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveField now splits only on the first dot, which breaks existing nested field paths like data.profile.tier (it will look for a literal key profile.tier under data). There are tests relying on nested access (e.g. WorkflowConditions.operators.test.ts uses data.profile.tier). Consider switching to a recursive resolver that (1) tries direct key lookup for the full remaining path and (2) otherwise descends by splitting on the next dot, so both nested objects and dotted custom-field keys are supported.

Copilot uses AI. Check for mistakes.
Comment on lines +821 to +824
// Use the entire jsonPath as a single path element.
// Contact data is a flat JSON object, so field names like "prefix.key"
// must be treated as literal keys, not nested paths.
const path = [jsonPath];
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing Prisma JSON path from jsonPath.split('.') to [jsonPath] removes support for querying nested JSON in contact.data (e.g. filtering on data.profile.tier previously produced path: ['profile','tier'], but will now look for a literal key profile.tier). Since contact.data is a Json column and other parts of the codebase/tests use nested objects, consider preserving backwards compatibility by generating filters that can match both interpretations when the field contains dots (e.g., OR between [jsonPath] and jsonPath.split('.')), or introducing an explicit escaping/syntax to disambiguate literal-dot keys vs nested paths.

Copilot uses AI. Check for mistakes.
Comment on lines +1114 to +1122
// Split only on the first dot to separate the top-level field (e.g., "data")
// from the custom field name. This preserves dots in custom field names
// (e.g., "data.prefix.key" → ["data", "prefix.key"]).
const firstDotIndex = normalizedField.indexOf('.');
const parts =
firstDotIndex === -1
? [normalizedField]
: [normalizedField.substring(0, firstDotIndex), normalizedField.substring(firstDotIndex + 1)];

Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is intended to support custom field names containing dots, but there doesn’t appear to be test coverage exercising a dotted key (e.g. contact.data { "app.plan": "premium" } with condition field data.app.plan). Adding an integration test in the existing Workflow condition test suite would help prevent regressions and ensure both dotted-key and nested-path cases behave as expected.

Copilot uses AI. Check for mistakes.
Comment on lines +821 to +824
// Use the entire jsonPath as a single path element.
// Contact data is a flat JSON object, so field names like "prefix.key"
// must be treated as literal keys, not nested paths.
const path = [jsonPath];
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are extensive SegmentService operator tests, but none seem to cover dotted custom field keys (e.g. contact.data { "app.plan": "premium" } with filter field data.app.plan). Adding a test case here would validate the intended fix and prevent regressions for both dotted-key and nested-path behavior.

Copilot uses AI. Check for mistakes.
@chamby
Copy link
Copy Markdown
Author

chamby commented Apr 11, 2026

I wasn't sure if you supported nested field paths, apparently you do, but maybe only for internal data structures. User-facing custom field data does not seem to allow nesting, but there is no prevention or escaping of the dot notation. I leave the solution up to your judgement.

@driaug
Copy link
Copy Markdown
Member

driaug commented Apr 13, 2026

The goal is definitely to allow nesting and the . is a correct separator imo. I haven't looked into it further to see what else would be needed to support it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Custom fields with dots in names break segment filtering, workflow conditions, and template rendering

3 participants