Skip to main content

Creating Entities

Learn how to define custom entities in Stack9 - the foundation of your application's data model. This guide covers all entity configuration options, field types, validation rules, and best practices for building robust data structures.

What You'll Learn

  • ✅ Entity structure and configuration
  • ✅ All available field types and their options
  • ✅ Validation rules and constraints
  • ✅ Relationships between entities
  • ✅ Entity features (comments, tasks, attachments)
  • ✅ Workflow definitions
  • ✅ Best practices for entity design

Time Required: 30-45 minutes

Prerequisites

  • Stack9 instance initialized
  • Basic understanding of JSON
  • Text editor or IDE

Understanding Entities

Entities in Stack9 are the core building blocks of your application. They define:

  • Data structure - What fields and types your records contain
  • Validation rules - What data is valid and required
  • Relationships - How entities connect to each other
  • Behavior - Comments, tasks, attachments, workflows
  • Database schema - Automatically generated tables and indexes

Stack9 entities are configuration-driven: you define them in JSON files, and Stack9 automatically generates:

  • Database tables and migrations
  • TypeScript types and models
  • REST API endpoints
  • Admin UI forms and screens
  • Validation logic

Entity File Structure

Entities are defined in src/entities/custom/ directory:

src/
└── entities/
└── custom/
├── customer.json
├── product.json
└── order.json

Basic Entity Definition

Let's create a simple Contact entity:

File: src/entities/custom/contact.json

{
"head": {
"name": "Contact",
"key": "contact",
"pluralisedName": "contacts",
"icon": "UserOutlined",
"allowComments": false,
"allowTasks": false,
"isActive": true
},
"fields": [
{
"label": "First Name",
"key": "first_name",
"type": "TextField",
"placeholder": "Enter first name",
"description": "Contact's first name",
"validateRules": {
"required": true,
"maxLength": 100,
"minLength": 2
},
"index": true
},
{
"label": "Last Name",
"key": "last_name",
"type": "TextField",
"placeholder": "Enter last name",
"validateRules": {
"required": true,
"maxLength": 100
},
"index": true
},
{
"label": "Email",
"key": "email",
"type": "TextField",
"placeholder": "email@example.com",
"validateRules": {
"required": true,
"pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
},
"index": true
},
{
"label": "Phone",
"key": "phone",
"type": "TextField",
"placeholder": "+1 (555) 123-4567"
},
{
"label": "Status",
"key": "status",
"type": "OptionSet",
"typeOptions": {
"values": ["Active", "Inactive", "Archived"]
},
"defaultValue": "Active",
"validateRules": {
"required": true
}
}
],
"hooks": []
}

Head Configuration

The head section defines entity metadata:

PropertyTypeRequiredDescription
namestringYesDisplay name (singular)
keystringYesUnique identifier (snake_case)
pluralisedNamestringYesPlural form for lists
iconstringNoAnt Design icon name
allowCommentsbooleanNoEnable comments feature
allowTasksbooleanNoEnable tasks feature
allowAttachmentsbooleanNoEnable file attachments
isActivebooleanNoWhether entity is active (default: true)
maxAttachmentSizeAllowednumberNoMax attachment size in bytes
acceptedAttachmentFileTypesarrayNoAllowed MIME types

All Field Types

1. TextField

Single-line text input.

{
"label": "Title",
"key": "title",
"type": "TextField",
"placeholder": "Enter title",
"description": "Short title or heading",
"validateRules": {
"required": true,
"maxLength": 200,
"minLength": 3,
"pattern": "^[a-zA-Z0-9 ]+$"
},
"behaviourOptions": {
"readOnly": false
},
"index": true
}

2. NumericField

Number input with decimal support.

{
"label": "Price",
"key": "price",
"type": "NumericField",
"placeholder": "0.00",
"description": "Product price in dollars",
"validateRules": {
"required": true,
"min": 0,
"max": 9999999
},
"typeOptions": {
"decimals": 2,
"allowNegative": false
},
"defaultValue": 0
}

typeOptions:

  • decimals - Number of decimal places (default: 0)
  • allowNegative - Allow negative numbers (default: true)

3. DateField

Date/time picker.

{
"label": "Birth Date",
"key": "birth_date",
"type": "DateField",
"validateRules": {
"required": true
},
"typeOptions": {
"time": false
}
}

typeOptions:

  • time - Include time picker (default: false)

4. Checkbox

Boolean true/false field.

{
"label": "Is Active",
"key": "is_active",
"type": "Checkbox",
"defaultValue": true,
"description": "Whether this record is active"
}

5. OptionSet

Dropdown with predefined options.

{
"label": "Priority",
"key": "priority",
"type": "OptionSet",
"typeOptions": {
"values": ["Low", "Medium", "High", "Urgent"]
},
"defaultValue": "Medium",
"validateRules": {
"required": true
}
}

6. RichTextEditor

Multi-line HTML editor.

{
"label": "Description",
"key": "description",
"type": "RichTextEditor",
"description": "Detailed description with formatting"
}

