# Adding A Feature

> Step-by-step guide for adding a new tool or service to @webiny/stdlib.

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

---


# Adding a New Tool to @webiny/stdlib

This mirrors the "Step-by-step: Adding a New Tool to an Existing Package" section in the repo's `AGENTS.md`, which is the authoritative source — check there if this drifts.

First decide which slice the tool belongs in, based on its runtime dependencies:

| Uses only JS built-ins / standard lib | → `@webiny/stdlib` root (`src/`) |
| Uses `node:*` APIs or Node-only npm packages | → `@webiny/stdlib/node` (`src/node/`) |
| Uses `window`, `document`, React, browser APIs | → `@webiny/stdlib/browser` (`src/browser/`) |

`src/node/` and `src/browser/` must NOT import from each other. Both may import common code via `~/common/index.js`.

Example below: adding `HttpTool` to `@webiny/stdlib/node`. Substitute the appropriate slice and path for your tool.

## Steps

1. **Create the abstraction** at `src/node/features/HttpTool/abstractions/HttpTool.ts`:
   - Define `interface IHttpTool` with JSDoc on each method.
   - Export `const HttpTool = createAbstraction<IHttpTool>("Core/HttpTool")`.
   - Export `namespace HttpTool { export type Interface = IHttpTool }`.

2. **Create the abstraction barrel** at `src/node/features/HttpTool/abstractions/index.ts`:
   - `export { HttpTool } from "./HttpTool.js";`

3. **Create the implementation** at `src/node/features/HttpTool/HttpTool.ts`:
   - Rename the token import to avoid a name clash: `import { HttpTool as HttpToolAbstraction } from "./abstractions/HttpTool.js";`
   - Import cross-slice dependencies via the common barrel: `import { Logger } from "~/common/index.js";`
   - `class HttpToolImpl implements HttpToolAbstraction.Interface { ... }`
   - `export const HttpTool = HttpToolAbstraction.createImplementation({ implementation: HttpToolImpl, dependencies: [...] })`
   - The `dependencies` array order must match the constructor parameter order exactly.

4. **Create the feature** at `src/node/features/HttpTool/feature.ts`:
   - `export const HttpToolFeature = createFeature({ name: "Core/HttpToolFeature", register(container) { container.register(HttpTool).inSingletonScope(); } })`

5. **Create the feature index** at `src/node/features/HttpTool/index.ts`:
   - `export { HttpTool } from "./abstractions/index.js";`
   - `export { HttpToolFeature } from "./feature.js";`
   - Never re-export the implementation class or the `createImplementation` output.

6. **Create the feature README** at `src/node/features/HttpTool/README.md`:
   - One-paragraph description — what it does and when to reach for it.
   - Interface section — the public methods with JSDoc (copy from the abstraction file).
   - Usage section — two code snippets: DI container wiring, and a `createXxx()` factory function.
   - Prepend YAML front-matter (`name`, `description`, `context`) so the README is discoverable by the MCP server — see the front-matter block at the top of any existing feature README for the exact shape.
   - Update the repo root `README.md` table to add a row pointing to the new feature README.

7. **Add to the slice barrel** `src/node/index.ts`:
   - `export { HttpTool, HttpToolFeature } from "./features/HttpTool/index.js";`

8. **Write tests** at `__tests__/node/HttpTool.test.ts`:
   - Create a `makeContainer()` helper — see the `testing-patterns` skill.
   - Cover the happy path and error paths.
   - Node tests do not need a `// @vitest-environment` directive (only browser tests do).

9. **Run the pre-commit chain** until fully clean:

   ```sh
   yarn && yarn adio && yarn format:fix && yarn lint:fix && yarn typecheck && yarn build && yarn test:coverage
   ```

   All steps must pass with zero errors and zero warnings before staging and committing. If any step fails, fix the issue and run the full chain again from the start.

## Conventions to keep in mind throughout

- **No default exports.** Always use named exports.
- **JSDoc comments are preferred** on interface methods, abstraction types, and public class methods — they're read by both IDE tooling and agents. Don't write comments that just restate the method name.
- **Strict TypeScript** — all strict flags in `tsconfig.json` must pass.
- **`.js` extensions in package source imports** — under `src/`, `import { Foo } from "./Foo.js"` (not `.ts`); tsgo resolves `.js` to `.ts` at compile time. (Exception: `scripts/`, which runs directly under Node 24 and needs real `.ts` extensions.)
- **`node:` prefix for Node built-ins** — `import { readFileSync } from "node:fs"`, never `"fs"`.
- **Singletons via DI** — register utils as `.inSingletonScope()` unless there's a reason not to.
- **DI token names use `"Domain/ToolName"` format** — `"Core/"` prefix for common utils, `"Node/"` prefix for Node-specific ones.
- **Namespace pattern for interface types** — always export `namespace ToolName { export type Interface = IToolName }` alongside the token.

See the `di-patterns` skill for the underlying abstraction/implementation/feature mechanics, and AGENTS.md's "Step-by-step: Adding a New Slice to @webiny/stdlib" section if you're adding an entirely new runtime environment rather than a tool within an existing one.

