ObjectStackObjectStack

Example Apps

Run and explore the built-in example applications to learn ObjectStack hands-on

Example Apps

The monorepo includes 2 ready-to-run examples that progressively demonstrate ObjectStack features — from a simple Todo app to a reusable BI plugin template. For a full enterprise reference, see the HotCRM repository.

app-todo

The simplest starting point. One object, basic CRUD, dashboards, seed data.

HotCRM (external)

Full-featured CRM: 10+ objects, AI agents, flows, security, sharing rules.

plugin-bi

A reusable plugin template. Shows how to package metadata for distribution.

Looking for the production server example? It has been moved to apps/objectos/ — see the Server documentation for multi-app orchestration and deployment.


Quick Run

The fastest way to explore all examples at once:

git clone https://github.com/nickstenning/spec.git
cd spec
pnpm install
pnpm studio

Open http://localhost:3000/_studio/ — the root objectstack.config.ts aggregates Todo + CRM + BI into one dev workspace.


app-todo — Your First App

Path: examples/app-todo/

The simplest complete ObjectStack application. Perfect for understanding the basics.

What's Inside

examples/app-todo/
├── objectstack.config.ts      # App manifest + metadata wiring
├── src/
│   ├── objects/
│   │   └── task.object.ts     # Task object: 10 fields, validations, indexes
│   ├── actions/
│   │   └── task.actions.ts    # Complete, Start, Defer, Delete batch
│   ├── apps/
│   │   └── todo.app.ts        # Navigation: Tasks, Analytics
│   ├── dashboards/
│   │   └── task.dashboard.ts  # Widget grid: stats, charts, lists
│   ├── reports/
│   │   └── task.report.ts     # Tabular + summary reports
│   └── flows/
│       └── task.flow.ts       # Auto-assignment automation

Key Concepts Demonstrated

ConceptFileWhat You'll Learn
Object & Fieldstask.object.tsField.text(), Field.select(), field options, indexes
Seed Dataobjectstack.config.tsdata array with upsert mode, 8 sample records
Actionstask.actions.tsScript actions, modal actions with params
App Navigationtodo.app.tsNavigation groups, object links, dashboard links
Automationtask.flow.tsAutolaunched flow with record-triggered logic

Run It

cd examples/app-todo
pnpm dev
# Already included in root objectstack.config.ts
pnpm studio

Code Walkthrough

1. Define an objectsrc/objects/task.object.ts:

import { ObjectSchema, Field } from '@objectstack/spec/data';

export const Task = ObjectSchema.create({
  name: 'task',
  label: 'Task',
  pluralLabel: 'Tasks',
  icon: 'check-square',

  fields: {
    subject: Field.text({
      label: 'Subject',
      required: true,
      searchable: true,
    }),
    status: {
      type: 'select',
      label: 'Status',
      required: true,
      options: [
        { label: 'Not Started', value: 'not_started', default: true },
        { label: 'In Progress', value: 'in_progress' },
        { label: 'Completed', value: 'completed' },
      ],
    },
    due_date: Field.date({ label: 'Due Date' }),
    priority: {
      type: 'select',
      label: 'Priority',
      options: [
        { label: 'Low', value: 'low' },
        { label: 'Normal', value: 'normal' },
        { label: 'High', value: 'high' },
        { label: 'Urgent', value: 'urgent' },
      ],
    },
  },
});

2. Wire it upobjectstack.config.ts:

import { defineStack } from '@objectstack/spec';
import * as objects from './src/objects';
import * as actions from './src/actions';
import * as apps from './src/apps';

export default defineStack({
  manifest: {
    id: 'com.example.todo',
    namespace: 'todo',
    version: '2.0.0',
    type: 'app',
    name: 'Todo Manager',
  },

  objects: Object.values(objects),
  actions: Object.values(actions),
  apps: Object.values(apps),

  // Seed data loaded at startup
  data: [{
    object: 'task',
    mode: 'upsert',
    externalId: 'subject',
    records: [
      { subject: 'Learn ObjectStack', status: 'completed', priority: 'high' },
      { subject: 'Build a cool app', status: 'in_progress', priority: 'normal' },
    ],
  }],
});

3. The server auto-detects what you need: ObjectQL engine → InMemory driver → Hono HTTP server → REST API at /api/v1/task.


HotCRM — Enterprise Scale (external repo)

Repo: github.com/objectstack-ai/hotcrm

A production-grade CRM demonstrating every ObjectStack feature. Use this as a reference for building real enterprise apps. It lives in a dedicated repository so it can evolve independently of the framework.

What's Inside

hotcrm/
├── objectstack.config.ts
├── src/
│   ├── objects/          # 10 objects: Account, Contact, Lead, Opportunity, ...
│   ├── actions/          # 15+ actions per domain
│   ├── agents/           # 5 AI agents: Sales, Service, Lead Enrichment, ...
│   ├── apis/             # Custom REST endpoints: lead-convert, pipeline-stats
│   ├── apps/             # CRM app with full navigation tree
│   ├── dashboards/       # Executive, Sales, Service dashboards
│   ├── data/             # Seed data for all objects
│   ├── flows/            # 5 automation flows: case escalation, approval, ...
│   ├── profiles/         # 5 security profiles: admin, sales rep, manager, ...
│   ├── rag/              # 4 RAG pipelines: product info, competitive intel, ...
│   ├── reports/          # 7 reports with tabular, summary, matrix formats
│   └── sharing/          # Sharing rules, role hierarchy, org defaults

