Skip to main content

Documentation Index

Fetch the complete documentation index at: https://devdocs-shaunak-branch.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

C3 AI packages use directory structure as a mechanism to activate platform features. Each directory serves a specific purpose, triggering different automations when files are placed in the correct locations. This guide explains the standard package structure, what each directory does, and how the platform processes files based on their location.

Getting started

Package manifest

The manifest file is the only required component of a C3 AI package. At its most minimal, a valid package consists of just a single JSON file:
myPackage/
  myPackage.c3pkg.json    # Required manifest file
myPackage.c3pkg.json
{
  "name": "myPackage",        // Must match directory name
  "description": "My first package",
  "author": "C3 AI",
  "version": "1.0.0",        // Semantic versioning
  "dependencies": {}         // Other packages you depend on
}
The manifest contains essential metadata about your package:
  • name: Must match the directory name exactly
  • description: Brief explanation of the package’s purpose
  • author: Individual or organization that created the package
  • version: Follows semantic versioning (MAJOR.MINOR.PATCH)
  • dependencies: Other packages required by this package
For more details on dependencies and how to use existing packages, see the Importing Packages guide.

Adding source code and tests

While a manifest is enough for a valid package, you’ll want to add code to make it useful. The platform automatically validates and connects your Types, implementations, and tests based on where you place them:
simpleAnalytics/
  src/                          # Source code directory
    Analytics.c3typ            # Type definition
    Analytics.js               # Implementation
  test/                        # Test directory
    Analytics.test.js          # Test file
  simpleAnalytics.c3pkg.json   # Package manifest
Here’s how these files work together:
  1. Define your interface in Analytics.c3typ:
// Analytics.c3typ - Define the analytics capabilities
type Analytics {
  // Fields (properties of the Analytics type)
  name: string;
  description: string;
  
  // Method signature that will be implemented in Analytics.js
  analyzeData(data: array<number>): {
    mean: number;
    median: number;
    min: number;
    max: number;
  };
}
  1. Implement the interface in Analytics.js:
// Analytics.js - Implement the analytics functionality
Analytics.prototype.analyzeData = function(data) {
  // Sort the data once for multiple operations
  const sortedData = [...data].sort((a, b) => a - b);
  
  return {
    mean: data.reduce((sum, value) => sum + value, 0) / data.length,
    median: sortedData[Math.floor(sortedData.length / 2)],
    min: sortedData[0],
    max: sortedData[sortedData.length - 1]
  };
};
  1. Verify it with tests in Analytics.test.js:
// Analytics.test.js - Test the analytics functionality
describe('Analytics', () => {
  it('calculates statistics correctly for ordered data', () => {
    // Create a new instance with required properties
    const analytics = new Analytics();
    analytics.name = 'Test Analytics';
    analytics.description = 'Analytics for testing';
    
    // Test with a simple ordered dataset
    const result = analytics.analyzeData([1, 2, 3, 4, 5]);
    
    // Verify all calculated statistics
    expect(result.mean).toBe(3);
    expect(result.median).toBe(3);
    expect(result.min).toBe(1);
    expect(result.max).toBe(5);
  });
  
  it('calculates statistics correctly for unordered data', () => {
    const analytics = new Analytics();
    
    // Test with an unordered dataset
    const result = analytics.analyzeData([5, 1, 3, 2, 4]);
    
    // Verify all calculated statistics
    expect(result.mean).toBe(3);
    expect(result.median).toBe(3);
    expect(result.min).toBe(1);
    expect(result.max).toBe(5);
  });
});

Building larger applications

Application structure

