{"slug": "custom-fields-that-work-everywhere-without-per-field-code", "title": "Custom fields that work everywhere without per-field code", "summary": "Relaticle built custom fields so that a field defined once in its UI becomes usable across the web app, REST API, AI chat assistant, and MCP tools without a deploy or per-field integration code, the company said. The record model owns the behavior through a UsesCustomFields trait that merges custom_fields into writable attributes, while the REST API dynamically merges each tenant's custom-field validation rules so a custom field validates and writes like a native attribute. Relaticle supports text, number, currency, date, choice, contact, rich-editor, and record-link field types, with FileUpload and MarkdownEditor retired in favor of RichEditor; deactivating a field blocks writes but does not hide its existence from chat, and per-surface visibility controls are not available.", "body_md": "# Custom fields that work everywhere without per-field code\n\nWe built custom fields so a field defined in Relaticle becomes usable in the web app, REST API, AI chat assistant, and MCP tools without a deploy or per-field integration code. The key is not a separate integration for each surface. It is one write path that accepts `custom_fields`, validates it from the tenant's current definition, and persists it with the record. That sounds ordinary until an agent needs labels rather than option IDs, an API consumer needs typed values, and an empty value must mean something different from an omitted key.\n\nThe result is a deliberately small contract. Define the field in the UI. Send it with the record's other writable attributes. The application does the rest. This post covers the mechanics and a permission boundary. Deactivating a field blocks writes but does not hide its existence from chat. Per-surface visibility controls are not available.\n\n## \n\nCustom fields are not an add-on payload that each integration has to interpret independently. The record model owns the behavior through `UsesCustomFields`. The trait merges `custom_fields` into the writable attributes and persists the values when the record saves. Actions pass `custom_fields` through. They do not extract individual fields or special-case their names.\n\nThat choice makes the surface behavior boring in the useful sense. A field follows the record through the same action path whether a person edits a form, an API client sends an update, an assistant proposes an update, or an MCP tool writes it. Adding a field through the UI therefore makes it available to the API and chat immediately. No deploy follows. No developer adds a conditional branch for that one field.\n\n| Surface | What it receives | What the shared path provides | \n|---|---|---|\n| Web app form | Field input as part of the record edit | A persisted custom-field value on save | \n| REST API | `custom_fields` alongside native attributes | Dynamic validation and the normal write path | \n| AI chat assistant | Field codes and option labels in its tool schema | Label translation, validation, and an approval proposal | \n| MCP tools | The same record write surface | Custom fields available without per-field tool changes | \n\nThe distinction matters because the alternatives fail in predictable ways. A static API schema becomes stale as soon as a tenant defines a field. A bespoke AI tool per field turns configuration into deployment work. A separate persistence path can make a field work in one screen while silently disappearing from another. We chose the common model behavior as the center, then kept the individual surfaces thin.\n\n## \n\nRelaticle offers text, number, currency, date, choice, contact, rich-editor, and record-link fields. FileUpload and MarkdownEditor are retired types. Use RichEditor for formatted content and supported attachments.\n\nThe list is why a generic string map would not be enough. A select needs choices. A multi-select needs more than one choice. A date and a currency carry different validation and presentation expectations. The implementation has to preserve those differences without teaching every caller the details of every type.\n\nThe model trait provides the persistence seam. The REST request and chat schema provide the type-aware seams. This gives us a single place to define what values are valid, while each surface can still present those values in the form its user can work with.\n\nFor the complete field list and configuration help, see [the custom-field type reference](https://relaticle.com/help/custom-fields/field-types-you-can-add).\n\n## \n\nThe REST API cannot treat custom fields as unchecked metadata. Every store and update request merges the tenant's custom-field validation rules dynamically. A custom field therefore validates and writes like a native attribute, even though its definition did not exist when the API code was deployed.\n\nThis is the important direction of dependency. The request does not carry its own idea of which fields exist. The application reads the current field definition, then extends validation before it accepts the write. A new field is available because the definition is data, not because a route changed.\n\nThe response layer also avoids handing clients an implementation detail. Select and multi-select values return as `{id, label}` objects rather than raw option IDs. The ID identifies the option. The label gives the value meaning in a client response. Returning both avoids forcing an API client to invent a second lookup just to display a saved choice.\n\nThat makes the REST API useful for integrations that need a stable write contract but do not want a separate mapping layer for each tenant's schema. It also gives the API and web app the same validation boundary. We do not need to decide whether a particular custom field is an \"API field\" or a \"form field.\" It is a field on the record.\n\nThe [REST API documentation](https://relaticle.com/developers/api) is the place to start when an external system needs to read or write these records.\n\n## \n\nThe AI chat assistant does not receive a static developer reference and then infer the rest. It receives a schema built for the tenant's active fields. A schema-describer service inlines field codes and option labels into the tool schema the model sees.\n\nThat wording is intentional. A model should see the label a person uses, rather than be asked to invent or remember an option ID. It can form a request using the field code and an option label. At validation time, a translation layer maps the label back to the option ID that persistence expects.\n\n| Chat step | Representation | Reason | \n|---|---|---|\n| Schema description | Field codes and option labels | The model can use the tenant's vocabulary | \n| Validation | Labels translated to option IDs | The saved value matches the configured option | \n| Proposal card | Old-to-new diff for the field type | A person can review the intended change | \n\nThe last step is part of the product contract, not a cosmetic preview. Chat writes become pending approval cards. A human approves or discards them. Showing an old-to-new diff per field type lets that person evaluate the actual proposed change before it persists.\n\nThis makes chat safer than pretending a natural-language request is self-explanatory. \"Change the status\" is not enough information for review. The proposal has to show which custom field changes and what the replacement value is.\n\nMCP has a different write behavior. MCP tool writes commit directly, with no approval gate. The same field definition and write path still apply, but the caller must choose its control boundary accordingly. For the related design lessons behind the tool surface, read [Designing an MCP tool suite for a CRM](https://relaticle.com/blog/designing-an-mcp-tool-suite-for-a-crm), and for client setup, [connecting Claude via MCP](https://relaticle.com/blog/how-to-connect-claude-to-your-crm-with-mcp).\n\n## \n\nOne rule carries more weight for agents than it first appears to: For an optional field, explicit `null` clears its value. Multi-value fields also accept `[]`. Omitted keys leave values unchanged. Required-field validation can reject clearing.\n\n| Payload state | Result | \n|---|---|\n| Field key is absent | Leave the existing value untouched | \n| Optional field key has `null` | Clear the field | \n| Optional multi-value field has `[]` | Clear the field | \n| Field key has a value | Validate and save the value | \n\nThis distinction prevents an agent from turning incomplete context into a destructive update. An agent often knows it should change one property. It may not know the current value of every other property. If a missing key meant \"clear,\" then a partial update would erase unrelated data. If an explicit empty value meant \"leave unchanged,\" then an agent could not reliably perform a requested removal.\n\nThe contract gives each action one clear meaning. Omission means no instruction. Explicit `null` means clear an optional field. An empty array also clears a multi-value field. A concrete value means replace it. That is simple enough to express in a tool schema and precise enough to make partial record updates safe to reason about.\n\nIt also keeps the behavior aligned across the web app, API, chat, and MCP tools. Required-field validation still applies when an integration attempts to clear a value. The input shape changes by surface. The write semantics do not.\n\n## \n\nThere is a real limitation here. Deactivation blocks writes, but chat still receives inactive field codes and types. It is not a confidentiality control. We do not offer independent visibility permissions for each surface.\n\nA team cannot use deactivation to guarantee that a field disappears from every agent response. Field visibility needs its own access-control policy.\n\nThe constraint follows directly from the design's strength. One definition travels through multiple surfaces because those surfaces share the same field model. When a team needs separate field exposure policies, that policy needs a first-class representation rather than a collection of exceptions in API requests, AI schemas, and tool definitions.\n\nFor now, deactivate a field when it should reject new writes. Treat per-surface visibility controls as a separate product requirement.\n\n## \n\n\"Define once\" does not mean every surface presents the field identically. The API returns select choices as `{id, label}` objects. The chat assistant sees option labels, then translates them back to IDs. Proposal cards show type-aware old-to-new changes. Those are deliberately different interfaces for different users.\n\nIt means the field definition remains the source of truth. The web app uses it to render and save. The API uses it to validate. Chat uses it to describe an allowed action. MCP tools inherit it without a per-field code path. That is what eliminates the deploy cycle after an administrator adds a field.\n\nWe think this is the right engineering boundary for an AI-native CRM. Configurable data should remain configurable when an assistant or integration touches it. The application should not force teams to choose between custom fields and automation.\n\nIf you want to connect an external client to that same record surface, start with [the REST API documentation](https://relaticle.com/developers/api). If you are defining the field itself, use [the field-type guide](https://relaticle.com/help/custom-fields/field-types-you-can-add).\n\n[#mcp](https://relaticle.com/blog/tag/mcp)", "url": "https://wpnews.pro/news/custom-fields-that-work-everywhere-without-per-field-code", "canonical_source": "https://relaticle.com/blog/custom-fields-that-work-everywhere-without-per-field-code", "published_at": "2026-09-22 09:00:00+00:00", "updated_at": "2026-09-22 21:54:24.323207+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "ai-tools", "developer-tools"], "entities": ["Relaticle", "UsesCustomFields", "REST API", "MCP", "RichEditor", "FileUpload", "MarkdownEditor"], "alternates": {"html": "https://wpnews.pro/news/custom-fields-that-work-everywhere-without-per-field-code", "markdown": "https://wpnews.pro/news/custom-fields-that-work-everywhere-without-per-field-code.md", "text": "https://wpnews.pro/news/custom-fields-that-work-everywhere-without-per-field-code.txt", "jsonld": "https://wpnews.pro/news/custom-fields-that-work-everywhere-without-per-field-code.jsonld"}}