Written by · Yuliana Sepúlveda, Frontend Web Developer at Alternova
In software development, it’s common to run into requirements that seem simple at first glance—nothing out of the ordinary. But when it’s time to implement them, that’s when reality hits: the tool in our hands doesn’t always have everything we need. At least, that’s what we thought at the time.
Strapi v5 lets us extend some of the CMS’s basic functionality directly within the admin panel using tools like conditional fields. But what happens when we have more specific requirements, such as implementing variable filters where the selection in one field dynamically affects the options in the next? This dependent-fields feature isn’t available natively, but it can be implemented by creating what Strapi’s documentation calls custom fields.
In this article, I’ll show you how we developed a complete solution to create dependent fields with variable filters in Strapi v5, solving a real use case where editors needed to select top-level categories and automatically see only the related subcategories—maintaining data integrity both in the frontend and through backend validations. A clearer example, given the context of this post, would be a medical form where you choose a symptom, then see only related specialties, and then the valid treatments for that combination. Each step naturally guides you to the next and reduces the possible margin of error.
The Problem: Limitations of Native Fields
The immediate issue was that Strapi v5 didn’t include this functionality out of the box. We only had well-known basic fields like text, booleans, lists, or conditional fields based on true/false values; as you can see, none of these come close to what we were looking for. This led us to consider other solutions we ultimately discarded. Here’s the difference: software can be built in a way that, in situations like this, gives the team a limitation—or an opportunity. This is where Strapi’s philosophy was crucial, because instead of changing the product or settling for solutions that didn’t fully meet the client’s needs—or that left the content admin with all validation responsibility—it allowed us to extend the platform as much as needed.
Our client needed content editors to choose main categories without being overwhelmed by irrelevant options, to see only the subcategories that actually matched their prior selections, and to maintain consistent classification without manual errors. Technically, this represented a significant challenge because the basic fields Strapi offers (text, booleans, lists, relations) operate independently. So when we tried to create forms where one field must dynamically filter another, we ran into these limitations:

Relation fields display all available options with no filtering

There is no conditional filtering based on previous selections

Native custom fields don’t support dependency logic between fields

