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.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.
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
- 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
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:- Define your interface in
Analytics.c3typ:
- Implement the interface in
Analytics.js:
- 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:Source Files (src/)
Source Files (src/)
Purpose: Core application logicRules:For more details on Types, see the C3 Type System guide.
- Type files define interfaces (
.c3typ) - Implementation files fulfill interfaces (
.js,.py) - Names must match (example:
Asset.c3typ→Asset.js)
- Validates implementations against Types
- Generates TypeScript definitions
- Creates API documentation
src/ directory. For an Asset type:User Interface (ui/)
User Interface (ui/)
Purpose: Application frontend components and pagesRules:Page Example (The platform will automatically bundle these files, create routes, and make components available to your pages.
- 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
- 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
ui/components/AssetCard.jsx):ui/pages/AssetDashboard.jsx):Configuration (config/)
Configuration (config/)
Purpose: Environment-specific settings that control application behavior without code changesRules:Production Override (Configuration Type Definition (Accessing Configuration in Code:When to use configuration:
- 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.jsonshould matchAppConfigType - Type validation ensures configuration correctness
- Prevents runtime errors from invalid configuration
- Example:
- 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)
- Base file:
- 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
- Use
config/AppConfig/settings.json):config/AppConfig/settings.prod.json):src/config/AppConfig.c3typ):- 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
Metadata (metadata/)
Metadata (metadata/)
Purpose: Platform integration points that define how your package interacts with the C3 AI Agentic PlatformRules:Role Definition Example (Translation Example (When you deploy your package, the platform automatically processes these metadata files and configures the appropriate platform services.
- One subdirectory per integration type: Each integration type has its own directory
- Common integration types include:
Schedule/: Scheduled jobs and cron tasksRole/: User roles and permissionsTranslation/: Internationalization stringsSimpleMetric/: Metrics for monitoringCodeCoverageInstrumentationSpec/: Test coverage configuration
- Common integration types include:
- 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
- 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
metadata/Schedule/HealthCheck.json):metadata/Role/Operator.json):metadata/Translation/en.csv):Tests (test/)
Tests (test/)
Purpose: Automated testing to ensure code quality and prevent regressionsRules:Integration Test (When to write tests:
- Mirror source structure: Tests should follow the same organization as your source code
- Example:
src/asset/Asset.js→test/src/asset/Asset.test.js - Makes it easy to find tests for specific components
- Ensures complete test coverage across your codebase
- Example:
- Names must match source files: Test files should have the same name as the source file with a
.testsuffix- Example:
Asset.js→Asset.test.js - Enables automatic test discovery
- Makes it clear what each test file is testing
- Example:
- 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
- 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/src/asset/Asset.test.js):test/integration/AssetSensor.test.js):- 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)
Seed Data (seed/)
Seed Data (seed/)
Purpose: Initial data loaded during first deployment to populate your application with starter contentRules:When to use seed data:
- 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
- Example:
- 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
- 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/Asset/InitialAssets.json):- 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
Reference Data (data/)
Reference Data (data/)
Purpose: Static reference data, lookup tables, and other non-persistent data used by your applicationRules:Status Mappings (When to use reference data:
- 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
- Example:
- 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
- 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
data/reference/equipment-codes.csv):data/reference/status-mappings.json):- 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: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: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.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