Headlessly
Projects

Project

Container for organized work -- features, releases, onboarding, and delivery tracking.

Schema

import { Noun } from 'digital-objects'

export const Project = Noun('Project', {
  name: 'string!',
  slug: 'string##',
  description: 'string',
  organization: '-> Organization',
  status: 'Active | Archived | Completed',
  visibility: 'Public | Private',
  owner: '-> Contact',
  startDate: 'date',
  targetDate: 'date',
  issues: '<- Issue.project[]',
  tags: 'string',
  archive: 'Archived',
  complete: 'Completed',
  activate: 'Activated',
})

Fields

FieldTypeRequiredDescription
namestringYesDisplay name of the project
slugstring (unique, indexed)NoURL-safe identifier
descriptionstringNoBrief description of the project scope
organization-> OrganizationNoOwning organization
statusenumNoActive, Archived, or Completed
visibilityenumNoPublic or Private
owner-> ContactNoPrimary responsible person
startDatedateNoWhen work begins
targetDatedateNoTarget completion date
issues<- Issue[]NoAll issues in this project
tagsstringNoComma-separated tags for categorization

Relationships

FieldDirectionTargetDescription
organization->OrganizationThe organization this project belongs to
owner->ContactThe person responsible for the project
issues<-Issue.project[]All issues filed under this project

Verbs

VerbEventDescription
createCreatedCreate a new project
updateUpdatedUpdate project fields
deleteDeletedSoft-delete the project
archiveArchivedArchive the project for reference
completeCompletedMark the project as done
activateActivatedRe-activate an archived or completed project

Verb Lifecycle

import { Project } from '@headlessly/projects'

// BEFORE hook -- validate before completion
Project.completing(project => {
  const openIssues = project.issues.filter(i => i.status !== 'Done' && i.status !== 'Closed')
  if (openIssues.length > 0) {
    throw new Error(`${openIssues.length} issues still open`)
  }
})

// Execute
await Project.complete('project_e5JhLzXc')

// AFTER hook -- react to completion
Project.completed((project, $) => {
  $.Activity.create({
    subject: `Project completed: ${project.name}`,
    type: 'Task',
    status: 'Completed',
  })
})

Status State Machine

              create()
(none) ──────────────→ Active

             archive()    │    complete()
                ┌─────────┤─────────┐
                ▼                   ▼
            Archived          Completed
                │                   │
                └─── activate() ────┘


                       Active

Valid transitions:

FromVerbTo
--createActive
ActivearchiveArchived
ActivecompleteCompleted
ArchivedactivateActive
CompletedactivateActive

Cross-Domain Patterns

Project is the top-level work container that connects to the broader business graph:

  • CRM: Organizations own projects. Contacts serve as project owners. When an Organization is created, an onboarding project can be auto-created.
  • Support: Escalated tickets create issues within projects for engineering follow-up.
  • Billing: Project delivery tracking ties to subscription milestones.
  • Analytics: Project completion events feed into Metrics and Goals.
import { Project } from '@headlessly/projects'

Project.completed((project, $) => {
  $.Metric.record({
    name: 'project.completed',
    value: 1,
    dimensions: { organization: project.organization },
  })
})

Query Examples

SDK

import { Project } from '@headlessly/projects'

// Find all active projects
const active = await Project.find({ status: 'Active' })

// Get a specific project with its issues
const project = await Project.get('project_e5JhLzXc', {
  include: ['issues', 'owner'],
})

// Create a new project
await Project.create({
  name: 'API v3 Migration',
  slug: 'api-v3-migration',
  organization: 'org_Nw8rTxJv',
  owner: 'contact_fX9bL5nRd',
  status: 'Active',
  visibility: 'Private',
  startDate: '2026-03-01',
  targetDate: '2026-06-01',
})

MCP

headless.ly/mcp#search
{
  "type": "Project",
  "filter": { "status": "Active", "visibility": "Public" },
  "sort": { "targetDate": "asc" },
  "limit": 20
}
headless.ly/mcp#fetch
{ "type": "Project", "id": "project_e5JhLzXc", "include": ["issues"] }
headless.ly/mcp#do
const active = await $.Project.find({ status: 'Active' })
await $.Project.complete('project_e5JhLzXc')

REST

# List active projects
curl https://headless.ly/~acme/projects?status=Active

# Get a specific project
curl https://headless.ly/~acme/projects/project_e5JhLzXc

# Create a project
curl -X POST https://headless.ly/~acme/projects \
  -H 'Content-Type: application/json' \
  -d '{"name": "API v3 Migration", "status": "Active"}'

# Complete a project
curl -X POST https://headless.ly/~acme/projects/project_e5JhLzXc/complete

On this page