Validations must be handled manually
However, as its documentation makes clear, Strapi v5 isn’t just a CMS; it aims to be a platform designed to be extended. Its architecture allows implementers to add completely new features that integrate seamlessly with the existing system without breaking the codebase, acknowledging that each user may face entirely different cases that would be impossible to cover in a single piece of software.
Step-by-Step Solution: Custom Fields with Variable Filters
We developed a two-layer solution: smart custom fields with variable filters that guide users through logical selections in the frontend, and automated validations that guarantee data integrity in the backend.
Step 1: Define the Custom Field
First, we created the custom field definition that would handle our variable filters. Our goal was simple: selecting a category should automatically apply variable filters to display only the relevant options in the next field:
// Custom field definition
export const smartDropdownField = {
name: “smart-dependent-dropdown”,
type: “json”,
label: “Selector Inteligente”,
description: “Campos dependientes con filtrado automático”,
icon: “List”,
component: SmartDropdownComponent,
};
Step 2: Create the React Component with Variable Filters
Along with this custom field, we developed a React component to handle all variable-filter logic and data fetching. This component needed to manage dynamic filtering when selections changed:
const SmartDropdownComponent = ({ value, onChange }) => {
const [categories, setCategories] = useState([]);
const [subcategories, setSubcategories] = useState([]);
const [items, setItems] = useState([]);
const handleCategoryChange = async (selectedCategories) => {
// When the main category changes
const handleCategoryChange = async (selectedCategories) => {
// Update the value
const newValue = {
categories: selectedCategories,
subcategories: [], // Clear dependents
items: [],
};
onChange(newValue);
// Load related subcategories
const relatedSubcategories = await fetchSubcategories(selectedCategories);
setSubcategories(relatedSubcategories);
setItems([]); // Clear the third level
};
return (
<div>
<MultiSelect
label=”Categories”
options={categories}
onChange={handleCategoryChange}
/>
<MultiSelect
label=” Subcategories “
options={subcategories}
disabled={!value?.categories?.length}
onChange={handleSubcategoryChange}
/>
<MultiSelect
label=”Elements”
options={items}
disabled={!value?.subcategories?.length}
onChange={handleItemsChange}
/>
</div>
);
};
This custom–fields–with-variable-filters approach let us create a frictionless end-user experience: editors see only relevant options; invalid combinations aren’t allowed; and the interface naturally guides them toward the correct selection.
Step 3: Register the Custom Field in Strapi
Once we have our custom field and its variable-filter component, we need to register it so Strapi can recognize it and make it available in the Content-Type Builder. This step is important because it connects our custom component to Strapi’s custom fields architecture—otherwise it wouldn’t be recognized:
import { smartDropdownField } from “./fields”;
export default {
register(app) {
app.customFields.register(smartDropdownField);
},
};
Step 4: Backend Validations for Custom Fields
Now that we have the interface and can interact with it in the Strapi admin, it may seem like the job is done—but we’re only halfway there. Important questions arise: what happens when someone accesses the API directly? Or when there are automated integrations? Faced with these questions, it becomes clear that we need guardrails at the server level.
To guarantee data integrity when using variable filters, we implemented “guards” that check each operation before the data reaches the database via lifecycle hooks. It’s important to mention that in Strapi v5, according to the lifecycle hooks documentation, these hooks work differently compared with version 4, especially with the new Document Service API.
// Centralized validation registration
export default {
bootstrap(strapi) {
// Listen to events on specific models
strapi.db.lifecycles.subscribe({
models: [“api::content.content”],
async beforeCreate(event) {
await validateDataIntegrity(event.params.data);
},
async beforeUpdate(event) {
await validateDataIntegrity(event.params.data);
},
});
},
};
The guard function we implemented verifies that the custom fields maintain the integrity established by our variable filters:
// Validation function for custom fields with variable filters
async function validateCustomFieldIntegrity(data, strapi) {
if (!data.smartSelection) return;
const { categories, subcategories, items } = data.smartSelection;
// Validate that the subcategories belong to the selected categories
if (categories?.length && subcategories?.length) {
const validSubcategories = await strapi.entityService.findMany(
“api::subcategory.subcategory”,
{
filters: {
id: { $in: subcategories },
category: { id: { $in: categories } },
},
},
);
if (validSubcategories.length !== subcategories.length) {
throw new ValidationError(
“ The selected subcategories do not correspond to the categories”“,
);
}
}
// Validate that the items belong to the selected subcategories
if (subcategories?.length && items?.length) {
const validItems = await strapi.entityService.findMany(“api::item.item”, {
filters: {
id: { $in: items },
subcategory: { id: { $in: subcategories } },
},
});
if (validItems.length !== items.length) {
throw new ValidationError(
“ The selected items do not correspond to the subcategories “,
);
}
}
}
Step 5: Content Type Configuration
Once we’ve finished creating the custom field, we can add it to our models from the Content-Type Builder, under custom fields in the Strapi admin. When creating a new record for that model, our field with all its preconfigured options should be available.
With this implementation of custom fields and variable filters in Strapi v5, we get:
- Automatic filtering: Fields update dynamically based on prior selections
- Guaranteed integrity: Backend validations prevent data inconsistencies
- Improved UX: Editors see only relevant options, reducing errors
- Reusable: The custom field can be applied to multiple content types
- Scalable: Easy to extend for more complex cases
Beyond Basic Custom Fields
The interesting part of this case—and the main takeaway—isn’t just that we solved the specific need for dependent fields, but that we confirmed something we often forget: tools are only the starting point. A traditional CMS like WordPress would have been too rigid here, while Strapi allowed us to extend the platform without breaking its core and without compromising the project’s scalability. That difference was key to meeting requirements without sacrificing quality or increasing estimates.
We also learned something important: before considering a platform switch or accepting limitations as final, it’s worth checking how extensible the technology in hand really is. In our case, the time invested in building a custom component and validations not only solved the immediate problem but also gave us a reusable foundation for other projects with similar dynamics.
In short, when a tool doesn’t offer what we need out of the box, it doesn’t always mean we should replace it. Sometimes the real value lies in how we adapt it to our context—and how much it allows us to grow with it.
References and Documentation
To dive deeper into the topics covered in this article, I recommend checking the official Strapi v5 documentation:
- Custom Fields — Comprehensive guide on how to create and use custom fields
- Content-Type Builder — Documentation for the content type builder
- Models — Information on models and validations
- Content Manager — Guide to managing content with custom fields
- Lifecycle hooks — Lifecycle hooks configuration for backend validations
