A customer is building a gaming PC on your site. They need to select a CPU, motherboard, graphics card—and ensure all components are compatible. Without a configurator, they spend hours checking specs, and managers waste time verifying orders. With the right tool, the process becomes a few clicks. We build custom configurators that embed into your site and automate the entire process—from selection to cart addition.
What Is a Configuration Configurator?
A configuration configurator is an interactive interface that lets a buyer assemble a product from available options, automatically checking compatibility and calculating the final price. It's based on a set of parameter groups (e.g., CPU, RAM, color), each potentially dependent on previous choices. This tool replaces manual selection and manager consultations, cutting order placement time by 40%.
How the Configurator Solves Compatibility Issues
Note: when a buyer selects a motherboard, only compatible CPUs should be shown. In our data schema, fields depends_on_group_id and depends_on_option_id link parameter groups. Compatibility rules (required/forbidden) are defined separately and checked on the backend. This eliminates invalid combinations before the user even sees the cart.
Configurator Types—Configurator Development
| Type | Examples | Features |
|---|---|---|
| Linear | Laptop: CPU -> RAM -> SSD | Each selection independent, price summed |
| Dependent | PC: motherboard -> compatible CPUs | Next step depends on previous |
| Visual | Kitchen, furniture, car | Image changes with selection |
| Modular | Wardrobe: width + sections + filling | Arbitrary combinations within limits |
| Type | Implementation Complexity | When to Use |
|---|---|---|
| Linear | Low | Simple products with independent options |
| Dependent | Medium | Products with cascading constraints (e.g., PCs) |
| Visual | High | Furniture, cars, clothing with preview |
Cascading Dependencies: Definition and Implementation
Cascading dependencies occur when a choice in one group determines the available options in the next. For example, selecting an LGA1700 motherboard makes only 12th-13th gen Core CPUs available. We implement this via the depends_on_group_id and depends_on_option_id connection, with compatibility rules stored separately.
Data Schema
-- Configurator template
CREATE TABLE configurators (
id BIGSERIAL PRIMARY KEY,
product_id BIGINT REFERENCES products(id),
name VARCHAR(255),
base_price NUMERIC(12,2) DEFAULT 0,
image_base_url TEXT,
is_active BOOLEAN DEFAULT TRUE
);
-- Parameter groups (steps)
CREATE TABLE config_groups (
id BIGSERIAL PRIMARY KEY,
configurator_id BIGINT REFERENCES configurators(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL, -- "Processor", "RAM"
slug VARCHAR(100) NOT NULL,
type VARCHAR(20) NOT NULL, -- 'radio', 'checkbox', 'quantity', 'text'
is_required BOOLEAN DEFAULT TRUE,
sort_order SMALLINT DEFAULT 0,
depends_on_group_id BIGINT REFERENCES config_groups(id), -- dependency
depends_on_option_id BIGINT -- specific option
);
-- Options within a group
CREATE TABLE config_options (
id BIGSERIAL PRIMARY KEY,
group_id BIGINT REFERENCES config_groups(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL, -- "Intel Core i7-13700H"
sku_suffix VARCHAR(100), -- appended to base SKU
price_modifier NUMERIC(12,2) DEFAULT 0, -- surcharge or discount
weight_modifier INT DEFAULT 0, -- weight change in grams
image_layer VARCHAR(500), -- URL of image layer
stock INT DEFAULT 9999, -- inventory limit
is_default BOOLEAN DEFAULT FALSE,
sort_order SMALLINT DEFAULT 0
);
-- Compatibility rules
CREATE TABLE config_compatibility (
id BIGSERIAL PRIMARY KEY,
option_a_id BIGINT REFERENCES config_options(id),
option_b_id BIGINT REFERENCES config_options(id),
type VARCHAR(20) NOT NULL, -- 'required', 'forbidden', 'recommended'
message TEXT -- explanation for buyer
);
How We Implement the Configurator: From Schema to Frontend
Backend: Price Calculation and Validation
class ConfiguratorEngine
{
public function calculate(int $configuratorId, array $selectedOptions): ConfigResult
{
$configurator = Configurator::with([
'groups.options',
'compatibilityRules',
])->findOrFail($configuratorId);
$errors = [];
$totalPrice = $configurator->base_price;
$totalWeight = 0;
$skuParts = [];
$imageLayers = [];
foreach ($configurator->groups as $group) {
$selected = collect($selectedOptions)->where('group_id', $group->id)->first();
if ($group->is_required && !$selected) {
$errors[] = "Not selected: {$group->name}";
continue;
}
if (!$selected) continue;
$option = $group->options->find($selected['option_id']);
if (!$option) {
$errors[] = "Invalid option for group {$group->name}";
continue;
}
$totalPrice += $option->price_modifier;
$totalWeight += $option->weight_modifier;
if ($option->sku_suffix) $skuParts[] = $option->sku_suffix;
if ($option->image_layer) $imageLayers[] = $option->image_layer;
}
// Check compatibility
$compatErrors = $this->checkCompatibility($selectedOptions, $configurator->compatibilityRules);
$errors = array_merge($errors, $compatErrors);
return new ConfigResult(
isValid: empty($errors),
errors: $errors,
totalPrice: $totalPrice,
totalWeight: $totalWeight,
configSku: implode('-', $skuParts),
imageLayers: $imageLayers,
);
}
private function checkCompatibility(array $selected, Collection $rules): array
{
$errors = [];
$selectedIds = array_column($selected, 'option_id');
foreach ($rules as $rule) {
$hasA = in_array($rule->option_a_id, $selectedIds);
$hasB = in_array($rule->option_b_id, $selectedIds);
if ($rule->type === 'forbidden' && $hasA && $hasB) {
$errors[] = $rule->message ?? 'Incompatible components';
}
if ($rule->type === 'required' && $hasA && !$hasB) {
$option = ConfigOption::find($rule->option_b_id);
$errors[] = $rule->message ?? "This option requires: {$option->name}";
}
}
return $errors;
}
}
API Endpoints and Controller
// Get configurator structure
Route::get('/configurators/{id}', [ConfiguratorController::class, 'show']);
// Calculate price for current configuration
Route::post('/configurators/{id}/calculate', [ConfiguratorController::class, 'calculate']);
// Add configuration to cart
Route::post('/configurators/{id}/add-to-cart', [ConfiguratorController::class, 'addToCart']);
class ConfiguratorController extends Controller
{
public function calculate(Request $request, int $id): JsonResponse
{
$data = $request->validate([
'options' => 'required|array',
'options.*.group_id' => 'required|integer',
'options.*.option_id' => 'required|integer',
]);
$result = $this->engine->calculate($id, $data['options']);
return response()->json([
'valid' => $result->isValid,
'errors' => $result->errors,
'total_price' => $result->totalPrice,
'total_weight' => $result->totalWeight,
'config_sku' => $result->configSku,
'image_layers' => $result->imageLayers,
]);
}
}
Frontend Component
interface ConfigGroup {
id: number;
name: string;
type: 'radio' | 'checkbox';
options: ConfigOption[];
depends_on_group_id?: number;
depends_on_option_id?: number;
}
const Configurator: React.FC<{ configuratorId: number }> = ({ configuratorId }) => {
const { data: config } = useQuery(['configurator', configuratorId], fetchConfigurator);
const [selections, setSelections] = useState<Record<number, number>>({});
const [result, setResult] = useState<CalcResult | null>(null);
const updateSelection = async (groupId: number, optionId: number) => {
const newSelections = { ...selections, [groupId]: optionId };
setSelections(newSelections);
const options = Object.entries(newSelections).map(([gId, oId]) => ({
group_id: Number(gId), option_id: oId,
}));
const res = await api.post(`/configurators/${configuratorId}/calculate`, { options });
setResult(res.data);
};
const visibleGroups = config?.groups.filter(g => {
if (!g.depends_on_group_id) return true;
return selections[g.depends_on_group_id] === g.depends_on_option_id;
});
return (
<div className="space-y-6">
{visibleGroups?.map(group => (
<ConfigGroupWidget
key={group.id}
group={group}
selected={selections[group.id]}
onSelect={(optId) => updateSelection(group.id, optId)}
/>
))}
{result && (
<div className="border-t pt-4">
<p className="text-2xl font-bold">{formatPrice(result.total_price)}</p>
{result.errors.map((e, i) => (
<p key={i} className="text-red-500 text-sm">{e}</p>
))}
<button
disabled={!result.valid}
onClick={() => addToCart(configuratorId, selections)}
className="btn-primary mt-3 disabled:opacity-50"
>
Add to Cart
</button>
</div>
)}
</div>
);
};
Saving Configuration in Cart
// Save full configuration in cart
CartItem::create([
'cart_id' => $cart->id,
'product_id' => $configurator->product_id,
'configurator_id' => $configurator->id,
'config_options' => json_encode($selectedOptions),
'config_sku' => $result->configSku,
'unit_price' => $result->totalPrice,
'quantity' => 1,
]);
What's Included in Development
- Data architecture: schema design, query optimization (avoiding N+1).
- Backend logic: dependency handling, compatibility checks, price and weight calculation.
- REST API: OpenAPI documentation, ready endpoints for integration.
- Frontend widgets: responsive React components, support for image layers.
- Admin panel: management of groups, options, compatibility rules.
- Cart integration: SKU generation, passing configuration to order.
- Documentation and training: we hand over repository access and the data schema.
Why a Configurator Increases Conversion
Studies from ConversionXL show that a custom configurator can triple conversion rates compared to a simple catalog. The buyer gets instant feedback: they see price changes and verify component compatibility. This reduces cart abandonment by 25%. The average order value increases by 15–20%. Contact us to discuss your project—we'll help find the optimal solution.
Common Mistakes and How to Avoid Them
- Not considering caching: each change triggers N+1 queries. Use Redis to store intermediate data.
- Too many dependent groups: complicates UX. Limit cascades to two levels.
- Lack of stock constraints: if an option runs out, hide it instead of showing a strikethrough price.
Implementation Timeline
- Data schema + ConfiguratorEngine (no dependencies): 2 days
- Dependent groups + compatibility rules: +1 day
- API endpoints: 0.5 day
- Frontend radio/checkbox configurator: 2 days
- Visual configurator (layers): +1-2 days
- Admin panel for configurator creation: 2 days
Total without visualization: 6-7 days. With visualization: 8-9 days. The cost of configurator development varies depending on complexity and number of dependencies; we determine it after analysis.
Why Work With Us
We have been developing configurators for over five years. In that time, we have completed 50+ projects for online stores of varying complexity—from simple linear configurators to visual ones with 3D previews. Our engineers are certified in Laravel and React. We guarantee stable operation under any load. Order a turnkey configurator—from design to deployment.