Features Showcased

FeatureDescription
10 Business ObjectsAccount, Contact, Lead, Opportunity, Case, Campaign, Product, Quote, Contract, Task
State MachineLead lifecycle: new → contacted → qualified → converted with Lead.state.ts
AI AgentsSales Assistant, Service Agent, Revenue Intelligence, Email Campaign, Lead Enrichment
RAG PipelinesProduct info, competitive intel, sales knowledge, support knowledge
Security Model5 profiles (Admin, Manager, Rep, Agent, Marketing), sharing rules, role hierarchy
AutomationLead conversion, case escalation, opportunity approval, campaign enrollment, quote generation
Custom APIsPOST /lead-convert, GET /pipeline-stats
Seed Data50+ realistic records across all objects

Run It

git clone https://github.com/objectstack-ai/hotcrm.git
cd hotcrm
pnpm install
pnpm dev        # Starts with auto-detected plugins

Or inspect its metadata without starting a server:

cd hotcrm
os info          # Show object/field/flow/agent counts
os validate      # Check schema correctness
os compile       # Build to dist/objectstack.json

plugin-bi — Reusable Plugin

Path: examples/plugin-bi/

A minimal plugin template showing how to package metadata for distribution. Plugins use type: 'plugin' in their manifest and can be composed into any host app.

// examples/plugin-bi/objectstack.config.ts
import { defineStack } from '@objectstack/spec';

export default defineStack({
  manifest: {
    id: 'com.example.bi',
    namespace: 'bi',
    version: '1.0.0',
    type: 'plugin',        // ← Plugin, not app
    name: 'BI Plugin',
    description: 'Business Intelligence dashboards and analytics',
  },
  objects: [],
  dashboards: [],
});

Key difference from apps: Plugins don't run standalone — they're loaded by a host app via AppPlugin:

import BiPlugin from '../plugin-bi/objectstack.config';
new AppPlugin(BiPlugin)  // Load into host

Multi-App Composition Pattern

The production server at apps/objectos/ demonstrates the Platform Server pattern: one host loading multiple apps and plugins. This is how you'd run ObjectStack in production with multiple teams' apps.

// apps/objectos/objectstack.config.ts
import { defineStack } from '@objectstack/spec';
import { AppPlugin, DriverPlugin } from '@objectstack/runtime';
import { ObjectQLPlugin } from '@objectstack/objectql';
import { InMemoryDriver } from '@objectstack/driver-memory';
import CrmApp from 'hotcrm/objectstack.config';  // from https://github.com/objectstack-ai/hotcrm
import TodoApp from '../examples/app-todo/objectstack.config';
import BiPlugin from '../examples/plugin-bi/objectstack.config';

export default defineStack({
  manifest: {
    id: 'com.objectstack.server',
    version: '1.0.0',
    type: 'app',
  },
  plugins: [
    new ObjectQLPlugin(),
    new DriverPlugin(new InMemoryDriver()),
    new AppPlugin(CrmApp),      // CRM app contributes objects under namespace 'crm'
    new AppPlugin(TodoApp),     // Todo app contributes objects under namespace 'todo'
    new AppPlugin(BiPlugin),    // BI plugin contributes objects under namespace 'bi'
  ],
});

Short Names Are Canonical

Each app declares a namespace in its manifest, but the short object name is what you use everywhere — in engine.find(), hooks, formulas, lookups, REST URLs, and physical tables. The namespace is internal metadata used only for package provenance and cross-package disambiguation.

AppNamespaceObject namePhysical table
Todotodotasktask
CRMcrmaccountaccount
BIbireportreport

If two packages contribute objects with the same short name, the registry logs a warning. Resolve the collision by renaming one object's short name (for example, crm_account) rather than using FQN strings in user code.

Run It

cd apps/objectos
pnpm dev        # Loads all 3 apps on one server

Project Structure Conventions

All examples follow the same pattern. When you run os init, you get this structure:

my-app/
├── objectstack.config.ts     # ← Entry point: manifest + metadata wiring
├── package.json
├── tsconfig.json
├── src/
│   ├── objects/              # Data models (required)
│   │   ├── index.ts          # Barrel export
│   │   └── *.object.ts       # One file per object
│   ├── actions/              # Buttons, batch operations (optional)
│   ├── flows/                # Automation logic (optional)
│   ├── apps/                 # Navigation definitions (optional)
│   ├── dashboards/           # Analytics dashboards (optional)
│   ├── reports/              # Report definitions (optional)
│   ├── agents/               # AI agents (optional)
│   └── rag/                  # RAG pipelines (optional)
└── test/                     # Tests

Naming conventions:

  • Directories: plural (objects/, actions/, flows/)
  • Files: {name}.object.ts, {name}.actions.ts, {name}.flow.ts
  • Object names: snake_case short names (task, sales_order, account)
  • Config keys: camelCase (maxLength, defaultValue, pluralLabel)

What's Next

On this page