As your package grows, you’ll want to leverage more platform features. Here’s how to organize a full application with business logic, UI components, and platform integrations:
reliabilityMonitor/
  src/                         # Application logic
    asset/                    # Domain-grouped files
      Asset.c3typ
      Asset.js
    sensor/
      Sensor.c3typ
      Sensor.js
  ui/                         # Frontend code
    components/
      AssetCard.jsx
    pages/
      Dashboard.jsx
  config/                     # Environment config
    AppConfig/
      settings.json
  metadata/                   # Platform integration
    Schedule/
      healthCheck.json
  seed/                      # Initial data
    Asset/
      defaultAssets.json
  data/                      # Reference data
    reference/
      equipment-codes.csv
  resource/                  # Static assets
    templates/
      alert-email.html
  test/                      # Tests
    src/
      asset/
        Asset.test.js
  reliabilityMonitor.c3pkg.json

Optional components

Each directory in your package enables specific platform features:
Purpose: Core application logicRules:
  • Type files define interfaces (.c3typ)
  • Implementation files fulfill interfaces (.js, .py)
  • Names must match (example: Asset.c3typAsset.js)
Automations:
  • Validates implementations against Types
  • Generates TypeScript definitions
  • Creates API documentation
Here’s how these rules work in the src/ directory. For an Asset type:
// src/Asset.c3typ - Define the interface
type Asset mixes Persistable {
  id: string;
  name: string;
  description: string;
  status: AssetStatus;
  sensors: array<Sensor>;  // Relationship to other Types
  
  // Methods that will be implemented in Asset.js
  getHealth(): number;
  updateStatus(status: AssetStatus): void;
}
For more details on Types, see the C3 Type System guide.
Purpose: Application frontend components and pagesRules:
  • Components go in components/: Reusable UI elements that can be composed into pages
    • Examples: Cards, panels, form elements, charts, tables
    • Should be focused on presentation and accept data via props
    • Should be reusable across multiple pages
  • Full pages go in pages/: Complete application views that combine components
    • Examples: Dashboards, detail views, settings screens
    • Typically fetch data and pass it to components
    • Correspond to routes in your application
  • Must use platform UI libraries for consistent styling and behavior
Automations:
  • Bundles and optimizes code for production deployment
  • Automatically generates application routes from page files
  • Creates TypeScript bindings for type safety
  • Handles CSS/SCSS processing and optimization
Let’s look at examples of both a component and a page:Component Example (ui/components/AssetCard.jsx):
// A reusable card component for displaying assets
// This can be used in multiple pages throughout the application
function AssetCard({ asset, onStatusChange }) {
  return (
    <Card className="asset-card">
      <Heading level={3}>{asset.name}</Heading>
      <StatusIndicator 
        status={asset.status} 
        onChange={onStatusChange} 
      />
      <MetricDisplay 
        label="Health Score" 
        value={asset.healthScore} 
        unit="%" 
      />
    </Card>
  );
}

export default AssetCard;
Page Example (ui/pages/AssetDashboard.jsx):
// A complete page that uses multiple components
// This will automatically be available at the /assets route
function AssetDashboard() {
  // Fetch data for the page
  const [assets, setAssets] = useState([]);
  
  useEffect(() => {
    // Load assets from the backend
    Asset.fetchAll().then(setAssets);
  }, []);
  
  // Handle status changes
  const handleStatusChange = (assetId, newStatus) => {
    Asset.updateStatus(assetId, newStatus);
  };
  
  return (
    <PageLayout title="Asset Dashboard">
      <FilterPanel onFilterChange={/* handler */} />
      
      <Grid columns={3}>
        {assets.map(asset => (
          <AssetCard 
            key={asset.id} 
            asset={asset} 
            onStatusChange={(status) => handleStatusChange(asset.id, status)}
          />
        ))}
      </Grid>
    </PageLayout>
  );
}

export default AssetDashboard;
The platform will automatically bundle these files, create routes, and make components available to your pages.
Purpose: Environment-specific settings that control application behavior without code changesRules:
  • Must be valid JSON or YAML: Configuration must be in a supported format
    • JSON is the most common format
    • YAML is supported for more complex configurations
    • Comments are allowed in configuration files
  • Values must match Type definitions: Configuration should have a corresponding Type
    • Example: config/AppConfig/settings.json should match AppConfig Type
    • Type validation ensures configuration correctness
    • Prevents runtime errors from invalid configuration
  • Use environment suffixes for environment-specific values:
    • Base file: config/AppConfig/settings.json (default values)
    • Development: config/AppConfig/settings.dev.json (overrides for development)
    • Production: config/AppConfig/settings.prod.json (overrides for production)
    • Test: config/AppConfig/settings.test.json (overrides for testing)
