Skip to main content

Conventions

Rules the types cannot enforce. Each one exists because breaking it fails in a way that is hard to trace back.

No hidden fallbacks

If a value can be absent, model the absence and handle each branch. A ?? over missing host data hides a real error — the mod keeps running with a made-up value, and the failure surfaces somewhere unrelated.

Do not import @modular/sdk/internal

It is published for the host, not for you, and it changes without notice. It is reachable through the exports map, which is not the same as being supported.

The same applies to ./eslint-plugin and ./ambient-surface: both are in the exports map because the build needs them, not because they are author API.

Do not bundle React, the design system, or @modular/sdk/ui

The host injects those instances. A second copy in your bundle breaks hooks and context, because two Reacts in one tree do not share state.

This is the most common cause of a view that renders once and then behaves strangely.

Tool types are the contract

Declare tool input and result types in TypeScript. The authoring compiler generates the runtime validators from them. Do not restate them in Zod — the hand-written copy is the one that drifts.

Do not hand-edit the generated config

The host rewrites tsconfig.json and eslint.config.mjs on every activation. See Resolution.

Ids are addresses

Command ids, condition ids, canvas element ids, and auth provider ids are all referenced by string from somewhere else. Renaming one does not produce a type error; it produces a link to nothing.