ProficientNowTechRFCs

RFC: SDTF Schema Creation Guide

RFC: SDTF Schema Creation Guide

Introduction

The Schema-Driven Table Framework (SDTF) is an advanced frontend architecture that enables dynamic rendering and management of tabular data through declarative schema configurations. This guide provides a structured approach to creating SDTF schemas based on API response objects or field requirements.

Initially this was referred to as dynamic tables, however, that just led more confusion, since it was unclear whether it referred to the frontend component or the backend data structure. To avoid ambiguity, we now refer to it as the Schema-Driven Table Framework (SDTF).

Creating an effective SDTF schema requires understanding:

  • The structure of your API response
  • The relationship between different data entities
  • The desired UI presentation and interactions
  • Field types and their configuration options

Schema Structure

An SDTF schema consists of several key components:

Metadata and Configuration

{
  "table_name": "EntityName", // Display name (typically pluralized)
  "db_table_name": "entity_name", // Name from API response (e.g., data.leads → "leads")
  "endpoint_url": "/api/v1/entity", // API endpoint
  "table_description": "Brief description", // Short explanation
  "icon": "IconName", // Exact name from Icons.tsx (e.g., "MagnetSvg")
  "version": "1.0.0", // Schema version
  "hidden_fields": [], // Fields to hide from UI
  "default_views": [], // Predefined views
  "user_specific": [], // User-specific configurations
  "history": [] // Historical data
}

[!IMPORTANT] The db_table_name must exactly match the property name in the API response. The frontend code accesses the data using tableData.data[schemaData.data.db_table_name].

Context Options

These define actions available for items in the table:

"context_options": [
  {
    "name": "Edit Entity",
    "action": {
      "type": "ui-interaction",
      "params": {
        "sheetId": "edit_entity_form"
      },
      "custom-action": "open-sheet"
    },
    "component": {
      "icon": "PencilIcon",
      "name": "Button",
      "text": "Edit Entity",
      "props": {
        "size": "sm",
        "variant": "ghost"
      }
    }
  }
]

Fields Configuration

Each field in the schema represents a property in your data:

"fields": [
  {
    "display_name": "Human-Readable Name",
    "db_field_name": "api_property_name",
    "type": "FIELD_TYPE", // From ZPrismaFieldTypes enum
    "db_field_type": "DATABASE_TYPE", // From ZPrismaDBFieldTypes enum
    "is_relational": false, // Whether it references other entities
    "relation": null, // Relation configuration (if is_relational is true)
    "formatting": {} // Optional formatting rules
  }
]

Step-by-Step Process

1. Analyze the API Response

Start by examining the structure of your API response:

{
  "status": "success",
  "message": "Data fetched successfully",
  "data": {
    "totalRecords": 1000,
    "currentPage": 1,
    "pageSize": 10,
    "totalPages": 100,
    "entity_name": [
      {
        "id": "uuid-value",
        "property1": "value1",
        "property2": "value2",
        "nested_object": {
          "nested_property": "value"
        },
        "related_items": [{ "id": "related-id", "name": "Related Name" }]
      }
    ]
  }
}

[!NOTE]

  • The name of the array containing your entities (e.g., data.entity_name)
  • The structure of each entity object
  • Any nested objects or arrays that represent relations
  • The primary key (usually id)

2. Set Up Basic Schema Configuration

Configure the top-level properties of your schema:

{
  "table_name": "EntityNames", // Capitalized plural noun
  "db_table_name": "entity_name", // Matches property name in API response
  "endpoint_url": "/api/v1/entity", // API endpoint
  "table_description": "Description of entities",
  "icon": "MagnetSvg", // Exact icon name from Icons.tsx
  "version": "1.0.0"
}

Important notes:

  • table_name should be a user-friendly, capitalized, plural noun
  • db_table_name must exactly match the property name in the API response
  • Use the exact icon name from Icons.tsx (e.g., "MagnetSvg" for leads, not just any icon)
  • The endpoint URL should match your API structure

3. Define Context Options

Add standard and domain-specific actions:

"context_options": [
  {
    "name": "Edit Entity",
    "action": {
      "type": "ui-interaction",
      "params": { "sheetId": "edit_entity_form" },
      "custom-action": "open-sheet"
    },
    "component": {
      "icon": "PencilIcon",
      "name": "Button",
      "text": "Edit",
      "props": { "size": "sm", "variant": "ghost" }
    }
  },
  {
    "name": "View Entity",
    "action": {
      "type": "ui-interaction",
      "params": { "sheetId": "view_entity_form" },
      "custom-action": "open-sheet"
    },
    "component": {
      "icon": "EyeIcon",
      "name": "Button",
      "text": "View Details",
      "props": { "size": "sm", "variant": "ghost" }
    }
  }
]

Common context options include:

  • Edit: Opens a form to edit the entity
  • View: Opens a detailed view of the entity
  • Delete: Triggers deletion of the entity
  • Domain-specific actions (e.g., "Convert to Company" for leads)

4. Map Fields

Create field definitions for each relevant property in the API response. The field types must match the ZPrismaFieldTypes and ZPrismaDBFieldTypes enums:

"fields": [
  {
    "display_name": "Name",
    "db_field_name": "name",
    "type": "SHORT_TEXT", // Must be from ZPrismaFieldTypes enum
    "db_field_type": "VARCHAR", // Must be from ZPrismaDBFieldTypes enum
    "is_relational": false,
    "relation": null
  }
]

5. Configure Special Properties

Define any additional configuration needed:

"hidden_fields": ["internal_id", "metadata"],
"default_views": [],
"user_specific": [],
"history": [],
"sheet_config": []

Field Types Reference

SDTF strictly uses the following field types as defined in the ZPrismaFieldTypes enum:

Text Field Types

  • SHORT_TEXT: Short text strings (names, titles, etc.)
  • LONG_TEXT: Longer text content (descriptions, notes)
  • EMAIL: Email addresses
  • PHONE: Phone numbers
  • URL: Web URLs

Numeric Field Types

  • NUMBER: General numeric values
  • PERCENT: Percentage values
  • CURRENCY: Monetary values

Date Field Types

  • DATE: Date values
  • DATETIME: Date and time values

Boolean Field Types

  • CHECKBOX: True/false values

Spatial Field Types

  • GEOLOCATION: Geographic coordinates

Relational Field Types

  • SINGLE_SELECT: One-to-one relationships
  • MULTI_SELECT: One-to-many relationships

Special Field Types

  • USER: References to users
  • ATTACHMENT: File attachments
  • FORMULA: Computed values

Database Field Types

The following database field types must be used (from ZPrismaDBFieldTypes enum):

  • VARCHAR: Variable-length strings
  • TEXT: Longer text content
  • TIMESTAMP: Date and time values
  • BOOLEAN: True/false values
  • INTEGER: Whole numbers
  • DECIMAL: Decimal numbers
  • POINT: Geographic points
  • RELATION: References to other entities

Multiple Relations to the Same Table

A key pattern in SDTF is configuring multiple relation fields to the same table but displaying different attributes. For example, showing different aspects of a company:

// Company Name
{
  "display_name": "Company",
  "db_field_name": "company",
  "type": "SINGLE_SELECT",
  "db_field_type": "RELATION",
  "is_relational": true,
  "relation": {
    "type": "ONE",
    "table": "companies",
    "display": {
      "style": [
        {
          "variant": "outline",
          "bg_color": "#E5E7EB",
          "text_color": "000000"
        }
      ],
      "format": {
        "fields": ["name"],
        "template": "{name}"
      },
      "display_type": "badge"
    },
    "display_fields": ["id", "name", "website"]
  }
}
// Company Size (same relation, different display)
{
  "display_name": "Company Size",
  "db_field_name": "company",
  "type": "SINGLE_SELECT",
  "db_field_type": "RELATION",
  "is_relational": true,
  "relation": {
    "type": "ONE",
    "table": "companies",
    "display": {
      "style": [
        {
          "variant": "outline",
          "bg_color": "#E5E7EB",
          "text_color": "000000"
        }
      ],
      "format": {
        "fields": ["size"],
        "template": "{size}"
      },
      "display_type": "badge"
    },
    "display_fields": ["id", "size", "website"]
  }
}

