Skip to main content
This guide explains how to create new packages in the Trezor Suite monorepo and follow best practices for package organization.

Quick Start

Use the package generator to create a new package:
yarn generate-package @scope/new-package-name
The generator will create a package boilerplate in the appropriate folder based on the scope.

Example

yarn generate-package @suite-common/wallet
This creates a package in the /suite-common/wallet folder.

Understanding Package Scopes

Choose the correct scope based on what your package does and what it needs to import:

@trezor/*

Location: /packagesUse for: Public packages used by Suite and third partiesCan import: Nothing from other scopesExamples: @trezor/connect, @trezor/utils

@suite-common

Location: /suite-commonUse for: Code shared between web/desktop and mobile SuiteCan import: @trezor/* onlyExamples: Wallet logic, shared state management

@suite-native

Location: /suite-nativeUse for: Mobile Suite (React Native)Can import: @trezor/*, @suite-commonExamples: Mobile-specific UI, native integrations

@suite

Location: /suiteUse for: Desktop and web SuiteCan import: @trezor/*, @suite-commonExamples: Web-specific features, desktop integrations

Using Your New Package

After creating a package, follow these steps to use it:
1

Add to Dependencies

Add the package to the dependencies field in package.json of the consuming package:
package.json
{
  "dependencies": {
    "@scope/new-package-name": "workspace:*"
  }
}
2

Generate TypeScript References

Run the refs command to generate tsconfig references:
yarn refs
3

Install Dependencies

Run yarn to let it symlink the package:
yarn
4

Import and Use

Import the package in your code:
import { something } from '@scope/new-package-name';

Package Design Best Practices

Smaller is Better

Creating smaller packages helps avoid cyclic dependencies and improves maintainability.
Avoid This Pattern:
  1. Create packageA with type FormInput and multiple forms
  2. Import FormInput from packageA into packageB
  3. Try to import form from packageB back into packageA
  4. Get cyclic dependency error
This forces you to either merge packages (creating a monolith) or refactor.
Better Approach:Create packageC containing shared types like FormInput. Both packageA and packageB import from packageC, avoiding circular dependencies.

Benefits of Smaller Packages

Avoid Cyclic Dependencies

Smaller packages reduce the chance of circular import issues.

Better Control

Fine-grained control over what other packages can use.

Faster Testing

Run smaller subsets of tests and lints for faster feedback.

Clear Purpose

Each package has a well-defined, focused responsibility.

Package Structure

A typical package structure looks like:
@scope/package-name/
├── src/
│   ├── index.ts          # Main entry point
│   ├── types.ts          # Type definitions
│   └── ...
├── tests/
│   └── *.test.ts         # Tests
├── package.json          # Package configuration
├── tsconfig.json         # TypeScript config
├── README.md             # Documentation
└── CHANGELOG.md          # Version history

Package Configuration

package.json

Key fields in your package.json:
package.json
{
  "name": "@scope/package-name",
  "version": "1.0.0",
  "private": true,  // or false for published packages
  "main": "./lib/index.js",
  "types": "./lib/index.d.ts",
  "scripts": {
    "build": "yarn build:lib",
    "build:lib": "tsc",
    "test": "jest",
    "lint": "eslint .",
    "type-check": "tsc --noEmit"
  },
  "dependencies": {},
  "devDependencies": {}
}

TypeScript Configuration

Your tsconfig.json should extend the root configuration:
tsconfig.json
{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "./lib",
    "rootDir": "./src",
    "composite": true
  },
  "include": ["src/**/*"],
  "references": [
    // Generated by yarn refs
  ]
}

Preventing Cyclic Dependencies

The Problem

Cyclic dependencies occur when:

The Solution

Extract shared code into a separate package:

Respect Scope Boundaries

1

@trezor packages

Cannot import from any other scope. Keep them independent and reusable.
2

@suite-common packages

Can only import from @trezor/*. No Suite-specific or native dependencies.
3

@suite and @suite-native packages

Can import from @trezor/* and @suite-common, but not from each other.

Testing Your Package

After creating a package, ensure it works correctly:
# Type checking
yarn workspace @scope/package-name type-check

# Linting
yarn workspace @scope/package-name lint

# Unit tests
yarn workspace @scope/package-name test

# Build
yarn workspace @scope/package-name build

Common Pitfalls

Don’t create packages that are too largeLarge packages like the original packages/suite lead to cyclic dependency issues and become difficult to maintain.
Don’t violate scope boundariesImporting from unauthorized scopes breaks the architecture. The build system will enforce these rules.
Don’t duplicate code instead of creating shared packagesIf multiple packages need the same utility, create a shared package rather than copying code.

Examples

Creating a Utility Package

# Create in @trezor scope (no dependencies on other scopes)
yarn generate-package @trezor/validation-utils

Creating a Shared State Package

# Create in @suite-common (shared between web and mobile)
yarn generate-package @suite-common/wallet-state

Creating a Platform-Specific Package

# For web/desktop
yarn generate-package @suite/desktop-native-bindings

# For mobile
yarn generate-package @suite-native/biometrics

Next Steps

Package Overview

Browse all packages in the monorepo

Common Tasks

Learn about dependency management and troubleshooting

TypeScript Guide

TypeScript conventions and best practices

Testing

How to write tests for your package

Build docs developers (and LLMs) love