7. FileField

File upload field.

{
"label": "Profile Picture",
"key": "profile_picture",
"type": "FileField",
"typeOptions": {
"accept": ["image/jpeg", "image/jpg", "image/png"],
"isPublic": false
}
}

typeOptions:

  • accept - Array of allowed MIME types
  • isPublic - Whether file is publicly accessible

8. SingleDropDown

Reference to another entity (foreign key).

{
"label": "Category",
"key": "category_id",
"type": "SingleDropDown",
"relationshipOptions": {
"ref": "category"
},
"typeOptions": {
"label": "name"
},
"validateRules": {
"required": true
}
}

relationshipOptions:

  • ref - Entity key to reference

typeOptions:

  • label - Field to display from related entity

9. MultiDropDown

Multiple references (many-to-many).

{
"label": "Tags",
"key": "tags",
"type": "MultiDropDown",
"relationshipOptions": {
"ref": "tag"
},
"typeOptions": {
"label": "name",
"value": "id"
}
}

10. Grid

Related entity list (one-to-many).

{
"label": "Order Items",
"key": "order_items",
"type": "Grid",
"relationshipOptions": {
"ref": "order_item"
},
"typeOptions": {
"relationshipField": "order_id"
}
}

typeOptions:

  • relationshipField - Foreign key field in child entity

Validation Rules

All field types support these validation rules:

{
"validateRules": {
"required": true,
"maxLength": 200,
"minLength": 3,
"max": 100,
"min": 0,
"pattern": "^[A-Z0-9-]+$"
}
}
RuleApplies ToDescription
requiredAllField must have a value
maxLengthTextMaximum character count
minLengthTextMinimum character count
maxNumericMaximum value
minNumericMinimum value
patternTextRegular expression pattern

Behavior Options

Control field behavior:

{
"behaviourOptions": {
"readOnly": true,
"hidden": false
}
}
OptionDescription
readOnlyField cannot be edited after creation
hiddenField is hidden in UI (still in database)

Entity with Attachments

Enable file attachments on an entity:

{
"head": {
"name": "Document",
"key": "document",
"pluralisedName": "documents",
"allowAttachments": true,
"maxAttachmentSizeAllowed": 10000000,
"acceptedAttachmentFileTypes": [
"application/pdf",
"image/jpeg",
"image/png",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
]
},
"fields": []
}

Entity with Comments and Tasks

Enable collaboration features:

{
"head": {
"name": "Project",
"key": "project",
"pluralisedName": "projects",
"allowComments": true,
"allowTasks": true
},
"fields": []
}

Advanced Example: Order Entity

Complete example with multiple field types and relationships:

File: src/entities/custom/order.json

{
"head": {
"name": "Order",
"key": "order",
"pluralisedName": "orders",
"icon": "ShoppingCartOutlined",
"allowComments": true,
"allowTasks": true,
"allowAttachments": true,
"isActive": true
},
"fields": [
{
"label": "Order Number",
"key": "order_number",
"type": "TextField",
"placeholder": "ORD-20250111-0001",
"description": "Unique order identifier",
"validateRules": {
"required": true,
"pattern": "^ORD-[0-9]{8}-[0-9]{4}$"
},
"behaviourOptions": {
"readOnly": true
},
"index": true
},
{
"label": "Customer",
"key": "customer_id",
"type": "SingleDropDown",
"relationshipOptions": {
"ref": "customer"
},
"typeOptions": {
"label": "{{name}} ({{email}})"
},
"validateRules": {
"required": true
}
},
{
"label": "Order Date",
"key": "order_date",
"type": "DateField",
"typeOptions": {
"time": true
},
"validateRules": {
"required": true
}
},
{
"label": "Status",
"key": "status",
"type": "OptionSet",
"typeOptions": {
"values": ["Draft", "Pending", "Processing", "Shipped", "Delivered", "Cancelled"]
},
"defaultValue": "Draft",
"validateRules": {
"required": true
}
},
{
"label": "Order Items",
"key": "order_items",
"type": "Grid",
"relationshipOptions": {
"ref": "order_item"
},
"typeOptions": {
"relationshipField": "order_id"
}
},
{
"label": "Subtotal",
"key": "subtotal",
"type": "NumericField",
"typeOptions": {
"decimals": 2,
"allowNegative": false
},
"behaviourOptions": {
"readOnly": true
},
"defaultValue": 0
},
{
"label": "Tax Amount",
"key": "tax_amount",
"type": "NumericField",
"typeOptions": {
"decimals": 2,
"allowNegative": false
},
"behaviourOptions": {
"readOnly": true
},
"defaultValue": 0
},
{
"label": "Total",
"key": "total",
"type": "NumericField",
"typeOptions": {
"decimals": 2,
"allowNegative": false
},
"behaviourOptions": {
"readOnly": true
},
"defaultValue": 0
},
{
"label": "Notes",
"key": "notes",
"type": "RichTextEditor",
"description": "Internal notes about the order"
}
],
"hooks": []
}

Entity Folders