Automations:
  • Loads correct values per environment: Platform automatically selects the right configuration
    • Base values are loaded first
    • Environment-specific values override base values
    • Environment is determined by deployment context
  • Validates against schemas: Configuration is validated against Type definitions
    • Type checking prevents invalid values
    • Required fields are enforced
    • Validation happens at deployment time
  • Makes settings available to code: Configuration is accessible through the Configuration API
    • Use Configuration.get('path.to.value') to access values
    • Changes to configuration don’t require code changes
    • Configuration can be updated without redeployment
Configuration Examples:Base Configuration (config/AppConfig/settings.json):
{
  "monitoring": {
    "checkInterval": "5m",
    "alertThreshold": 90,
    "notificationChannels": ["email"],
    "retentionPeriod": "30d"
  },
  "ui": {
    "refreshInterval": "1m",
    "theme": "light",
    "defaultView": "dashboard"
  },
  "integration": {
    "externalApiUrl": "https://api.example.com",
    "timeout": "30s",
    "retryCount": 3
  }
}
Production Override (config/AppConfig/settings.prod.json):
{
  "monitoring": {
    "checkInterval": "1m",
    "notificationChannels": ["email", "sms", "slack"]
  },
  "integration": {
    "externalApiUrl": "https://api.production.example.com",
    "timeout": "10s"
  }
}
Configuration Type Definition (src/config/AppConfig.c3typ):
type AppConfig mixes Configuration {
  monitoring: {
    checkInterval: string;
    alertThreshold: number;
    notificationChannels: array<string>;
    retentionPeriod: string;
  };
  ui: {
    refreshInterval: string;
    theme: string;
    defaultView: string;
  };
  integration: {
    externalApiUrl: string;
    timeout: string;
    retryCount: number;
  };
}
Accessing Configuration in Code:
// Get a specific configuration value
const threshold = Configuration.get('monitoring.alertThreshold');

// Get a section of configuration
const uiConfig = Configuration.get('ui');

// Get configuration with environment-specific overrides
const apiUrl = Configuration.get('integration.externalApiUrl');
// In development: "https://api.example.com"
// In production: "https://api.production.example.com"
When to use configuration:
  • For settings that change between environments
  • For values that non-developers need to adjust
  • For feature flags and toggles
  • For connection strings and external service URLs
Purpose: Platform integration points that define how your package interacts with the C3 AI Agentic PlatformRules:
  • One subdirectory per integration type: Each integration type has its own directory
    • Common integration types include:
      • Schedule/: Scheduled jobs and cron tasks
      • Role/: User roles and permissions
      • Translation/: Internationalization strings
      • SimpleMetric/: Metrics for monitoring
      • CodeCoverageInstrumentationSpec/: Test coverage configuration
  • Must follow integration schemas: Each integration type has a specific schema
    • Schemas are validated during deployment
    • Invalid schemas will cause deployment failures
  • Changes require redeployment: Metadata changes only take effect after redeployment
    • Unlike configuration, metadata can’t be changed at runtime
    • Plan metadata changes carefully to minimize disruption
Automations:
  • Registers scheduled jobs in the platform’s job scheduler
  • Sets up permissions and access control for users
  • Configures metrics for monitoring and dashboards
  • Registers translations for internationalization
