# Conventions

> Use when writing new packages, adding CLI commands, creating build plugins, or following Meteor code patterns. Covers package.js structure, file naming, and common code patterns.

- Skill: `meteor/conventions` (Agent Skill)
- Install (CLI): `npx skillmds@latest add meteor/conventions`
- Raw SKILL.md: https://api.skillmd.com/api/skills/meteor/conventions/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: meteor (https://skillmd.com/u/meteor)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/meteor/conventions

---


# Code Conventions

Package structure, file naming, and code patterns for the Meteor codebase.

## Package Structure

Every Meteor package follows this structure:

```
packages/my-package/
├── package.js          # Package manifest (name, version, dependencies, exports)
├── my-package.js       # Main implementation (or split by concern)
├── my-package-server.js # Server-only code (optional)
├── my-package-client.js # Client-only code (optional)
├── my-package-tests.js  # Tests (loaded via api.addFiles in test mode)
└── README.md           # Documentation (optional)
```

## Package.js Anatomy

```javascript
Package.describe({
  name: 'my-package',
  version: '1.0.0',
  summary: 'Brief description',
  git: 'https://github.com/meteor/meteor.git',
  documentation: 'README.md'
});

Package.onUse(function(api) {
  api.versionsFrom(['3.0']);  // Minimum Meteor version

  api.use([
    'ecmascript',            // ES2015+ support
    'mongo',                 // MongoDB integration
    'tracker'                // Reactivity (client)
  ]);

  api.use('accounts-base', { weak: true }); // Optional dependency

  api.mainModule('my-package-server.js', 'server');
  api.mainModule('my-package-client.js', 'client');

  api.export('MyPackage');   // Global export
});

Package.onTest(function(api) {
  api.use(['tinytest', 'my-package']);
  api.addFiles('my-package-tests.js');
});

Npm.depends({
  'lodash': '4.17.21'        // npm dependencies
});
```

## File Naming Conventions

| Pattern | Purpose |
|---------|---------|
| `*-server.js` | Server-only code |
| `*-client.js` | Client-only code |
| `*-common.js` | Shared code |
| `*-tests.js` | Test files |
| `*.d.ts` | TypeScript declarations |

## Common Patterns

### Adding a New Core Package

1. Create directory in `/packages/my-package/`
2. Add `package.js` with proper dependencies
3. Implement functionality with proper exports
4. Add tests in `*-tests.js`
5. Update version numbers if needed

### Modifying Build System

Key files to understand:
- `/tools/isobuild/bundler.js` - High-level bundling
- `/tools/isobuild/compiler.js` - Package compilation
- `/tools/project-context.js` - Dependency resolution
- `/tools/cli/commands.js` - CLI command handlers

### Adding CLI Commands

Edit `/tools/cli/commands.js` or create new command file:

```javascript
main.registerCommand({
  name: 'my-command',
  options: {
    'option-name': { type: String, short: 'o' }
  },
  catalogRefresh: new catalog.Refresh.Never()
}, function(options) {
  // Implementation
});
```

### WebApp Middleware Pattern

```javascript
import { WebApp } from 'meteor/webapp';

// Add middleware before Meteor's default handlers
WebApp.rawConnectHandlers.use('/api', (req, res, next) => {
  // Runs before authentication
  next();
});

// Add middleware after authentication
WebApp.connectHandlers.use('/api', (req, res, next) => {
  // req.userId available if authenticated
  next();
});
```

### Build Plugin Pattern

```javascript
// In package.js
Package.registerBuildPlugin({
  name: 'compile-my-files',
  use: ['ecmascript', 'caching-compiler'],
  sources: ['plugin.js'],
  npmDependencies: { 'my-compiler': '1.0.0' }
});

// In plugin.js
Plugin.registerCompiler({
  extensions: ['myext'],
  archMatching: 'web'
}, () => new MyCompiler());

class MyCompiler extends CachingCompiler {
  getCacheKey(inputFile) {
    return inputFile.getSourceHash();
  }

  compileOneFile(inputFile) {
    const source = inputFile.getContentsAsString();
    const compiled = transform(source);
    inputFile.addJavaScript({
      data: compiled,
      path: inputFile.getPathInPackage() + '.js'
    });
  }
}
```

### Using tools-core in Packages

```javascript
// In package.js
api.use('tools-core');

// In implementation
import {
  logProgress,
  ensurePackageDependencies,
  getMeteorAppDir,
  spawnProcess
} from 'meteor/tools-core';

// Declare host-app dependencies. The shared engine handles auto-install versus
// manual instructions; do not gate it on meteor.autoInstallDeps yourself.
await ensurePackageDependencies({
  packageId: 'my-compiler',
  packageLabel: 'My Compiler',
  dependencies: [
    { name: '@rspack/core', version: '1.7.1', semverCondition: 'gte', dev: true }
  ]
});

// Spawn external process
const proc = spawnProcess('npx', ['rspack', 'build'], {
  cwd: getMeteorAppDir(),
  onStdout: (data) => logProgress(data)
});
```

## Version Patterns

Meteor uses `X.Y.Z-rcN.M` versioning where:
- `X.Y.Z` - Semantic version
- `rcN` - Release candidate number
- `M` - Package-specific revision

