Implementing Custom Marketplace Option Types
This guide explains how to add new option types to Waldur's marketplace offering system, using the conditional_cascade implementation as a reference.
Overview
Waldur marketplace options allow service providers to define custom form fields for their offerings. The system supports various built-in types like string, select_string, boolean, etc., and can be extended with custom types.
Architecture
The marketplace options system consists of several components:
- Backend: Option type validation, serialization, and storage
- Admin Interface: Configuration UI for service providers
- User Interface: Form fields displayed to users during ordering
- Form Processing: Attribute handling during order creation
Implementation Steps
1. Backend: Add Field Type Constant
Add your new type to the FIELD_TYPES constant:
File: src/waldur_mastermind/marketplace/serializers.py
1 2 3 4 5 6 7 | |
2. Backend: Create Configuration Serializers
Define serializers for validating your option configuration:
File: src/waldur_mastermind/marketplace/serializers.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
3. Backend: Add Order Validation Support
Register your field type for order processing:
File: src/waldur_mastermind/common/serializers.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
4. Frontend: Add Type Constant
Add the new type to the frontend constants:
File: src/marketplace/offerings/update/options/constants.ts
1 2 3 4 5 6 7 | |
5. Frontend: Create Configuration Component
Create an admin configuration component:
File: src/marketplace/offerings/update/options/YourCustomConfiguration.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
6. Frontend: Create User-Facing Component
Create the component that users see in order forms:
File: src/marketplace/common/YourCustomField.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | |
7. Frontend: Update Configuration Forms
Add your type to the option configuration form:
File: src/marketplace/offerings/update/options/OptionForm.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
8. Frontend: Update Order Form Rendering
Add your field to the order form renderer:
File: src/marketplace/common/OptionsForm.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
9. Frontend: Handle Form Data Processing
Update form utilities if needed:
File: src/marketplace/offerings/store/utils.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
File: src/marketplace/details/utils.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
10. Testing
Create comprehensive tests for your new option type:
File: src/waldur_mastermind/marketplace/tests/test_your_custom_type.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | |
Key Considerations
Data Format Consistency
- Configuration Phase: How admins configure the option (JSON strings for complex data)
- Display Phase: How the option is displayed in forms (parsed objects)
- Submission Phase: What format users submit (depends on your UI component)
- Storage Phase: How the data is stored in orders/resources (final format)
Error Handling
- Ensure all error dictionaries use string keys for JSON serialization compatibility
- Provide clear, actionable error messages
- Handle edge cases (empty values, malformed data, etc.)
Form Integration
- Redux-form compatibility: For admin configuration interfaces
- React-final-form compatibility: For some user interfaces (when
finalForm=true) - FormContainer integration: For most user order forms
Performance
- Use
useCallbackanduseRefto prevent unnecessary re-renders - Avoid object dependencies in
useEffectthat cause infinite loops - Memoize expensive computations
Example: Conditional Cascade Implementation
The conditional_cascade type demonstrates all these concepts:
Backend Components
CascadeStepSerializer- Validates individual steps with JSON parsingCascadeConfigSerializer- Validates overall configuration with dependency checkingConditionalCascadeField(in common/serializers.py) - Handles order validation
Frontend Components
ConditionalCascadeConfiguration- Admin configuration interface (redux-form)ConditionalCascadeWidget- Admin form component (redux-form)ConditionalCascadeField- User order form component (FormContainer/redux-form)
Key Features
- Cascading Dependencies: Dropdowns that depend on previous selections
- JSON Configuration: Complex configuration stored as JSON strings
- Object Preservation: Keeps selection objects intact through form processing
- Bidirectional Sync: Proper state management between form and component
Testing Strategy
Create tests covering:
- Configuration Validation - Valid/invalid option configurations
- Order Processing - Attribute validation during order creation
- Edge Cases - Unicode, special characters, empty values, malformed data
- Error Handling - JSON serialization compatibility, clear error messages
- Integration - Mixed field types, form submission end-to-end
Best Practices
- Follow Existing Patterns - Study similar option types before implementing
- Incremental Development - Implement backend validation first, then frontend
- Comprehensive Testing - Test all data paths and edge cases
- Error Prevention - Use TypeScript interfaces and runtime validation
- Documentation - Document configuration format and usage examples
Common Pitfalls
- JSON Serialization Errors - Always use string keys in error dictionaries
- Infinite Re-renders - Avoid objects in useEffect dependencies
- Form Integration Issues - Ensure proper
inputprop handling - Data Format Mismatches - Handle format differences between config/display/submission
- Validation Bypass - Don't forget to add your type to
FIELD_CLASSESmapping
Update Frontend Type Handlers
Add your new type to the OptionValueRenders object in the frontend:
File: src/marketplace/resources/options/OptionValue.tsx
1 2 3 4 | |
Important: If this step is missed, TypeScript compilation will fail with:
1 | |
Following this guide ensures your custom option type integrates seamlessly with Waldur's marketplace system and provides a consistent user experience.
Built-in Option Types
Component Multiplier
The component_multiplier option type allows users to input a value that gets automatically multiplied by a configurable factor to set limits for limit-based offering components.
Use Case
Perfect for scenarios where users need to specify resources in user-friendly units that need conversion:
- Storage: User enters "2 TB", automatically sets 100,000 inodes (2 × 50,000)
- Compute: User enters "4 cores", automatically sets 16 GB RAM (4 × 4)
- Network: User enters "100 Mbps", automatically sets bandwidth limits in bytes
Configuration
Backend Configuration (component_multiplier_config):
1 2 3 4 5 6 | |
Option Definition:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Behavior
- User Input: User enters a value (e.g., "2" for 2 TB)
- Frontend Multiplication: Value is multiplied by factor (2 × 50,000 = 100,000)
- Automatic Limit Setting: The calculated value (100,000) is automatically set as the limit for the specified component (
storage_inodes) - Validation: Frontend validates user input against
min_limitandmax_limitbefore multiplication
Requirements
- Component Dependency: Must reference an existing limit-based component (
billing_type: "limit") - Factor: Must be a positive integer ≥ 1
- Limits:
min_limitandmax_limitapply to user input, not the calculated result
Implementation Components
- Configuration:
ComponentMultiplierConfiguration.tsx- Admin interface for setting up the multiplier - User Field:
ComponentMultiplierField.tsx- User input field that handles multiplication and limit updates