Notice the differences:

  • Same db_field_name ("company")
  • Different display_name ("Company" vs "Company Size")
  • Different format.fields and template to show different attributes
  • Different display_fields to include the needed attributes

Display Configuration Options

The relation display configuration has several important parts:

"display": {
  "display_type": "badge", // Options: "text", "badge", "avatar", "link", "tag", "status", "attachment"
  "style": [
    {
      "variant": "outline", // Options: "default", "secondary", "outline"
      "bg_color": "color_hex", // Background color
      "text_color": "000000" // Text color
    }
  ],
  "format": {
    "fields": ["field1", "field2"], // Fields to include in the template
    "template": "{field1} ({field2})" // Template for displaying the fields
  }
}

Complete Working Example: Leads Schema

Here's a complete working example for a Leads SDTF schema:

{
  "table_name": "Leads",
  "db_table_name": "leads",
  "endpoint_url": "/api/v1/leads",
  "table_description": "Potential client leads and contacts",
  "icon": "MagnetSvg",
  "version": "1.0.0",
  "hidden_fields": [],
  "default_views": [],
  "user_specific": [],
  "history": [],
  "context_options": [
    {
      "name": "Edit Lead",
      "action": {
        "type": "ui-interaction",
        "params": {
          "sheetId": "edit_lead_form"
        },
        "custom-action": "open-sheet"
      },
      "component": {
        "icon": "PencilIcon",
        "name": "Button",
        "text": "Edit Lead",
        "props": {
          "size": "sm",
          "variant": "ghost"
        }
      }
    },
    {
      "name": "View Lead",
      "action": {
        "type": "ui-interaction",
        "params": {
          "sheetId": "view_lead_form"
        },
        "custom-action": "open-sheet"
      },
      "component": {
        "icon": "EyeIcon",
        "name": "Button",
        "text": "View Details",
        "props": {
          "size": "sm",
          "variant": "ghost"
        }
      }
    },
    {
      "name": "Convert to Company",
      "action": {
        "type": "ui-interaction",
        "params": {
          "sheetId": "convert_lead_to_company_form"
        },
        "custom-action": "open-sheet"
      },
      "component": {
        "icon": "BuildingIcon",
        "name": "Button",
        "text": "Convert to Company",
        "props": {
          "size": "sm",
          "variant": "ghost"
        }
      }
    }
  ],
  "fields": [
    {
      "display_name": "Email",
      "db_field_name": "email",
      "type": "EMAIL",
      "db_field_type": "VARCHAR",
      "is_relational": false,
      "relation": null
    },
    {
      "display_name": "Phone",
      "db_field_name": "phone",
      "type": "PHONE",
      "db_field_type": "VARCHAR",
      "is_relational": false,
      "relation": null
    },
    {
      "display_name": "LinkedIn",
      "db_field_name": "company",
      "type": "SINGLE_SELECT",
      "db_field_type": "RELATION",
      "is_relational": true,
      "relation": {
        "type": "ONE",
        "table": "companies",
        "display": {
          "style": [
            {
              "variant": "outline",
              "bg_color": "#E5E7EB",
              "text_color": "000000"
            }
          ],
          "format": {
            "fields": ["linkedin_url"],
            "template": "{linkedin_url}"
          },
          "display_type": "badge"
        },
        "display_fields": ["id", "linkedin_url", "website"]
      }
    },
    {
      "display_name": "Company Size",
      "db_field_name": "company",
      "type": "SINGLE_SELECT",
      "db_field_type": "RELATION",
      "is_relational": true,
      "relation": {
        "type": "ONE",
        "table": "companies",
        "display": {
          "style": [
            {
              "variant": "outline",
              "bg_color": "#E5E7EB",
              "text_color": "000000"
            }
          ],
          "format": {
            "fields": ["size"],
            "template": "{size}"
          },
          "display_type": "badge"
        },
        "display_fields": ["id", "size", "website"]
      }
    },
    {
      "display_name": "Company",
      "db_field_name": "company",
      "type": "SINGLE_SELECT",
      "db_field_type": "RELATION",
      "is_relational": true,
      "relation": {
        "type": "ONE",
        "table": "companies",
        "display": {
          "style": [
            {
              "variant": "outline",
              "bg_color": "#E5E7EB",
              "text_color": "000000"
            }
          ],
          "format": {
            "fields": ["industry"],
            "template": "{industry}"
          },
          "display_type": "badge"
        },
        "display_fields": ["id", "industry", "website"]
      }
    },
    {
      "display_name": "Status",
      "db_field_name": "status",
      "type": "SINGLE_SELECT",
      "db_field_type": "RELATION",
      "is_relational": true,
      "relation": {
        "type": "ONE",
        "table": "status",
        "display": {
          "style": [
            {
              "variant": "outline",
              "bg_color": "color_hex",
              "text_color": "000000"
            }
          ],
          "format": {
            "fields": ["value"],
            "template": "{value}"
          },
          "display_type": "badge"
        },
        "display_fields": ["id", "value", "key", "color_hex"]
      }
    },
    {
      "display_name": "Company",
      "db_field_name": "company",
      "type": "SINGLE_SELECT",
      "db_field_type": "RELATION",
      "is_relational": true,
      "relation": {
        "type": "ONE",
        "table": "companies",
        "display": {
          "style": [
            {
              "variant": "outline",
              "bg_color": "#E5E7EB",
              "text_color": "000000"
            }
          ],
          "format": {
            "fields": ["name"],
            "template": "{name}"
          },
          "display_type": "badge"
        },
        "display_fields": ["id", "name", "website"]
      }
    },
    {
      "display_name": "Research Analyst",
      "db_field_name": "researcher",
      "type": "SINGLE_SELECT",
      "db_field_type": "RELATION",
      "is_relational": true,
      "relation": {
        "type": "ONE",
        "table": "users",
        "display": {
          "style": [
            {
              "variant": "outline",
              "bg_color": "#DBEAFE",
              "text_color": "000000"
            }
          ],
          "format": {
            "fields": ["first_name", "last_name"],
            "template": "{first_name} {last_name}"
          },
          "display_type": "badge"
        },
        "display_fields": ["id", "first_name", "last_name", "email"]
      }
    },
    {
      "display_name": "Recruiter",
      "db_field_name": "recruiter",
      "type": "SINGLE_SELECT",
      "db_field_type": "RELATION",
      "is_relational": true,
      "relation": {
        "type": "ONE",
        "table": "users",
        "display": {
          "style": [
            {
              "variant": "outline",
              "bg_color": "#DBEAFE",
              "text_color": "000000"
            }
          ],
          "format": {
            "fields": ["first_name", "last_name"],
            "template": "{first_name} {last_name}"
          },
          "display_type": "badge"
        },
        "display_fields": ["id", "first_name", "last_name", "email"]
      }
    },
    {
      "display_name": "Created By",
      "db_field_name": "created_by",
      "type": "SHORT_TEXT",
      "db_field_type": "VARCHAR",
      "is_relational": false,
      "relation": null
    },
    {
      "display_name": "Created At",
      "db_field_name": "created_at",
      "type": "DATETIME",
      "db_field_type": "TIMESTAMP",
      "is_relational": false,
      "relation": null
    }
  ],
  "references": [],
  "sheet_config": []
}