Here are examples of common metadata types:Scheduled Job Example (metadata/Schedule/HealthCheck.json):
{
  "name": "HealthCheck",
  "description": "Daily health check for all assets",
  "cron": "0 0 * * *",  // Daily at midnight
  "method": "reliability.checkAssetHealth",
  "enabled": true,
  "parameters": {
    "thoroughScan": true,
    "notifyOnFailure": true
  },
  "timeout": "1h"
}
Role Definition Example (metadata/Role/Operator.json):
{
  "name": "Operator",
  "description": "Role for plant operators",
  "permissions": [
    "asset:read",
    "asset:update",
    "alert:read",
    "alert:acknowledge"
  ],
  "extends": ["BaseUser"]
}
Translation Example (metadata/Translation/en.csv):
id,locale,key,value
alert.critical,en,alert.critical,Critical Alert
alert.warning,en,alert.warning,Warning
alert.info,en,alert.info,Information
When you deploy your package, the platform automatically processes these metadata files and configures the appropriate platform services.
Purpose: Automated testing to ensure code quality and prevent regressionsRules:
  • Mirror source structure: Tests should follow the same organization as your source code
    • Example: src/asset/Asset.jstest/src/asset/Asset.test.js
    • Makes it easy to find tests for specific components
    • Ensures complete test coverage across your codebase
  • Names must match source files: Test files should have the same name as the source file with a .test suffix
    • Example: Asset.jsAsset.test.js
    • Enables automatic test discovery
    • Makes it clear what each test file is testing
  • Required for production code: All production code should have tests
    • Unit tests for individual functions and methods
    • Integration tests for interactions between components
    • End-to-end tests for complete workflows
Automations:
  • Discovers and runs tests: Platform automatically finds and executes tests
    • No need to manually register tests
    • Tests run automatically during builds
    • Supports multiple test frameworks (Jest, Mocha, Pytest)
  • Reports coverage: Platform generates test coverage reports
    • Shows which code is tested and which isn’t
    • Identifies untested code paths
    • Can enforce minimum coverage thresholds
  • Integrates with CI/CD: Tests run automatically in CI/CD pipelines
    • Prevents deployment of failing code
    • Provides feedback on test failures
    • Maintains code quality over time
Test Examples:Unit Test (test/src/asset/Asset.test.js):
// Unit test for Asset.js
describe('Asset', () => {
  // Test the getHealth method
  describe('getHealth', () => {
    it('returns the health score when available', () => {
      // Create a test asset with a known health score
      const asset = new Asset({
        id: 'test-asset',
        name: 'Test Asset',
        healthScore: 85
      });
      
      // Verify the method returns the expected value
      expect(asset.getHealth()).toBe(85);
    });
  });
  
});
Integration Test (test/integration/AssetSensor.test.js):
// Integration test for Asset and Sensor interaction
describe('Asset and Sensor Integration', () => {
  it('updates asset health when sensor readings change', async () => {
    // Create test data
    const asset = await Asset.create({
      id: 'test-asset',
      name: 'Test Asset'
    });
    
    const sensor = await Sensor.create({
      id: 'test-sensor',
      name: 'Test Sensor',
      asset: asset.id,
      reading: 75
    });
    
    // Trigger the event that should update the asset
    await sensor.updateReading(90);
    
    // Reload the asset to get the latest data
    const updatedAsset = await Asset.fetch(asset.id);
    
    // Verify the asset health was updated based on the sensor
    expect(updatedAsset.healthScore).toBe(90);
  });
});
When to write tests:
  • Before writing implementation code (Test-Driven Development)
  • When adding new features
  • When fixing bugs (to prevent regressions)
  • When refactoring code (to ensure behavior doesn’t change)
Purpose: Initial data loaded during first deployment to populate your application with starter contentRules:
  • Must match Type definitions: Seed data must conform to your Type schemas
    • Field names and types must match your Type definitions
    • Required fields must be present
    • Relationships between entities must be valid
  • One subdirectory per entity Type: Organize seed data by the Type it populates
    • Example: seed/Asset/ for Asset instances
    • Example: seed/User/ for User instances
  • JSON or CSV format: Data must be in a supported format
    • JSON is preferred for complex objects with nested structures
    • CSV is useful for simple tabular data
