Common Patterns
Top 10 patterns for building applications with ObjectStack — CRUD, search, auth, realtime, and more
Common Patterns Guide
This guide covers the most common patterns you will use when building applications with ObjectStack. Each pattern includes a complete, copy-pasteable example.
Import: import { defineStack, defineView, defineApp, defineFlow, defineAgent } from '@objectstack/spec'
1. CRUD Object Definition
Define a basic object with common fields for create, read, update, and delete operations.
import { defineStack } from '@objectstack/spec';
export default defineStack({
objects: [
{
name: 'project',
label: 'Project',
fields: {
title: { label: 'Title', type: 'text', required: true, maxLength: 200 },
description: { label: 'Description', type: 'textarea' },
status: { label: 'Status', type: 'select', options: [
{ label: 'Planning', value: 'planning', default: true },
{ label: 'Active', value: 'active' },
{ label: 'Complete', value: 'complete' },
{ label: 'Archived', value: 'archived' }
]},
owner: { label: 'Owner', type: 'lookup', reference: 'user' },
due_date: { label: 'Due Date', type: 'date' },
budget: { label: 'Budget', type: 'currency', currencyConfig: { precision: 2, defaultCurrency: 'USD' } },
}
}
]
});import { defineStack } from '@objectstack/spec';
export default defineStack({
objects: {
project: {
label: 'Project',
fields: {
title: { label: 'Title', type: 'text', required: true, maxLength: 200 },
description: { label: 'Description', type: 'textarea' },
status: { label: 'Status', type: 'select', options: [
{ label: 'Planning', value: 'planning', default: true },
{ label: 'Active', value: 'active' },
{ label: 'Complete', value: 'complete' },
{ label: 'Archived', value: 'archived' }
]},
owner: { label: 'Owner', type: 'lookup', reference: 'user' },
due_date: { label: 'Due Date', type: 'date' },
budget: { label: 'Budget', type: 'currency', currencyConfig: { precision: 2, defaultCurrency: 'USD' } },
},
},
},
});2. Master-Detail Relationship
Create a parent-child relationship where child records are cascaded on delete.
{
objects: [
{
name: 'order',
label: 'Order',
fields: {
order_number: { label: 'Order #', type: 'autonumber', autonumberFormat: 'ORD-{0000}' },
customer: { label: 'Customer', type: 'lookup', reference: 'contact' },
total: { label: 'Total', type: 'summary', summaryOperations: ['sum'] },
status: { label: 'Status', type: 'select', options: [
{ label: 'Draft', value: 'draft', default: true },
{ label: 'Submitted', value: 'submitted' },
{ label: 'Fulfilled', value: 'fulfilled' }
]},
}
},
{
name: 'order_line',
label: 'Order Line',
fields: {
order: { label: 'Order', type: 'master_detail', reference: 'order' },
product: { label: 'Product', type: 'lookup', reference: 'product' },
quantity: { label: 'Qty', type: 'number', min: 1, required: true },
unit_price: { label: 'Unit Price', type: 'currency' },
line_total: { label: 'Line Total', type: 'formula', expression: 'quantity * unit_price' },
}
}
]
}3. Search & Filtered List View
Create a list view with pre-configured filters and column display.
import { defineView } from '@objectstack/spec';
export const taskBoard = defineView({
name: 'open_tasks',
label: 'Open Tasks',
object: 'task',
type: 'list',
listType: 'grid',
columns: [
{ field: 'title', width: 300 },
{ field: 'status', width: 120 },
{ field: 'priority', width: 100 },
{ field: 'assigned_to', width: 150 },
{ field: 'due_date', width: 120 }
],
defaultFilters: {
status: { $ne: 'closed' }
},
defaultSort: [
{ field: 'priority', order: 'desc' },
{ field: 'due_date', order: 'asc' }
]
});4. Kanban Board View
Display records as a kanban board grouped by a status field.
import { defineView } from '@objectstack/spec';
export const kanban = defineView({
name: 'task_board',
label: 'Task Board',
object: 'task',
type: 'list',
listType: 'kanban',
kanbanConfig: {
groupByField: 'status',
cardTitle: 'title',
cardSubtitle: 'assigned_to',
showCount: true
}
});5. Application with Navigation
Define an application with a structured navigation menu.
import { defineApp } from '@objectstack/spec';
export const crm = defineApp({
name: 'crm',
label: 'CRM',
description: 'Customer Relationship Management',
navigation: [
{
type: 'object',
object: 'contact',
label: 'Contacts',
icon: 'users'
},
{
type: 'object',
object: 'deal',
label: 'Deals',
icon: 'dollar-sign'
},
{
type: 'object',
object: 'activity',
label: 'Activities',
icon: 'activity'
},
{
type: 'dashboard',
dashboard: 'sales_dashboard',
label: 'Dashboard',
icon: 'bar-chart'
}
]
});6. Automated Flow (Record-Triggered)
Create an automation that fires when a record is created or updated.
import { defineFlow } from '@objectstack/spec';
export const assignmentNotification = defineFlow({
name: 'task_assignment_notification',
label: 'Task Assignment Notification',
type: 'autolaunched',
trigger: {
type: 'record_change',
object: 'task',
event: 'after_update',
conditions: {
field: 'assigned_to',
changed: true
}
},
steps: [
{
type: 'action',
name: 'send_notification',
action: 'send_email',
config: {
to: '{{record.assigned_to.email}}',
subject: 'Task Assigned: {{record.title}}',
body: 'You have been assigned to task "{{record.title}}".'
}
}
]
});7. Approval Workflow
Define a multi-step approval process for records.
{
objects: [{
name: 'expense_report',
label: 'Expense Report',
fields: {
title: { label: 'Title', type: 'text', required: true },
amount: { label: 'Amount', type: 'currency' },
status: { label: 'Status', type: 'select', options: [
{ label: 'Draft', value: 'draft', default: true },
{ label: 'Submitted', value: 'submitted' },
{ label: 'Approved', value: 'approved' },
{ label: 'Rejected', value: 'rejected' }
]},
submitted_by: { label: 'Submitted By', type: 'lookup', reference: 'user' },
approved_by: { label: 'Approved By', type: 'lookup', reference: 'user' },
},
enable: {
trackHistory: true,
apiEnabled: true
}
}],
flows: [{
name: 'expense_approval',
label: 'Expense Approval',
type: 'autolaunched',
trigger: {
type: 'record_change',
object: 'expense_report',
event: 'after_update'
},
steps: [
{
type: 'decision',
name: 'check_amount',
conditions: [
{ name: 'auto_approve', condition: '{{record.amount}} < 100' },
{ name: 'needs_approval', condition: '{{record.amount}} >= 100' }
]
}
]
}]
}8. AI Agent Configuration
Configure an AI agent with specific tools and instructions.
import { defineAgent } from '@objectstack/spec';
export const supportAgent = defineAgent({
name: 'support_agent',
label: 'Customer Support Agent',
role: 'assistant',
model: 'gpt-4o',
instructions: `You are a helpful customer support agent.
You can search for customer records, look up order status,
and create support tickets. Always be polite and professional.`,
tools: [
{
type: 'object_query',
object: 'contact',
description: 'Search customer records'
},
{
type: 'object_query',
object: 'order',
description: 'Look up order status'
},
{
type: 'object_create',
object: 'support_ticket',
description: 'Create a support ticket'
}
],
temperature: 0.3,
maxTokens: 2048
});9. Pagination Pattern
Implement pagination for large datasets using offset-based or cursor-based pagination.
Offset-Based (Simple)
// Page 1
const page1 = {
object: 'contact',
fields: ['id', 'name', 'email'],
orderBy: [{ field: 'name', order: 'asc' }],
limit: 25,
offset: 0
};
// Page 2
const page2 = { ...page1, offset: 25 };
// Page 3
const page3 = { ...page1, offset: 50 };Cursor-Based (Scalable)
// First page
const firstPage = {
object: 'activity',
fields: ['id', 'type', 'description', 'created_at'],
orderBy: [{ field: 'created_at', order: 'desc' }],
limit: 50,
keyset: {
field: 'id',
order: 'desc'
}
};
// Next page (using cursor from previous response)
const nextPage = {
...firstPage,
keyset: {
...firstPage.keyset,
after: 'last_id_from_previous_page'
}
};10. Field-Level Security
Restrict field visibility and editability based on user profiles.
{
objects: [{
name: 'employee',
label: 'Employee',
fields: {
name: { label: 'Name', type: 'text', required: true },
email: { label: 'Email', type: 'email', required: true },
department: { label: 'Department', type: 'lookup', reference: 'department' },
// Sensitive fields with encryption
salary: { label: 'Salary', type: 'currency',
hidden: true, // Hidden from default views
encryptionConfig: {
algorithm: 'aes-256-gcm',
keyRotation: true
}
},
ssn: { label: 'SSN', type: 'text',
hidden: true,
maskingRule: {
strategy: 'partial',
visibleChars: 4,
maskChar: '*',
position: 'end'
},
encryptionConfig: {
algorithm: 'aes-256-gcm',
keyRotation: true
}
},
}
}]
}Putting It All Together
Combine these patterns into a complete defineStack() configuration:
import { defineStack } from '@objectstack/spec';
export default defineStack({
objects: [
// Pattern 1: CRUD objects
{ name: 'project', label: 'Project', fields: {/* ... */} },
// Pattern 2: Master-detail
{ name: 'task', label: 'Task', fields: {/* ... */} },
],
views: [
// Pattern 3: List views
// Pattern 4: Kanban boards
],
apps: [
// Pattern 5: Navigation
],
flows: [
// Pattern 6: Automations
// Pattern 7: Approval workflows
],
agents: [
// Pattern 8: AI agents
]
});import { defineStack } from '@objectstack/spec';
export default defineStack({
objects: {
// Pattern 1: CRUD objects
project: { label: 'Project', fields: {/* ... */} },
// Pattern 2: Master-detail
task: { label: 'Task', fields: {/* ... */} },
},
apps: {
// Pattern 5: Navigation
},
flows: {
// Pattern 6: Automations
// Pattern 7: Approval workflows
},
agents: {
// Pattern 8: AI agents
},
});Next Steps:
- Field Type Gallery — Choose the right field type
- Query Cheat Sheet — Write powerful queries
- Error Catalog — Handle errors gracefully