-
Notifications
You must be signed in to change notification settings - Fork 0
Custom fields
Pierre Lecointre edited this page Sep 24, 2025
·
2 revisions
This guide explains how to use the custom fields feature in Stood, which allows teams to define additional fields for Account, Contact, Deal, and Activity entities.
The custom fields feature provides a flexible way to extend the data model without code changes. Each team can define their own custom fields by uploading a datamodel.json file to Firebase Storage.
-
Team-specific Configuration: Each team has its own
datamodel.jsonfile stored in Firebase Storage at{teamId}/datamodel.json - Dynamic Loading: The app loads custom field definitions at startup and when switching teams
- NoSQL Storage: Custom field values are stored directly in the same Firestore collections as the main entity data
- Type Safety: Custom fields support various data types with validation
- text: Single-line text input
- textarea: Multi-line text input
- number: Numeric input with optional min/max validation
- boolean: True/false checkbox
- date: Date picker
- select: Dropdown with predefined options
- email: Email input with validation
- url: URL input with validation
- phone: Phone number input
The datamodel.json file follows this structure:
{
"account": {
"fieldKey": {
"name": "Display Name",
"type": "fieldType",
"required": false,
"defaultValue": "default",
"options": ["option1", "option2"],
"description": "Field description",
"maxLength": 100,
"minValue": 0,
"maxValue": 100
}
},
"contact": { ... },
"deal": { ... },
"activity": { ... }
}- name (required): Human-readable field name
- type (required): Field type (see supported types above)
- required (optional): Whether the field is mandatory (default: false)
- defaultValue (optional): Default value for the field
- options (optional): Array of options for select fields
- description (optional): Help text for the field
- maxLength (optional): Maximum length for text fields
- minValue (optional): Minimum value for number fields
- maxValue (optional): Maximum value for number fields
Custom fields are stored in the same Firestore documents as the main entity data:
{
"name": "Acme Corp",
"description": "Software company",
"customFields": {
"industry": "Technology",
"annualRevenue": 1000000,
"isPublic": true
}
}- Each team manages its own custom fields independently
- Field definitions are loaded when switching teams
- No cross-team field sharing (by design)
- Existing entities without custom fields will have an empty
customFieldsmap - The system gracefully handles missing or invalid field definitions
- No data migration is required
{
"account": {
"industry": {
"name": "Industry",
"type": "select",
"required": false,
"options": [
"Technology",
"Healthcare",
"Finance",
"Manufacturing",
"Retail",
"Education",
"Government",
"Non-profit",
"Other"
],
"description": "The industry sector this account belongs to"
},
"annualRevenue": {
"name": "Annual Revenue",
"type": "number",
"required": false,
"minValue": 0,
"description": "Annual revenue in USD"
},
"employeeCount": {
"name": "Employee Count",
"type": "number",
"required": false,
"minValue": 1,
"description": "Number of employees in the company"
},
"foundedYear": {
"name": "Founded Year",
"type": "number",
"required": false,
"minValue": 1800,
"maxValue": 2024,
"description": "Year the company was founded"
},
"isPublic": {
"name": "Public Company",
"type": "boolean",
"required": false,
"defaultValue": false,
"description": "Whether the company is publicly traded"
},
"notes": {
"name": "Internal Notes",
"type": "textarea",
"required": false,
"maxLength": 1000,
"description": "Internal notes about this account"
}
},
"contact": {
"jobTitle": {
"name": "Job Title",
"type": "text",
"required": false,
"maxLength": 100,
"description": "Professional job title"
},
"department": {
"name": "Department",
"type": "select",
"required": false,
"options": [
"Sales",
"Marketing",
"IT",
"Finance",
"HR",
"Operations",
"Executive",
"Other"
],
"description": "Department within the organization"
},
"seniority": {
"name": "Seniority Level",
"type": "select",
"required": false,
"options": [
"Entry Level",
"Mid Level",
"Senior Level",
"Manager",
"Director",
"VP",
"C-Level"
],
"description": "Seniority level of the contact"
},
"linkedinProfile": {
"name": "LinkedIn Profile",
"type": "url",
"required": false,
"description": "LinkedIn profile URL"
},
"preferredContactMethod": {
"name": "Preferred Contact Method",
"type": "select",
"required": false,
"options": [
"Email",
"Phone",
"LinkedIn",
"SMS",
"Other"
],
"description": "Preferred method of communication"
},
"timezone": {
"name": "Timezone",
"type": "text",
"required": false,
"defaultValue": "UTC",
"description": "Contact's timezone"
},
"lastContactDate": {
"name": "Last Contact Date",
"type": "date",
"required": false,
"description": "Date of last contact with this person"
}
},
"deal": {
"probability": {
"name": "Deal Probability",
"type": "number",
"required": false,
"minValue": 0,
"maxValue": 100,
"description": "Probability of closing this deal (0-100%)"
},
"competitor": {
"name": "Main Competitor",
"type": "text",
"required": false,
"maxLength": 100,
"description": "Primary competitor for this deal"
},
"decisionMaker": {
"name": "Decision Maker",
"type": "text",
"required": false,
"maxLength": 100,
"description": "Name of the key decision maker"
},
"budgetApproved": {
"name": "Budget Approved",
"type": "boolean",
"required": false,
"defaultValue": false,
"description": "Whether budget has been approved"
},
"timeline": {
"name": "Expected Timeline",
"type": "select",
"required": false,
"options": [
"This Quarter",
"Next Quarter",
"This Year",
"Next Year",
"Undecided"
],
"description": "Expected timeline for closing"
},
"source": {
"name": "Deal Source",
"type": "select",
"required": false,
"options": [
"Cold Outreach",
"Referral",
"Website",
"Trade Show",
"Social Media",
"Partner",
"Other"
],
"description": "How this deal was sourced"
},
"customNotes": {
"name": "Deal Notes",
"type": "textarea",
"required": false,
"maxLength": 2000,
"description": "Additional notes about this deal"
}
},
"activity": {
"priority": {
"name": "Priority",
"type": "select",
"required": false,
"options": [
"Low",
"Medium",
"High",
"Urgent"
],
"description": "Priority level of this activity"
},
"estimatedDuration": {
"name": "Estimated Duration (minutes)",
"type": "number",
"required": false,
"minValue": 1,
"maxValue": 480,
"description": "Estimated duration in minutes"
},
"followUpRequired": {
"name": "Follow-up Required",
"type": "boolean",
"required": false,
"defaultValue": false,
"description": "Whether a follow-up is needed"
},
"followUpDate": {
"name": "Follow-up Date",
"type": "date",
"required": false,
"description": "Date for follow-up if required"
},
"outcome": {
"name": "Activity Outcome",
"type": "select",
"required": false,
"options": [
"Successful",
"Needs Follow-up",
"Rescheduled",
"Cancelled",
"No Response"
],
"description": "Outcome of this activity"
},
"location": {
"name": "Location",
"type": "text",
"required": false,
"maxLength": 200,
"description": "Location where activity took place"
},
"attendees": {
"name": "Attendees",
"type": "textarea",
"required": false,
"maxLength": 500,
"description": "List of attendees or participants"
}
}
}