This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is a modular monorepo for platformOS modules. Each pos-module-* directory is an independent, self-contained module with its own git history, versioning, and dependencies. Modules are designed as composable building blocks that follow shared architectural patterns while maintaining independence for distribution via the platformOS Partner Portal Marketplace.
# Install a module from marketplace (also downloads module and all dependencies)
pos-cli modules install <module-name>
# Deploy to environment
pos-cli deploy <env>
# Real-time sync during development
pos-cli sync <env>
# View instance logs
pos-cli logs <env>
# GraphQL explorer
pos-cli gui serve
# Create migration
pos-cli migrations generate <env> <name>Most modules with package.json support these commands:
# Versioning (updates CHANGELOG.md automatically)
npm run version # Prompts for version type
# Testing
npm run pw-tests # Run Playwright E2E tests
npm run test # Run Vitest unit tests (core module)
npm run test:ui # Vitest UI mode
# Building (for modules with webpack)
npm run build # Production webpack build
npm run build:dev # Development webpack build
# Deployment
npm run deploy # Build + deploy to production
npm run deploy:dev # Build + deploy to staging# Run tests via pos-cli (uses test runner module)
pos-cli test run <env> [test-name]
# Access test UI in browser
# Navigate to: https://your-instance.staging.oregon.platform-os.com/_tests
# View sent emails: /_tests/sent_mailsEach module is independently distributable but shares development infrastructure:
- Independent: Own git history, versioning (template-values.json), marketplace distribution
- Hierarchical: Complex modules contain
modules/subdirectory with nested dependency modules - Composable: Modules integrate via hooks, commands, events without modifying source code
pos-module-<name>/
├── modules/
│ └── <machine-name>/ # The actual module (distributed part)
│ ├── public/
│ │ ├── lib/
│ │ │ ├── commands/ # Business logic (Build/Check/Execute pattern)
│ │ │ ├── queries/ # Data access wrappers
│ │ │ ├── hooks/ # Integration hooks (hook_*.liquid)
│ │ │ ├── consumers/ # Event consumers
│ │ │ ├── validations/ # Input validators
│ │ │ └── helpers/ # Utility functions
│ │ ├── graphql/ # GraphQL queries/mutations
│ │ ├── views/
│ │ │ ├── pages/ # Endpoints
│ │ │ ├── partials/ # Reusable components
│ │ │ └── layouts/ # Page templates
│ │ ├── assets/ # CSS, JS, images
│ │ ├── schema/ # Database schema
│ │ ├── translations/ # i18n
│ │ └── api_calls/ # External API templates
│ └── template-values.json # Module metadata & dependencies
├── app/ # Example application (NOT distributed)
├── package.json # npm scripts for development
└── README.md # Module documentation
pos-module-core is the foundation - all complex modules depend on it. It provides:
- Hook System: Implements Open/Closed Principle for extensibility
- Command Pattern: 3-stage (Build/Check/Execute) for business logic
- Event System: Async communication via activities and consumers
- Module Registry: Dependency management and version tracking
- Utilities: Email sending, API calls, validators, global variables
core (no dependencies)
├── user (+ common-styling)
│ ├── oauth (optional integration)
│ └── profile (extends user)
├── chat (+ user, profile, common-styling)
├── reports (+ user, tests)
├── payments
│ └── payments-stripe
├── openai
├── data-export-api
└── instance-portal (+ common-styling)
common-styling (standalone, used by UI modules)
tests (standalone testing framework)
All modules follow this pattern for business logic:
{% liquid
# 1. BUILD: Normalize input, set defaults, type conversions
function object = 'commands/<resource>/<action>/build', object: object
# 2. CHECK: Validate inputs using core validators
function object = 'commands/<resource>/<action>/check', object: object
# 3. EXECUTE: Run GraphQL mutation (only if valid)
if object.valid
function object = 'commands/<resource>/<action>/execute', object: object
endif
return object
%}Commands are located in lib/commands/<resource>/<action>/ with build.liquid, check.liquid, execute.liquid files.
# Generate single command
pos-cli generate run modules/core/generators/command <resource>/<action>
# Generate full CRUD
pos-cli generate run modules/core/generators/crud <resource> field1:type field2:type --includeViewsHooks enable extensibility without modifying source code:
{% liquid
# Fire hook at integration point
function results = 'modules/core/commands/hook/fire', hook: 'my-hook-name', params: data
%}Create lib/hooks/hook_my-hook-name.liquid (in app/ or module/):
{% liquid
# Hook logic here
# Access params via params variable
# MUST return (even if null)
return result
%}The hook system searches for all files matching hook_<hook-name>.liquid pattern and executes them.
hook_permission: Contribute permissions for RBAC (user module)hook_headscripts: Inject CSS/JS into layout headhook_user_create: React to user creation events
Events enable async, loosely-coupled communication:
{% liquid
assign event_data = null | hash_merge: foo_id: "123", bar: "value"
function activity = 'modules/core/commands/events/publish', type: 'something_happened', object: event_data
%}Create lib/consumers/<event_name>/<consumer_name>.liquid:
---
metadata:
priority: default # low, default, high
max_attempts: 9 # Retry count
delay: 0 # Minutes to delay
---
{% liquid
# Event data available in 'event' variable
log event
%}Event types should be past tense (e.g., user_created, payment_succeeded).
# Invoke command
function result = 'modules/<module>/commands/<resource>/<action>', param1: value1
# Invoke query
function data = 'modules/<module>/queries/<resource>/search', limit: 20
# Common query patterns:
# - <resource>/search.liquid (multiple results)
# - <resource>/find.liquid (single result)# Check if module exists
function exists = 'modules/core/queries/module/exists', type: 'module'
# List installed modules
function modules = 'modules/core/queries/registry/search', type: 'module'- Permissions:
<resource>.<action>(e.g.,user.create,orders.manage.all) - Commands:
<resource>/<action>(e.g.,users/create,order/cancel) - Queries:
<resource>/searchor<resource>/find - Hooks:
hook_<hook-name>prefix - Events:
<action>_<resource>(past tense:user_created,payment_succeeded) - CSS Classes:
pos-prefix (e.g.,.pos-button,.pos-form) - JavaScript:
window.posnamespace
anonymous: Unauthenticated usersauthenticated: Any logged-in usersuperadmin: Full access to all permissions
# Get current user profile
function profile = 'modules/user/helpers/current_profile'
# Check permission (returns boolean)
function can = 'modules/user/helpers/can_do', requester: profile, do: 'resource.action'
# Enforce permission (renders 403 if unauthorized)
# platformos-check-disable ConvertIncludeToRender, UnreachableCode
include 'modules/user/helpers/can_do_or_unauthorized', requester: profile, do: 'resource.action'
# platformos-check-enable ConvertIncludeToRender, UnreachableCodeOverride modules/user/public/lib/queries/role_permissions/permissions.liquid:
{% parse_json permissions %}
{
"member": ["articles.create", "comments.create"],
"moderator": ["comments.manage.all"],
"admin": ["articles.manage.all", "users.manage"]
}
{% endparse_json %}
{% return permissions %}To customize a module without forking:
- Copy file from
modules/<module>/public/toapp/modules/<module>/public/ - Modify the copy - it will take precedence
- Add module to
modules_that_allow_delete_on_deployinapp/config.yml
# app/config.yml
modules_that_allow_delete_on_deploy:
- core
- userPlace tests in app/lib/test/ with _test.liquid suffix:
{% liquid
# Setup test data
assign data = '{ "email": "test@example.com" }' | parse_json
# Execute command
function result = 'commands/users/create', object: data
# Assertions
function contract = 'modules/tests/assertions/valid_object', contract: contract, object: result, field_name: 'user_create'
function contract = 'modules/tests/assertions/equal', contract: contract, given: result.email, expected: 'test@example.com', field_name: 'email'
%}assertions/equal- Check value equalityassertions/presence/assertions/not_presence- Check field existsassertions/blank- Check field is blankassertions/valid_object/assertions/not_valid_object- Check object.validassertions/true/assertions/not_true- Boolean checksassertions/object_contains_object- Check object containment
# Via pos-cli
pos-cli test run staging [test-name]
# Via browser
# https://instance.staging.oregon.platform-os.com/_tests- Liquid: Server-side templating for all business logic
- GraphQL: Data queries and mutations
- platformOS: Serverless backend platform
- JavaScript (ESM): Frontend interactivity
- CSS: Styling with
pos-prefixed classes - Webpack: Asset bundling (where needed)
- Vitest: Unit testing (core module)
- Playwright: E2E testing
- npm: Package management and scripts
Modules use partial deployment - files are never deleted unless explicitly configured. This allows developers to override specific files without breaking the module.
All common-styling uses pos- prefix to avoid conflicts. Use CSS layers for low-specificity overrides.
User module uses session-based authentication. Current user via context.current_user, current profile via function profile = 'modules/user/helpers/current_profile'.
Commands can be executed as background jobs. Events use background jobs via consumers with priority, retry, and delay configuration.
Authentication, RBAC, password reset, 2FA, impersonation. Depends on core and common-styling. Auto-creates profile for each user.
Foundation module. Zero dependencies. Provides hooks, commands, events, validators, module registry.
Testing framework. Only runs in staging/development. Access via /_tests endpoint.
Generic payment interface (payments) + Stripe implementation (payments-stripe). Event-based (payment_transaction_pending/succeeded/failed/expired).
Real-time chat via WebSockets. Depends on user, profile, common-styling.
Design system with CSS variables and JS utilities. Theme support (light/dark). Zero dependencies.