In the previous article, we explored how OneEntry allows you to manage data structure without changing frontend code: adding attributes, grouping them using markers, and building a dynamic interface driven by the model. This provides flexibility and makes it possible to evolve the product without constant deployments. However, once data becomes dynamic, the focus shifts to ensuring correctness and maintaining predictable system behavior.
If fields can be freely added and modified, who guarantees that invalid values will not end up in them? Where should rules for required fields, formats, and value ranges live? How can UI behavior be controlled through the data model without reintroducing duplicated logic into frontend code?
In a mature headless architecture, responsibility for data integrity cannot reside solely in the interface. It must be embedded in the model itself. In OneEntry, this role is fulfilled by validators and additional attribute fields — mechanisms that allow you to manage data quality and UI behavior at the architectural level rather than at the level of individual components.
Validators as Part of the Data Model
In most projects, validation logic is implemented on the frontend: required fields, email format checks, minimum string length, numeric ranges. This works well at the beginning because the logic sits close to the form, errors are immediately visible to the user, and developers do not have to think about deeper levels of control.
In practice, the problem appears later. If validation exists only in the interface, the data remains fundamentally unprotected. The API can be called directly, bypassing the UI. The frontend itself may change — a mobile application might be introduced, another client added, or an integration with an external system implemented. As a result, validation logic starts to be duplicated across different codebases, diverges over time, and gradually loses consistency. In such a model, responsibility for data integrity becomes fragmented, and the architecture grows dependent on a specific interface implementation.
In OneEntry, validators are defined at the data model level. Validation rules become part of the structure itself rather than an additional layer placed on top of it. This means correctness is enforced regardless of which client interacts with the API. This is where the architectural shift occurs: the model stops being a passive container of fields and begins to define the rules under which data can exist within the system.
Form Validators: Retrieving Configuration from the API
Let us look at a practical example. Suppose the system contains a form called contact_us. On the frontend, we do not define its structure manually. Instead, we retrieve its configuration from the API through the SDK:
const form = await api.Forms.getFormByMarker("contact_us", "en_US");
The response contains an attributes array, and each attribute already includes its validators as part of the model:
{
"identifier": "contact_us",
"attributes": [
{
"type": "string",
"marker": "email",
"validators": {
"requiredValidator": { "strict": true },
"emailInspectionValidator": true
}
},
{
"type": "string",
"marker": "name",
"validators": {
"requiredValidator": { "strict": true },
"stringInspectionValidator": {
"minLength": 2,
"maxLength": 100
}
}
}
]
}
This means the frontend receives not just a list of fields, but a fully declarative model with embedded rules. Based on this model, the form can be generated automatically, a validation schema can be assembled dynamically, required fields can be highlighted, and regular expressions or constraints can be applied for client-side validation. The interface becomes an interpreter of configuration rather than a source of business logic.
However, the key point is not the convenience of form generation. Even if the frontend fails to validate the data for any reason or is only partially implemented, OneEntry will still enforce the validators when the data is saved. Validation is embedded in the model and executed at the system level, not at the level of a specific client. This is what distinguishes a declarative architecture from a merely dynamic UI.
Form Submission and Server-Side Validation
Form data is submitted via FormData.postFormsData:
const body = {
formIdentifier: "contact_us",
formModuleConfigId: 2,
moduleEntityIdentifier: "blog",
replayTo: null,
status: "sent",
formData: [
{ marker: "email", type: "string", value: "wrong-email" },
{ marker: "name", type: "string", value: "J" }
]
};
const result = await api.FormData.postFormsData(body);
In this example, invalid values are intentionally passed: an email without a valid format and a name string that does not meet the minimum length requirement. If the data violates the rules defined in the model, the record is simply not accepted. The API returns an error, and the object is not stored in the system.
It is important to understand the boundary of responsibility here. The frontend may perform preliminary validation — this improves UX and allows errors to be displayed before submission. But the final decision always belongs to the data model, because it defines which values are acceptable and ultimately guarantees the integrity of the storage layer.
As a result, the API remains protected regardless of the client. It does not matter whether the request comes from a web interface, a mobile application, or a direct API call. Validators function not as a supporting layer for the UI, but as part of the persistence mechanism itself. This transforms the model from a passive description of structure into an active mechanism for enforcing data quality.
An Attribute Is More Than Just a Field
So far, we have discussed validation as a mechanism for controlling values. In OneEntry, however, an attribute is structured much more deeply than a typical “form field” or a database column. It is not limited to storing a value.
An attribute includes a marker that defines its identifier within the model, a type that specifies the data format, a position that affects display order, additionalFields for extending behavior, and metadata required for client-side interpretation. This makes it more than a simple data container, turning it into a structural unit with its own rules and context.
This approach changes how the model is perceived. An attribute becomes an independent entity capable of influencing UI behavior and rendering logic, not just holding a value. To see how this works in practice, let us look at a concrete example below.
Example: Image Attribute with Fallback Logic
Let us look at a more illustrative scenario. A product has an attribute called card_image of type image. The requirement is simple: if the main image is missing, a fallback image should be used; if that is also unavailable, a text message should be displayed instead. In a traditional architecture, this is typically handled in the frontend through conditional checks and hardcoded values. As a result, rendering logic ends up living in the code rather than in the data model.
In OneEntry, this problem is approached differently. Additional fields such as fallback_image and fallback_text can be attached to the attribute. They become part of the model and are delivered together with the primary value through the API.
Below is an example of a response (simplified, but close to the actual SDK format):
{
"attributeValues": {
"card_image": {
"type": "image",
"value": [
{
"filename": "files/project/product/77/image/main.png",
"downloadLink": "https://project.oneentry.cloud/cloud-static/files/project/product/77/image/main.png",
"size": 392585
}
],
"position": 0,
"additionalFields": [
{
"marker": "fallback_image",
"value": [
{
"filename": "files/project/system/fallbacks/no-image.png",
"downloadLink": "https://project.oneentry.cloud/cloud-static/files/project/system/fallbacks/no-image.png",
"size": 10240
}
]
},
{
"marker": "fallback_text",
"value": "Image temporarily unavailable"
}
]
}
}
}
What matters here is not the existence of a fallback itself, but the fact that the behavior is defined at the data level. The attribute contains not only the primary value, but also additional elements that determine how the system should respond in different states. Rendering logic becomes part of the model rather than a set of conditional statements embedded in the interface.
Using This in the Frontend
On the frontend, we fetch the product data through a standard SDK request:
const response = await api.Products.getProductsByPageUrl(
null,
"en_US",
{ pageUrls: [slug], limit: 1, offset: 0 }
);
const product = response.items[0];
Next, we extract the required attribute and interpret its structure. The main image is stored in value, while the fallback configuration comes through additionalFields:
const imageAttr = product.attributeValues?.card_image;
const mainUrl = imageAttr?.value?.[0]?.downloadLink;
const fallbackUrl =
imageAttr?.additionalFields
?.find(f => f.marker === "fallback_image")
?.value?.[0]?.downloadLink;
const fallbackText =
imageAttr?.additionalFields
?.find(f => f.marker === "fallback_text")
?.value ?? product.title;
Rendering remains straightforward. We simply select the available source, and use onError as a safeguard in case the primary image fails to load:
<img
src={mainUrl || fallbackUrl}
alt={fallbackText}
onError={(e) => {
if (fallbackUrl) {
e.currentTarget.src = fallbackUrl;
}
}}
/>
From an architectural perspective, something else matters here. The frontend does not know in advance whether a fallback exists or how it is structured, because it simply reads the model. Rendering logic is defined by the data itself, which means it can change without modifying the code or triggering a deployment. UI behavior is configured through the admin panel, while the interface remains stable and predictable, interpreting configuration rather than embedding it internally.
Architectural Implications: Control, Predictability, and Separation of Responsibility
When validators and additional fields are viewed not as isolated features but as architectural elements, it becomes clear that they solve a systemic problem. The data model begins to govern not only structure, but also data quality, UI behavior, and fallback scenarios. Rules are no longer scattered across components and services; they are centralized and declaratively defined at the model level.
This changes the role of the frontend. It stops being the place where data is validated, patched, and safeguarded with dozens of conditional statements. Instead, the interface becomes an interpreter of the model: it receives configuration, reads the rules, and applies them correctly. Responsibility for integrity shifts to the architectural layer rather than remaining at the implementation level.
For the business, this translates into reduced operational risk. There are fewer form errors, fewer lost submissions caused by invalid data, and fewer incidents resulting from human error or inconsistencies in logic across different clients. Changes can be implemented faster because rules can be adjusted within the model without modifying the codebase or issuing new releases.
For developers, the impact is just as significant. The amount of defensive code decreases, the need to duplicate validation logic across multiple interfaces disappears, and the architecture becomes cleaner and more predictable. Instead of constantly “adding one more validation check,” there is a single point of control for rules. As a result, the number of minor tasks related to validation and logic alignment is reduced, allowing the team to focus on meaningful product development.
Core Idea: The Model as the Center of Architecture
The headless approach is often perceived in a simplified way — as an API replacing templates and the freedom to choose frontend technologies. But its architectural value does not lie in separating rendering; it lies in separating responsibility. The real shift happens when data, its structure, validation rules, and UI behavior can evolve independently of frontend code.
In OneEntry, an attribute is not just a field with a type and a value. It is a managed entity that includes validators, additional fields, and metadata, and therefore can influence how data is interpreted and rendered. The model stops being a static schema description and becomes an active participant in the architecture.
This is what makes it possible to evolve data independently of the interface. The UI can change, be rewritten, or scale across new channels, while rules and logic remain centralized. As a result, headless stops being merely a technological choice and becomes a system design principle in which the data model serves as the point of control rather than a byproduct of implementation.
Thank you for taking the time to read this article. If it resonated with you, we would appreciate your like or comment. 🙌💬
Top comments (0)