Automations:
  • Automatic loading during first deployment: Data is loaded when the package is first deployed
    • No manual data entry required to get started
    • Creates a consistent starting state
  • Data validation against Types: Platform validates data against Type definitions
    • Invalid data will cause deployment failures
    • Ensures data integrity from the start
  • Preservation across updates: Seed data is only loaded once
    • Subsequent deployments won’t overwrite existing data
    • Safe to modify seeded data after deployment
Seed Data Example (seed/Asset/InitialAssets.json):
[
  {
    "id": "asset-001",
    "name": "Main Turbine",
    "description": "Primary power generation turbine",
    "status": "ACTIVE",
    "healthScore": 100,
    "installDate": "2023-01-15",
    "location": {
      "facility": "Plant A",
      "area": "Generation Hall",
      "coordinates": {
        "latitude": 37.7749,
        "longitude": -122.4194
      }
    },
    "sensors": ["sensor-001", "sensor-002", "sensor-003"]
  },
  {
    "id": "asset-002",
    "name": "Backup Generator",
    "description": "Emergency backup power generator",
    "status": "STANDBY",
    "healthScore": 95,
    "installDate": "2023-02-20",
    "location": {
      "facility": "Plant A",
      "area": "Auxiliary Building",
      "coordinates": {
        "latitude": 37.7750,
        "longitude": -122.4195
      }
    },
    "sensors": ["sensor-004", "sensor-005"]
  }
]
When to use seed data:
  • To provide default configuration values
  • To populate lookup tables and reference data
  • To create demo or sample content for testing
  • To establish initial user roles and permissions
Purpose: Static reference data, lookup tables, and other non-persistent data used by your applicationRules:
  • Standard formats (CSV, JSON): Use widely supported formats
    • CSV for simple tabular data
    • JSON for structured data
    • YAML for configuration-like data
  • Organized by data type: Group related data together
    • Example: data/reference/ for lookup tables
    • Example: data/validation/ for validation rules
    • Example: data/ml/ for machine learning parameters
  • Must include schema validation: Define the structure of your data
    • Include schema files when possible
    • Document data format in comments
    • Ensure consistent structure across files
Automations:
  • Automatic loading and validation: Data is loaded at runtime
    • Available to your code through the Data API
    • Validated against schemas when loaded
  • Runtime access to data: Access data programmatically
    • Query and filter data in memory
    • Join with other data sources
    • Cache for performance
  • Version tracking: Data is versioned with your package
    • Changes are tracked in source control
    • Updates deploy with your package
Reference Data Examples:Equipment Codes (data/reference/equipment-codes.csv):
code,description,category,manufacturer,lifecycle_years
TRB-001,Main Turbine,TURBINE,General Electric,25
GEN-001,Primary Generator,GENERATOR,Siemens,20
PMP-001,Cooling Pump,PUMP,Flowserve,15
VLV-001,Control Valve,VALVE,Emerson,10
MTR-001,Electric Motor,MOTOR,ABB,15
Status Mappings (data/reference/status-mappings.json):
{
  "statusMappings": {
    "ACTIVE": {
      "displayName": "Active",
      "color": "#4CAF50",
      "priority": 1,
      "allowedTransitions": ["MAINTENANCE", "STANDBY", "FAILED"]
    },
    "STANDBY": {
      "displayName": "Standby",
      "color": "#2196F3",
      "priority": 2,
      "allowedTransitions": ["ACTIVE", "MAINTENANCE"]
    },
    "MAINTENANCE": {
      "displayName": "Under Maintenance",
      "color": "#FFC107",
      "priority": 3,
      "allowedTransitions": ["ACTIVE", "STANDBY", "FAILED"]
    },
    "FAILED": {
      "displayName": "Failed",
      "color": "#F44336",
      "priority": 4,
      "allowedTransitions": ["MAINTENANCE"]
    }
  }
}
When to use reference data:
  • For lookup tables that rarely change
  • For mapping and translation tables
  • For configuration parameters that aren’t environment-specific
  • For validation rules and constraints