Organize entities into folders for better management:

File: src/entities/entity-folders.json

{
"folders": [
{
"key": "crm",
"name": "CRM",
"order": 1
},
{
"key": "products",
"name": "Products",
"order": 2
},
{
"key": "orders",
"name": "Orders",
"order": 3
}
]
}

Then reference folder in entity:

{
"head": {
"name": "Customer",
"key": "customer"
},
"fields": [],
"folderKey": "crm"
}

Testing Your Entity

After creating an entity:

  1. Restart Stack9:
npm run dev
  1. Check Database: Stack9 automatically creates:
  • Table: customers (plural of entity key)
  • Columns for each field
  • Indexes where specified
  • Foreign key constraints
  1. Test via API:
# Create a contact
curl -X POST http://localhost:3000/api/contact \
-H "Content-Type: application/json" \
-d '{
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com",
"phone": "+1 555-123-4567",
"status": "Active"
}'

# Get contact
curl http://localhost:3000/api/contact/1

# Search contacts
curl -X POST http://localhost:3000/api/contact/search \
-H "Content-Type: application/json" \
-d '{
"$where": {
"status": "Active"
},
"$orderBy": [
{"column": "last_name", "order": "asc"}
]
}'

Best Practices

1. Naming Conventions

// ✅ Good
{
"key": "customer", // snake_case
"name": "Customer", // PascalCase
"pluralisedName": "customers" // lowercase plural
}

// ❌ Bad
{
"key": "Customer", // Don't use PascalCase
"name": "customer", // Don't use lowercase
"pluralisedName": "Customers" // Don't capitalize
}

2. Field Keys

// ✅ Good - snake_case
"key": "first_name"
"key": "order_date"
"key": "customer_id"

// ❌ Bad - avoid camelCase or special characters
"key": "firstName"
"key": "order-date"
"key": "customer.id"

3. Index Frequently Queried Fields

{
"label": "Email",
"key": "email",
"type": "TextField",
"index": true // ✅ Index fields used in searches
}

4. Use Descriptive Labels and Descriptions

{
"label": "SKU",
"key": "sku",
"type": "TextField",
"description": "Stock Keeping Unit - unique product identifier", // ✅ Helpful
"placeholder": "e.g., PROD-001" // ✅ Shows expected format
}

5. Set Appropriate Default Values

{
"label": "Status",
"key": "status",
"type": "OptionSet",
"typeOptions": {
"values": ["Draft", "Active", "Archived"]
},
"defaultValue": "Draft" // ✅ Good default
}

6. Make Read-Only Calculated Fields

{
"label": "Total",
"key": "total",
"type": "NumericField",
"behaviourOptions": {
"readOnly": true // ✅ Calculated in hooks
}
}

Common Patterns

Pattern 1: Audit Fields

{
"fields": [
{
"label": "Created By",
"key": "_created_by",
"type": "SingleDropDown",
"relationshipOptions": {
"ref": "user"
},
"behaviourOptions": {
"readOnly": true
}
},
{
"label": "Updated By",
"key": "_updated_by",
"type": "SingleDropDown",
"relationshipOptions": {
"ref": "user"
},
"behaviourOptions": {
"readOnly": true
}
}
]
}

Pattern 2: Status with Colors

{
"label": "Status",
"key": "status",
"type": "OptionSet",
"typeOptions": {
"values": ["Draft", "Active", "Completed", "Cancelled"]
}
}

Pattern 3: Hierarchical Data

{
"label": "Parent Category",
"key": "parent_category_id",
"type": "SingleDropDown",
"relationshipOptions": {
"ref": "category"
},
"typeOptions": {
"label": "name"
}
}

Troubleshooting

Entity Not Appearing

Problem: Created entity but not seeing it in UI or API

Solutions:

  1. Restart Stack9: npm run dev
  2. Check file is in src/entities/custom/
  3. Validate JSON syntax
  4. Check logs for errors
  5. Ensure isActive: true in head

Field Not Saving

Problem: Field value doesn't persist

Solutions:

  1. Check field key is unique
  2. Verify field type is correct
  3. Check validation rules aren't too strict
  4. Look for errors in browser console

Relationship Not Working

Problem: SingleDropDown or MultiDropDown not showing options

Solutions:

  1. Verify relationshipOptions.ref matches target entity key
  2. Ensure target entity exists and has records
  3. Check typeOptions.label references valid field

Validation Failing

Problem: Cannot save valid data

Solutions:

  1. Check regex pattern is correct (escape special characters)
  2. Verify min/max values are appropriate
  3. Test pattern with online regex tester
  4. Check for conflicts between rules

Next Steps

Now that you've mastered creating entities:

Summary

You now understand:

✅ Entity structure and configuration ✅ All available field types ✅ Validation rules and constraints ✅ Relationships (SingleDropDown, MultiDropDown, Grid) ✅ Entity features (comments, tasks, attachments) ✅ Best practices for entity design

Entities are the foundation of your Stack9 application. Well-designed entities make everything else easier!