Key Features in the Example:

  1. Multiple Company Relations: Notice how there are multiple fields that all use db_field_name: "company" but display different attributes:

    • One displays the company name
    • Another displays the LinkedIn URL
    • Another displays the company size
    • Another displays the industry
  2. Specific Icon: Uses "MagnetSvg" specifically for leads

  3. Field Types: Uses proper field types from the ZPrismaFieldTypes enum:

    • "EMAIL" for email addresses
    • "PHONE" for phone numbers
    • "SINGLE_SELECT" for relational fields
    • "DATETIME" for timestamp fields
  4. Custom Actions: The "Convert to Company" action is domain-specific to leads

Best Practices

  1. Field Names and Types

    • Use exact field types from the ZPrismaFieldTypes enum
    • Use exact database field types from the ZPrismaDBFieldTypes enum
    • Make sure db_field_name matches the API response property exactly
  2. Multiple Relations to the Same Entity

    • You can have multiple fields pointing to the same relation (e.g., multiple company fields)
    • Use different display_name values to clarify the purpose
    • Configure different format.fields and template to show different attributes
    • Include all needed fields in display_fields
  3. Icon Selection

    • Use exact icon names from Icons.tsx (e.g., "MagnetSvg" for leads)
    • Stick to the established conventions (BuildingSvg for companies, etc.)
  4. Performance Considerations

    • Be aware that each relational field increases the load on the frontend
    • For simple formatting, prefer formula fields over multiple relations
    • Only include the necessary fields in display_fields
  5. API Response Mapping

    • db_table_name must match the property in the API response
    • The frontend code accesses data via tableData.data[schemaData.data.db_table_name]

