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:

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