Metadata Reference
Comprehensive metadata examples for @objectstack/spec — covering data, UI, automation, AI, and integration patterns
Metadata Reference
This guide provides representative examples of every major metadata type in @objectstack/spec. Use these patterns as a starting point when building HotCRM packages.
Data Metadata
Object Definition
Every business entity starts with an ObjectSchema. Fields use the Field factory for strict typing.
// packages/crm/src/account.object.ts
import { ObjectSchema, Field } from '@objectstack/spec/data';
export const Account = ObjectSchema.create({
name: 'account',
label: 'Account',
pluralLabel: 'Accounts',
icon: 'building',
description: 'Companies and organizations',
fields: {
name: Field.text({ label: 'Account Name', required: true, unique: true, maxLength: 255, searchable: true }),
type: Field.select({
label: 'Account Type',
options: [
{ label: 'Prospect', value: 'Prospect' },
{ label: 'Customer', value: 'Customer' },
{ label: 'Partner', value: 'Partner' }
],
defaultValue: 'Prospect'
}),
annual_revenue: Field.currency({ label: 'Annual Revenue', precision: 2, min: 0 }),
parent_account: Field.lookup({ label: 'Parent Account', reference_to: 'account', cascade_delete: false }),
created_date: Field.datetime({ label: 'Created Date', readonly: true, defaultValue: 'NOW()' })
},
enable: { searchable: true, trackHistory: true, feeds: true, files: true, apiEnabled: true }
});Field Types
All field types available through the Field factory:
fields: {
// Text types
text_field: Field.text({ label: 'Text', maxLength: 255 }),
textarea_field: Field.textarea({ label: 'Textarea', maxLength: 5000 }),
richtext_field: Field.richtext({ label: 'Rich Text' }),
// Numeric types
number_field: Field.number({ label: 'Number', precision: 2 }),
currency_field: Field.currency({ label: 'Currency', precision: 2 }),
percent_field: Field.percent({ label: 'Percent', precision: 1 }),
// Date/time types
date_field: Field.date({ label: 'Date' }),
datetime_field: Field.datetime({ label: 'DateTime' }),
// Selection types
select_field: Field.select({ label: 'Select', options: [{ label: 'A', value: 'a' }] }),
multiselect_field: Field.multiselect({ label: 'Multi-Select', options: [{ label: 'T1', value: 't1' }] }),
// Relationships
lookup_field: Field.lookup({ label: 'Lookup', reference_to: 'account' }),
master_detail: Field.masterDetail({ label: 'Master-Detail', reference_to: 'account', cascade_delete: true }),
// Communication
email_field: Field.email({ label: 'Email' }),
phone_field: Field.phone({ label: 'Phone' }),
url_field: Field.url({ label: 'URL' }),
// Auto-generated
autonumber_field: Field.autonumber({ label: 'Auto Number', format: 'SHO-{YYYY}-{000000}' }),
// Computed
formula_field: Field.formula({ label: 'Formula', formula: 'quantity * unit_price', returnType: 'currency' }),
summary_field: Field.summary({ label: 'Summary', summarizedObject: 'opportunity', summaryType: 'sum', field: 'amount' }),
// Special
boolean_field: Field.boolean({ label: 'Boolean' }),
geolocation_field: Field.geolocation({ label: 'Location' }),
encrypted_field: Field.encrypted({ label: 'Encrypted Data' }),
json_field: Field.json({ label: 'JSON Data' })
}Relationships
Relationships are expressed through lookup fields, master-detail, and rollup summaries.
// packages/crm/src/opportunity.object.ts
import { ObjectSchema, Field } from '@objectstack/spec/data';
export const Opportunity = ObjectSchema.create({
name: 'opportunity',
label: 'Opportunity',
fields: {
name: Field.text({ label: 'Opportunity Name', required: true }),
// Master-detail — cascade deletes with parent
account: Field.masterDetail({
label: 'Account', reference_to: 'account', cascade_delete: true, required: true
}),
// Lookup — no cascade
primary_contact: Field.lookup({
label: 'Primary Contact', reference_to: 'contact', cascade_delete: false
}),
// Rollup summary — aggregate child records
total_quote_amount: Field.summary({
label: 'Total Quote Amount', summarizedObject: 'quote',
summaryType: 'sum', field: 'total_amount', filter: [['status', '=', 'Approved']]
}),
// Formula — cross-object reference
days_to_close: Field.formula({ label: 'Days to Close', formula: 'close_date - TODAY()', returnType: 'number' }),
account_owner: Field.formula({ label: 'Account Owner', formula: 'account.owner', returnType: 'lookup', reference_to: 'user' })
}
});UI Metadata
Page Layout
Page layouts define record detail views with field sections, related lists, and custom components.
// packages/crm/src/account.page.ts
import { PageSchema } from '@objectstack/spec/ui';
export const AccountPage = PageSchema.create({
name: 'account_detail',
object: 'account',
type: 'record',
layout: {
type: 'tabs', // or 'accordion', 'wizard'
sections: [
{
label: 'Account Information',
columns: 2,
fields: ['name', 'account_number', 'type', 'industry', 'phone', 'website', 'annual_revenue']
},
{
label: 'Opportunities',
type: 'related_list',
object: 'opportunity',
columns: ['name', 'stage', 'amount', 'close_date'],
filters: [['stage', '!=', 'Closed Lost']],
sort: [{ field: 'close_date', direction: 'asc' }],
actions: ['new', 'edit', 'delete']
},
{
label: 'AI Insights',
type: 'component',
component: 'AccountAIInsights',
props: { showHealthScore: true, showChurnRisk: true }
}
]
},
actions: [
{ name: 'edit', label: 'Edit', type: 'standard' },
{ name: 'ai_analyze', label: 'AI Analyze', type: 'custom', handler: 'analyzeWithAI', icon: 'sparkles' }
]
});List Views
List views control column layout, filters, sorting, and inline editing for record tables.
// packages/crm/src/account.view.ts
import { ListView } from '@objectstack/spec/ui';
export const AccountListViews = {
allAccounts: ListView.create({
name: 'all_accounts',
label: 'All Accounts',
object: 'account',
columns: [
{ field: 'name', width: 250, sortable: true, link: true },
{ field: 'type', width: 120, sortable: true },
{ field: 'annual_revenue', width: 150, sortable: true, align: 'right' },
{ field: 'owner', width: 150 },
{ field: 'created_date', width: 150, sortable: true, format: 'YYYY-MM-DD' }
],
sort: [{ field: 'name', direction: 'asc' }],
bulkActions: ['delete', 'update_owner', 'export'],
inlineEdit: true,
pagination: { pageSize: 25, options: [10, 25, 50, 100] }
}),
enterpriseAccounts: ListView.create({
name: 'enterprise_accounts',
label: 'Enterprise Accounts',
object: 'account',
filters: [
{ field: 'annual_revenue', operator: '>', value: 10000000 },
{ field: 'type', operator: '=', value: 'Customer' }
],
columns: [
{ field: 'name', width: 250 },
{ field: 'annual_revenue', width: 150 },
{ field: 'industry', width: 150 }
],
sort: [{ field: 'annual_revenue', direction: 'desc' }]
})
};Automation Metadata
Workflow Rules
Workflows trigger actions (field updates, emails, tasks, HTTP calls) on record events.
// packages/crm/src/lead_assignment.workflow.ts
import { WorkflowRule } from '@objectstack/spec/automation';
export const LeadAutoAssignment = WorkflowRule.create({
name: 'lead_auto_assignment',
label: 'Auto Assign Leads',
object: 'lead',
triggerType: 'onCreate', // or 'onUpdate', 'onDelete'
condition: 'status = "New" AND owner = NULL',
actions: [
{ type: 'fieldUpdate', field: 'owner_id', formula: 'getNextAvailableRep(territory, industry)' },
{ type: 'emailAlert', template: 'new_lead_assigned', recipients: ['owner_id'] },
{ type: 'taskCreation', subject: 'Follow up: ${name}', assignee: '${owner_id}', dueDate: 'TODAY() + 1', priority: 'High' },
{ type: 'httpCall', method: 'POST', url: 'https://api.slack.com/webhooks/...',
headers: { 'Content-Type': 'application/json' },
body: { text: 'New lead assigned: ${name} to ${owner.name}' } }
],
executionOrder: 1,
active: true
});Approval Process
Multi-step approval flows with conditional routing and per-step actions.
// packages/products/src/quote_approval.workflow.ts
import { ApprovalProcess } from '@objectstack/spec/automation';
export const QuoteDiscountApproval = ApprovalProcess.create({
name: 'quote_discount_approval',
object: 'quote',
triggerType: 'onUpdate',
condition: 'discount_percent > 10 AND status = "Draft"',
initialSubmissionActions: [
{ type: 'fieldUpdate', field: 'approval_status', value: 'Pending' },
{ type: 'emailAlert', template: 'approval_request_submitted', recipients: ['${submitter}'] }
],
steps: [
{
stepNumber: 1, name: 'sales_manager_approval',
approverType: 'user', approver: '${owner.manager}',
skipCondition: 'discount_percent <= 15',
approvalActions: [{ type: 'fieldUpdate', field: 'approval_status', value: 'Manager Approved' }],
rejectionActions: [
{ type: 'fieldUpdate', field: 'approval_status', value: 'Rejected' },
{ type: 'emailAlert', template: 'approval_rejected', recipients: ['${owner}'] }
]
},
{
stepNumber: 2, name: 'sales_director_approval',
approverType: 'role', approver: 'sales_director',
skipCondition: 'discount_percent <= 20',
approvalActions: [
{ type: 'fieldUpdate', field: 'approval_status', value: 'Director Approved' },
{ type: 'fieldUpdate', field: 'status', value: 'Approved' }
],
rejectionActions: [{ type: 'fieldUpdate', field: 'approval_status', value: 'Rejected' }]
}
],
finalApprovalActions: [{ type: 'emailAlert', template: 'quote_approved', recipients: ['${owner}'] }],
finalRejectionActions: [{ type: 'emailAlert', template: 'quote_rejected', recipients: ['${owner}'] }]
});State Machine
Declarative lifecycle management with states, transitions, guards, and timeouts.
// packages/support/src/case.statemachine.ts
import { StateMachine } from '@objectstack/spec/automation';
export const CaseLifecycle = StateMachine.create({
name: 'case_lifecycle',
object: 'case',
initial: 'new',
states: [
{
name: 'new',
label: 'New',
onEntry: [{ type: 'fieldUpdate', field: 'status', value: 'New' }],
transitions: [
{ to: 'assigned', event: 'assign', guard: 'owner != NULL',
actions: [{ type: 'emailAlert', template: 'case_assigned', recipients: ['${owner}'] }] }
]
},
{
name: 'assigned',
label: 'Assigned',
onEntry: [{ type: 'fieldUpdate', field: 'status', value: 'In Progress' }],
transitions: [
{ to: 'waiting_customer', event: 'request_info' },
{ to: 'escalated', event: 'escalate', guard: 'sla_violation = true OR priority = "Critical"' },
{ to: 'resolved', event: 'resolve' }
]
},
{
name: 'waiting_customer',
label: 'Waiting on Customer',
timeout: { duration: 72, unit: 'hours', event: 'timeout', to: 'closed' },
transitions: [{ to: 'assigned', event: 'customer_responded' }]
},
{
name: 'closed',
label: 'Closed',
type: 'final',
onEntry: [
{ type: 'fieldUpdate', field: 'closed_date', value: 'NOW()' },
{ type: 'fieldUpdate', field: 'status', value: 'Closed' }
]
}
],
globalGuards: { hasOwner: 'owner != NULL', isNotClosed: 'status != "Closed"' }
});AI Metadata
Agent Definition
AI agents combine a system prompt, callable tools, model config, and safety constraints.
// packages/crm/src/sales_assistant.agent.ts
import { Agent } from '@objectstack/spec/ai';
export const SalesAssistant = Agent.create({
name: 'sales_assistant',
role: 'Sales AI Assistant',
description: 'Helps reps with lead qualification, opportunity management, and deal intelligence',
systemPrompt: `You are an expert sales assistant. Help reps:
1. Qualify leads efficiently
2. Manage opportunities effectively
3. Close deals faster
Always provide actionable insights backed by data.`,
tools: [
{
name: 'scoreLeads',
description: 'Score leads based on fit, intent, and engagement',
action: 'lead_scoring',
parameters: { lead_id: { type: 'string', required: true } }
},
{
name: 'suggestNextSteps',
description: 'Suggest next best actions for an opportunity',
action: 'opportunity_next_steps',
parameters: { opportunity_id: { type: 'string', required: true } }
},
{
name: 'findSimilarDeals',
description: 'Find similar won deals for insights',
action: 'deal_intelligence',
parameters: {
opportunity_id: { type: 'string', required: true },
similarity_factors: { type: 'array', items: ['industry', 'deal_size', 'region'] }
}
}
],
model: { provider: 'openai', model: 'gpt-4', temperature: 0.7, maxTokens: 2000 },
memory: { type: 'conversational', maxMessages: 10, summaryThreshold: 8 },
safety: {
enableContentFilter: true,
allowedDomains: ['sales', 'crm', 'customer_data'],
restrictedActions: ['delete_account', 'transfer_funds']
}
});Predictive Model
Declare ML models with feature engineering, training, and deployment settings.
// packages/crm/src/churn_prediction.model.ts
import { PredictiveModel } from '@objectstack/spec/ai';
export const ChurnPredictionModel = PredictiveModel.create({
name: 'account_churn_prediction',
description: 'Predict customer churn risk',
type: 'classification',
training: {
algorithm: 'random_forest',
features: [
{ name: 'account_age_days', type: 'numeric', source: 'DAYS_BETWEEN(created_date, TODAY())' },
{ name: 'total_revenue', type: 'numeric', source: 'SUM(opportunities.amount WHERE stage = "Closed Won")' },
{ name: 'activity_count_30d', type: 'numeric', source: 'COUNT(activities WHERE created_date >= LAST_30_DAYS)' },
{ name: 'industry', type: 'categorical', source: 'industry', encoding: 'one_hot' }
],
target: { name: 'churned', type: 'binary', positiveClass: true,
definition: 'last_activity_date < LAST_90_DAYS AND status = "Inactive"' },
hyperparameters: { n_estimators: 100, max_depth: 10, class_weight: 'balanced' },
dataSource: {
object: 'account',
filters: [['type', '=', 'Customer'], ['created_date', '<', 'LAST_YEAR']],
splitRatio: { train: 0.7, validation: 0.15, test: 0.15 }
}
},
evaluation: {
metrics: ['accuracy', 'precision', 'recall', 'f1_score', 'auc_roc'],
thresholds: { min_accuracy: 0.80, min_auc_roc: 0.85 }
},
deployment: { endpoint: '/api/ml/predict/churn', batchPrediction: true, realTimePrediction: true },
monitoring: { trackDrift: true, driftThreshold: 0.05, retrainingSchedule: 'monthly' }
});Integration Metadata
Webhooks
Outbound webhooks fire on record events with payload templating and retry logic.
// packages/crm/src/opportunity_webhooks.webhook.ts
import { Webhook } from '@objectstack/spec/automation';
export const DealWonWebhook = Webhook.create({
name: 'deal_won_notification',
object: 'opportunity',
event: 'onUpdate',
condition: 'stage = "Closed Won" AND ISCHANGED(stage)',
url: 'https://api.company.com/webhooks/deal-won',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': '${env.WEBHOOK_API_KEY}',
'X-Signature': '${HMAC_SHA256(payload, env.WEBHOOK_SECRET)}'
},
payload: {
event: 'deal.won',
timestamp: '${NOW()}',
data: {
opportunity_id: '${id}', opportunity_name: '${name}', amount: '${amount}',
account: { id: '${account.id}', name: '${account.name}' },
owner: { id: '${owner.id}', name: '${owner.name}' }
}
},
retry: { enabled: true, maxAttempts: 3, backoffStrategy: 'exponential', initialDelay: 1000 },
responseHandling: {
successCodes: [200, 201, 202],
onSuccess: [{ type: 'fieldUpdate', field: 'webhook_sent_date', value: 'NOW()' }],
onFailure: [{ type: 'emailAlert', template: 'webhook_failed', recipients: ['admin@company.com'] }]
}
});Connector
Connectors define bi-directional integrations — outbound operations and inbound triggers.
// packages/integrations/src/stripe.connector.ts
import { Connector } from '@objectstack/spec/automation';
export const StripeConnector = Connector.create({
name: 'stripe_payment',
category: 'payment',
label: 'Stripe Payment Gateway',
authentication: {
type: 'oauth2',
authUrl: 'https://connect.stripe.com/oauth/authorize',
tokenUrl: 'https://connect.stripe.com/oauth/token',
scopes: ['read_write'],
clientId: '${env.STRIPE_CLIENT_ID}',
clientSecret: '${env.STRIPE_CLIENT_SECRET}'
},
operations: [{
name: 'create_customer',
type: 'create',
endpoint: { url: 'https://api.stripe.com/v1/customers', method: 'POST' },
parameters: [
{ name: 'email', type: 'string', required: true, source: '${account.billing_email}' },
{ name: 'name', type: 'string', source: '${account.name}' }
],
responseMapping: { stripe_customer_id: 'id', stripe_created_at: 'created' }
}],
triggers: [{
name: 'payment_succeeded',
type: 'webhook',
event: 'payment_intent.succeeded',
action: {
type: 'create', object: 'payment',
fields: { amount: '${data.amount / 100}', status: 'Completed', payment_date: 'NOW()', external_id: '${data.id}' }
}
}]
});Advanced Features
Validation Rules
Formula-based conditions that enforce data quality.
// packages/crm/src/opportunity.validation.ts
import { ValidationRule } from '@objectstack/spec/data';
export const OpportunityValidations = {
amountRange: ValidationRule.create({
name: 'opportunity_amount_range', object: 'opportunity', field: 'amount',
condition: 'amount >= 1000 AND amount <= 10000000',
errorMessage: 'Opportunity amount must be between $1,000 and $10,000,000',
active: true
}),
closeDateFuture: ValidationRule.create({
name: 'close_date_future', object: 'opportunity', field: 'close_date',
condition: 'close_date >= TODAY()',
errorMessage: 'Close date must be in the future',
when: 'stage NOT IN ["Closed Won", "Closed Lost"]',
active: true
}),
stageProgression: ValidationRule.create({
name: 'stage_progression_check', object: 'opportunity',
condition: `IF(ISCHANGED(stage),
CASE stage
WHEN "Qualification" THEN has_decision_maker = true
WHEN "Proposal" THEN has_budget_confirmed = true AND has_timeline = true
WHEN "Closed Won" THEN has_signed_contract = true
ELSE true
END, true)`,
errorMessage: 'Stage progression requirements not met',
active: true
})
};Permission Configuration
Permission sets control object-level, field-level, and record-level access.
// packages/core/src/permissions/sales_rep.permission.ts
import { PermissionSet } from '@objectstack/spec/system';
export const SalesRepPermissions = PermissionSet.create({
name: 'sales_representative',
label: 'Sales Representative',
objectPermissions: [
{ object: 'account', create: true, read: true, update: true, delete: false, viewAll: false },
{ object: 'opportunity', create: true, read: true, update: true, delete: false }
],
fieldPermissions: [
{ object: 'opportunity', field: 'discount_percent', read: true, edit: true, constraint: 'value <= 10' },
{ object: 'account', field: 'annual_revenue', read: true, edit: false }
],
recordAccess: {
account: {
ownedRecords: true,
teamRecords: true,
sharingRules: [{ name: 'territory_based_sharing', condition: 'territory = ${user.territory}' }]
}
}
});Summary
This reference covers the major @objectstack/spec metadata categories:
| Category | Spec Module | File Suffix |
|---|---|---|
| Data (Objects, Fields) | @objectstack/spec/data | *.object.ts |
| UI (Pages, Views) | @objectstack/spec/ui | *.page.ts, *.view.ts |
| Automation (Workflows, State Machines) | @objectstack/spec/automation | *.workflow.ts |
| AI (Agents, Models) | @objectstack/spec/ai | *.agent.ts, *.model.ts |
| Integration (Webhooks, Connectors) | @objectstack/spec/automation | *.webhook.ts, *.connector.ts |
| System (Permissions, i18n) | @objectstack/spec/system | *.permission.ts, *.i18n.ts |
Next Steps
- Creating Objects — Step-by-step object creation guide
- Business Logic — Hooks and triggers
- Actions — API endpoints and AI tools
- Workflows — Rule-based automation
- Page Layouts — Record detail pages
- List Views — Record list tables
- UI Development — Dashboards and custom components