Package organization strategies

Single vs multiple packages

As your application grows, you’ll need to make strategic decisions about package organization. Here are concrete guidelines for when to use each approach:

Single Package Approach

Start with a single package for small teams (1-5 developers) working on applications with fewer than 50 Types. This works especially well for early-stage projects where you need flexibility to evolve your design. You’ll benefit from simpler dependency management, quicker refactoring across component boundaries, and faster development cycles since everything lives in one place. Example Structure:
reliabilityMonitor/
  src/                      # Core application logic
    asset/                 # Asset domain
      Asset.c3typ         # Asset Type definition
      Asset.js            # Asset implementation
      AssetService.js     # Asset-specific business logic
    sensor/               # Sensor domain
      Sensor.c3typ
      Sensor.js
      SensorService.js
    alert/                # Alert domain
      Alert.c3typ
      Alert.js
      AlertService.js
    common/               # Shared utilities
      DateUtils.js
      FormatUtils.js
  ui/                      # Frontend code
    components/           # Reusable UI components
      AssetCard.jsx
      SensorDisplay.jsx
      AlertBadge.jsx
    pages/                # Full application pages
      Dashboard.jsx
      AssetDetails.jsx
      Settings.jsx
  config/                  # Configuration
    AppConfig/
      settings.json
  metadata/                # Platform integration
    Schedule/
      dailyHealthCheck.json
    Role/
      Operator.json
      Administrator.json
  reliabilityMonitor.c3pkg.json
Real-world example: The reliabilityDataModel package in our reliability examples uses a single package approach because it focuses on a set of data models that are tightly coupled.

Multiple Package Approach

As your application grows beyond 50 Types or your team expands past 5 developers, you’ll want to consider splitting into multiple packages. This approach helps for mature applications where you’ve clearly defined component boundaries. Breaking things up creates clear ownership lines, lets teams version components independently, enables reuse across projects, and reduces merge conflicts when multiple developers work in parallel. Example Structure:
# Core domain models and business logic
reliability-core/
  src/
    asset/
    sensor/
    alert/
  config/
  metadata/
  reliability-core.c3pkg.json

# UI components and pages
reliability-ui/
  src/
    components/
    pages/
    themes/
  reliability-ui.c3pkg.json  # Depends on reliability-core

# Analytics and reporting
reliability-analytics/
  src/
    reports/
    metrics/
    predictions/
  reliability-analytics.c3pkg.json  # Depends on reliability-core

# Main application that ties everything together
reliability-app/
  src/
    app/
  config/
  reliability-app.c3pkg.json  # Depends on all other packages
Real-world example: The reliability examples in our platform use multiple packages (reliabilityDataModel, reliabilityMl, reliabilityUiComponentLibrary) because each focuses on a different aspect of the application that can evolve independently.

When to split packages

You’ll recognize when to split your package as natural boundaries emerge in your codebase: Functional boundaries appear when you see distinct groups forming in your code. For example, we separated data models from ML components in the reliability examples because they serve different purposes and evolve independently. This separation lets specialists focus on their domain without affecting other areas.
Before:                After:
myApp/                 myApp-dataModel/    
  src/                   src/
    dataModel/             Asset.c3typ
    analytics/          myApp-analytics/
    ui/                   src/
                        myApp-ui/
Team boundaries matter when multiple teams contribute to your codebase. Giving each team their own package reduces coordination overhead and prevents merge conflicts, letting teams work at their own pace. Reuse potential signals the need to extract shared components. We created reliabilityUiComponentLibrary to share UI components across applications, reducing duplication and ensuring consistent user experiences. Versioning needs differ across components. Your data models might stabilize early while your UI evolves rapidly. Separating these components lets you version them independently, using minor bumps for stable components and major updates for rapidly changing ones.

Next steps

Set Up Your Environment

Create your first package and start developing

Import Packages

Use existing packages in your application

Manage Artifacts

Version and distribute your packages