Icon Reference

The "icon" field must use exactly one of the icon names available in Icons.tsx:

// SVG Icons
BuildingSvg // Company-related entities
ContactsSvg // Contacts/people entities
DashboardSvg // Dashboard-related entities
DomainSettingsSvg // Settings entities
EmailSvg // Email-related entities
IntegrationSvg // Integration entities
InterviewSvg // Interview entities
BriefcaseSvg // Job/position entities
MagnetSvg // Lead entities
AccountSvg // Account entities
SubmissionSvg // Submission entities
Boolean // Boolean/toggle entities
 
// Lucide Icons
;(ArrowDownIcon,
  ArrowUpIcon,
  EyeOffIcon,
  FilterIcon,
  PencilIcon,
  MoreHorizontalIcon,
  ChevronRightIcon,
  PlusIcon,
  TrashIcon,
  CheckIcon,
  XIcon,
  EyeIcon,
  User,
  Pen,
  AlertCircle,
  Text,
  Mail,
  Hash,
  Building,
  Briefcase,
  Calendar,
  Activity,
  Phone,
  Link,
  User2,
  Loader2,
  FileText,
  LineChart,
  DollarSign,
  MapPin,
  Globe,
  Users,
  UserCheck,
  MessageCircle)

[!NOTE]
Could have been updated, so make sure to check out Icons.tsx

Troubleshooting

Common Issues

  1. Field not appearing in the table

    • Check if the field is in the hidden_fields array
    • Verify the db_field_name matches the API response property exactly
    • Ensure the field type is one of the ZPrismaFieldTypes enum values
  2. Relation not displaying properly

    • Check if the related entity exists and is properly configured
    • Verify the display_fields include all fields referenced in the template
    • Ensure the template uses the correct field names in braces
  3. Formula not working

    • Check the formula syntax for errors
    • Verify that referenced fields exist in the row data
    • Ensure the returnType is one of: "TEXT", "NUMBER", "BOOLEAN" (case-sensitive)
  4. API data not loading

    • Verify the endpoint_url is correct
    • Check that db_table_name exactly matches the property in the API response
    • Examine the network request to see the actual API response structure

Data Flow Debugging

Understanding how data flows through the system is crucial:

  1. The frontend loads the schema using the tableId parameter
  2. From the schema, it gets endpoint_url to fetch the table data
  3. The API responds with data in a structure like data.leads (for leads)
  4. The code accesses the data using tableData.data[schemaData.data.db_table_name]
  5. This is why db_table_name must exactly match the property in the API response

Conclusion

Creating effective SDTF schemas requires understanding both the data structure and the technical requirements of the framework. By following this guide and referencing the working examples, you can create schemas that properly integrate with the SDTF system.

Remember to use the correct enumerated values for field types, configure relations properly, and ensure field names match the API response exactly. This attention to detail will help avoid common issues and ensure your SDTF schemas work as expected.