Skip to main content
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.c3pkg.json
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:
Here’s how these files work together:
  1. Define your interface in Analytics.c3typ:
  1. Implement the interface in Analytics.js:
  1. Verify it with tests in Analytics.test.js:

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:

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:
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):
Page Example (ui/pages/AssetDashboard.jsx):
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):
Production Override (config/AppConfig/settings.prod.json):
Configuration Type Definition (src/config/AppConfig.c3typ):
Accessing Configuration in Code:
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):
Role Definition Example (metadata/Role/Operator.json):
Translation Example (metadata/Translation/en.csv):
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):
Integration Test (test/integration/AssetSensor.test.js):
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):
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):
Status Mappings (data/reference/status-mappings.json):
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:
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:
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.
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