| name | package-scaffolding |
| description | Create new packages in the Fluid Framework monorepo following all conventions. Use when creating a new package, adding a new DDS, or scaffolding a new library. Triggers on mentions of new package, scaffold, create package, add package, or package template. |
Package Scaffolding
Guide for creating new packages in the Fluid Framework monorepo with all required conventions.
Where to Place the Package
| Directory | Scope | Purpose |
|---|
packages/common/ | @fluidframework/* | Shared interfaces, utilities |
packages/dds/ | @fluidframework/* | Distributed Data Structures |
packages/drivers/ | @fluidframework/* | Service drivers |
packages/framework/ | @fluidframework/* | Framework components |
packages/loader/ | @fluidframework/* | Container loading |
packages/runtime/ | @fluidframework/* | Runtime components |
packages/service-clients/ | @fluidframework/* | Service client implementations |
packages/utils/ | @fluidframework/* | Shared utilities |
packages/test/ | @fluid-internal/* | Test utilities (not published) |
packages/tools/ | @fluidframework/* | Developer tools |
experimental/ | @fluid-experimental/* | Experimental packages |
azure/packages/ | @fluidframework/* | Azure-specific packages |
examples/ | @fluid-example/* | Examples (not published) |
All these paths are included in pnpm-workspace.yaml. No workspace registration is needed.
Directory Structure
my-package/
├── api-extractor/
│ ├── api-extractor.current.json
│ ├── api-extractor.legacy.json
│ ├── api-extractor-lint-public.esm.json
│ ├── api-extractor-lint-public.cjs.json
│ ├── api-extractor-lint-legacy.esm.json
│ ├── api-extractor-lint-legacy.cjs.json
│ └── api-extractor-lint-bundle.json
├── api-report/ # Generated, committed
│ └── (created after first build)
├── src/
│ ├── cjs/
│ │ └── package.json
│ ├── test/
│ │ ├── tsconfig.json
│ │ ├── tsconfig.cjs.json
│ │ └── *.spec.ts
│ ├── index.ts
│ └── internal.ts
├── .mocharc.cjs
├── eslint.config.mts
├── package.json
├── test-config.json
├── tsconfig.json
└── tsconfig.cjs.json
Required Files
package.json
Adapt the version, name, description, and directory path. Match devDependency versions to an existing peer package.
{
"name": "@fluidframework/my-package",
"version": "2.90.0",
"description": "Description of the package",
"homepage": "https://fluidframework.com",
"repository": {
"type": "git",
"url": "https://github.com/microsoft/FluidFramework.git",
"directory": "packages/category/my-package"
},
"license": "MIT",
"author": "Microsoft and contributors",
"sideEffects": false,
"type": "module",
"exports": {
".": {
"import"
Key conventions:
- Use
workspace:~ for internal Fluid dependencies
- Match the monorepo version (
2.90.0 or current)
- Use
"type": "module" for ESM-first
- Internal dependencies go in
dependencies, build tools in devDependencies
tsconfig.json (ESM)
Adjust the extends path based on package depth relative to common/build/build-common/.
{
"extends": "../../../common/build/build-common/tsconfig.node16.json",
"include": ["src/**/*"],
"exclude": ["src/test/**/*"],
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib"
}
}
tsconfig.cjs.json (CJS)
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./dist"
}
}
src/index.ts
export type { IMyInterface } from "./myInterface.js";
export { MyClass } from "./myClass.js";
Tag every export with a release tag (@public, @beta, @alpha, @internal) in the source file where it's defined.
src/internal.ts
Re-exports everything from index.ts plus any internal-only exports:
export * from "./index.js";
src/cjs/package.json
{
"type": "commonjs"
}
This gets copied to dist/ during build to mark CJS output.
eslint.config.mts
Adjust the import path depth to reach common/build/eslint-config-fluid/.
import type { Linter } from "eslint";
import { strict } from "../../../common/build/eslint-config-fluid/flat.mts";
const config: Linter.Config[] = [
...strict,
];
export default config;
Available presets: strict (core packages), recommended (mid-level), minimalDeprecated (minimal).
.mocharc.cjs
"use strict";
const testCJS = process.env.FLUID_TEST_MODULE_SYSTEM === "CJS";
const outputFilePrefix = testCJS ? "CJS-" : "";
const suiteName = "@fluidframework/my-package" + (testCJS ? " - CJS" : "");
module.exports = {
spec: testCJS ? "dist/test/**/*.spec.*js" : "lib/test/**/*.spec.*js",
recursive: true,
reporter: "mocha-multi-reporters",
"reporter-options": [
`configFile=test-config.json,cmrOutput=xunit+output+${outputFilePrefix}:xunit+suiteName+${suiteName}`,
],
"unhandled-rejections": "strict",
};
test-config.json
{
"reporterEnabled": "spec, xunit",
"xunitReporterOptions": {
"output": "nyc/{id}junit-report.xml",
"suiteName": "{id}"
}
}
src/test/tsconfig.json
Adjust the extends path depth.
{
"extends": "../../../../common/build/build-common/tsconfig.test.node16.json",
"compilerOptions": {
"rootDir": "./",
"outDir": "../../lib/test",
"types": ["mocha", "node"]
},
"include": ["./**/*"],
"references": [
{ "path": "../.." }
]
}
API Extractor Configs
api-extractor/api-extractor.current.json
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"extends": "<projectFolder>/../../../common/build/build-common/api-extractor-report.esm.current.json",
"mainEntryPointFilePath": "<projectFolder>/lib/public.d.ts"
}
api-extractor/api-extractor.legacy.json
Only needed if the package has a legacy API surface. Otherwise omit.
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"extends": "<projectFolder>/../../../common/build/build-common/api-extractor-report.esm.legacy.json"
}
api-extractor/api-extractor-lint-public.esm.json
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"extends": "<projectFolder>/../../../common/build/build-common/api-extractor-lint.entrypoint.json",
"mainEntryPointFilePath": "<projectFolder>/lib/public.d.ts"
}
api-extractor/api-extractor-lint-public.cjs.json
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"extends": "<projectFolder>/../../../common/build/build-common/api-extractor-lint.entrypoint.json",
"mainEntryPointFilePath": "<projectFolder>/dist/public.d.ts"
}
api-extractor/api-extractor-lint-bundle.json
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"extends": "<projectFolder>/../../../common/build/build-common/api-extractor-lint.json",
"mainEntryPointFilePath": "<projectFolder>/lib/index.d.ts"
}
Add additional lint configs for ./legacy, ./beta, or ./alpha paths if the package exposes them.
Register in layerInfo.json
Add the package to the appropriate group and layer in layerInfo.json at the repo root:
"LayerName": {
"packages": ["@fluidframework/my-package"],
"deps": ["Core-Interfaces", "Core-Utils"]
}
Choose dependencies carefully — they define the allowed dependency graph. Run flub check layers --info layerInfo.json to validate.
Post-Creation Steps
- Run
pnpm install to update the workspace lockfile
- Build the package:
fluid-build packages/category/my-package --task build
- Generate API reports:
cd packages/category/my-package && npm run build:api-reports
- Commit the generated
api-report/ files
- Run
flub check policy to verify repo policy compliance
- Run
flub check layers --info layerInfo.json to verify layer dependencies
Biome Formatting
Packages inherit from the root biome.jsonc automatically. No per-package config needed unless you need overrides (rare).
Default formatting: tabs, 95-char line width, double quotes, semicolons always, trailing commas.
Checklist