Files
dicebear--dicebear/CONTRIBUTING.md
T
2026-07-13 12:25:08 +08:00

14 KiB

Contributing

Thanks for your interest in contributing to DiceBear.

This is the main monorepo: the JavaScript, PHP, Python, Rust, Go, and Dart core libraries, the CLI, the docs site, and the editor all live here. Repositories covering the JSON Schema, the avatar style definitions, the HTTP API, and the Figma exporter are separate and each have their own CONTRIBUTING.md:

If your contribution belongs to one of those repos, read its file first. The instructions below only cover this monorepo.

Before you start

  • Bug fixes, small improvements, new tests: open a pull request against the branch that matches the target major (for DiceBear 10 that's 10.x; the current stable line lives on 9.x).
  • New avatar styles: contribute them to dicebear/styles, not here. The walkthrough is in Create an avatar style with Figma or Create an avatar style from scratch.
  • Larger changes (new public API, rendering behavior, breaking changes): open an issue first so we can agree on scope before you spend time on it.
  • Security issues go privately to contact@dicebear.com; never file a public issue for a vulnerability.
  • Contributors follow the DiceBear Code of Conduct.

Requirements

  • Node.js 20 or newer (CI runs on 20, 22, 24, 25)
  • npm 11 (this repo pins packageManager in package.json; use Corepack if you don't already)
  • For PHP work: PHP 8.2+ and Composer, plus vendor/bin/phpunit via composer install inside src/php/core/
  • For Python work: Python 3.10+ (CI runs on 3.10 to 3.14); install the package in a virtualenv with pip install -e ".[dev]" inside src/python/core/
  • For Rust work: Rust 1.80+ with the clippy and rustfmt components; build and test with cargo test, cargo clippy, and cargo fmt inside src/rust/core/
  • For Go work: Go 1.23+ (CI runs on 1.23 to 1.25); test with go test ./... and check with gofmt -l . and go vet ./... inside src/go/core/
  • For Dart work: Dart SDK 3.4+ (CI runs on 3.4 and stable); test with dart test and check with dart format --output=none --set-exit-if-changed . and dart analyze --fatal-infos inside src/dart/core/

Local setup

git clone git@github.com:dicebear/dicebear.git
cd dicebear
npm install

The monorepo uses npm workspaces (src/js/* and apps/*) driven by Turborepo. A single install at the root is enough; do not npm install inside individual packages.

Common scripts

Run these from the repo root:

Script What it does
npm run build Builds every workspace via Turbo
npm test Runs every workspace's test target
npm run test:scripts Runs the repo-level scripts in tests/**/*.test.js
npm run lint Runs ESLint across the repo with caching
npm run lint:fix Same, with --fix
npm run prettier Formats src/ and apps/ with Prettier

For faster loops, scope to a single workspace:

npm run build --workspace @dicebear/core
npm run test  --workspace @dicebear/core

Repository layout

src/
├── js/              # TypeScript packages published to npm
│   ├── core/          # Rendering engine (@dicebear/core)
│   ├── cli/           # Command-line interface
│   └── converter/     # SVG → raster converter
├── php/             # PHP port (Composer package `dicebear/core`)
├── python/          # Python port (PyPI package `dicebear-core`)
├── rust/            # Rust port (crates.io crate `dicebear-core`)
├── go/              # Go port (module `github.com/dicebear/dicebear-go/v10`)
└── dart/            # Dart port (pub.dev package `dicebear_core`)
apps/
├── docs/            # VitePress documentation site (dicebear.com), including the Playground
└── editor/          # The in-browser editor (editor.dicebear.com)
tests/
└── fixtures/parity/ # Cross-language parity fixtures (see below)
scripts/
└── version.mjs      # Bumps versions across the workspace and tags

Working on a package

TypeScript packages (src/js/*)

npm run build --workspace <package>
npm run test  --workspace <package>

If you are working on the CLI, call the compiled script directly once you've built it:

node src/js/cli/bin/index.js <command>

PHP core (src/php/core/)

cd src/php/core
composer install
vendor/bin/phpunit

Python core (src/python/core/)

cd src/python/core
pip install -e ".[dev]"   # in a virtualenv
ruff check .
ruff format --check .
mypy src
pytest

The Python core reads the two draft-07 schemas from the dicebear-schema package (the Python counterpart of @dicebear/schema / dicebear/schema), which pip install -e ".[dev]" pulls in as a runtime dependency. Nothing is vendored.

Rust core (src/rust/core/)

cd src/rust/core
cargo test
cargo clippy --all-targets --all-features -- -D warnings
cargo fmt --check

The Rust core reads the two draft-07 schemas from the dicebear-schema crate (the Rust counterpart of @dicebear/schema / dicebear/schema) as a runtime dependency. Nothing is vendored.

Go core (src/go/core/)

cd src/go/core
go test ./...
go vet ./...
gofmt -l .   # prints nothing when formatting is correct

The Go core reads the two draft-07 schemas from the github.com/dicebear/schema module (the Go counterpart of @dicebear/schema / dicebear/schema) as a dependency. Nothing is vendored. Style definitions come from github.com/dicebear/styles/v10. The module path carries the major version (/v10), so a major bump changes the path by hand; scripts/version.mjs only creates the Git tag the module proxy reads.

Dart core (src/dart/core/)

cd src/dart/core
dart pub get
dart test
dart analyze --fatal-infos
dart format --output=none --set-exit-if-changed .

The Dart core reads the two draft-07 schemas from the dicebear_schema package (the Dart counterpart of @dicebear/schema / dicebear/schema) as a runtime dependency, validated with package:json_schema. Nothing is vendored.

src/dart/core/CHANGELOG.md is a git-ignored build artifact: pub.dev expects a changelog inside the package directory, so the CI workflows copy the repository root CHANGELOG.md there before dart pub publish [--dry-run]. This follows the same pattern as the generated LICENSE copies in the styles and schema repositories. Maintain the root changelog only.

Cross-language parity

Every port must produce output byte-identical to the reference JavaScript core (@dicebear/core) for the same inputs. A shared fixture suite in tests/fixtures/parity/ (generated from the JS reference) enforces this, and each side consumes it:

  • JS side: src/js/core/tests/Parity.test.js, run via npm run test --workspace @dicebear/core.
  • PHP side: tests/ParityTest.php, run via vendor/bin/phpunit in src/php/core/.
  • Python side: tests/test_parity.py, run via pytest in src/python/core/.
  • Rust side: the module unit tests plus tests/avatars.rs, run via cargo test in src/rust/core/.
  • Go side: the in-package tests (parity_test.go, avatars_test.go), run via go test ./... in src/go/core/.
  • Dart side: the tests under test/parity/, run via dart test in src/dart/core/.

The fixtures cover Fnv1a (hash + hex), Mulberry32 (chained sequences), every Prng method, number-to-string formatting (numbers.json, the formatNumber contract: every emitted number rounded to at most 5 decimal places, which every port must reproduce identically), initials extraction, the Color helpers (including bit-exact luminance doubles), accept/reject validation outcomes plus circular color-reference chains, the OptionsDescriptor field map, and full Avatar.toString() (plus selected toDataUri()) output for the initials, thumbs, glass, notionists, and shape-grid styles. That last bucket covers seed, size, transforms, gradients, title escaping, and component-variant overrides.

Float determinism is part of the parity contract: pow is not correctly rounded (JS engines, libm, and Go all differ in the last bit), so the sRGB linearization ships as a precomputed 256-entry table in every port, and languages that fuse multiply-add into FMA instructions (e.g. Go on arm64) must force intermediate rounding. The details live in the Implement DiceBear Core spec.

If your change affects rendering or the PRNG in @dicebear/core, regenerate the fixtures from the JS reference and commit the diff:

npm run fixtures:parity

The PHP, Python, Rust, Go, and Dart suites will then fail loudly until those sides are brought back in sync. That is the intended signal. If you only intend to touch one language, expect to update both before your PR can be merged.

When porting DiceBear to another language, run these fixtures against your implementation to prove it conforms. See Implement DiceBear Core for the full spec.

Documentation changes (apps/docs/)

The docs site is a VitePress app under apps/docs/.

npm run dev --workspace @dicebear/docs    # live reload on localhost
npm run build --workspace @dicebear/docs  # production build check

For larger editorial changes, open a draft PR early so reviewers can follow along.

Editor changes (apps/editor/)

The editor is the standalone app served at editor.dicebear.com.

npm run dev --workspace @dicebear/editor
npm run build --workspace @dicebear/editor

Code style

  • ESLint and Prettier decide what counts as correctly formatted code; run npm run lint and npm run prettier before you open a PR.
  • TypeScript is strict. Prefer narrow types to any / unknown casts.
  • PHP code follows PSR-12; vendor/bin/phpunit and Composer's built-in scripts catch the rest.
  • Python code is formatted and linted with Ruff and type-checked with mypy in strict mode; run ruff check ., ruff format ., and mypy src in src/python/core/ before you open a PR.
  • Rust code is formatted with rustfmt and linted with Clippy (warnings denied); run cargo fmt and cargo clippy --all-targets --all-features -- -D warnings in src/rust/core/ before you open a PR.
  • Go code is formatted with gofmt and vetted with go vet; run gofmt -w . and go vet ./... in src/go/core/ before you open a PR.
  • Dart code is formatted with dart format and analyzed with dart analyze --fatal-infos (lints from analysis_options.yaml); run both in src/dart/core/ before you open a PR.

Releasing (maintainers only)

Only maintainers with write access can release new versions. This section is documented here for completeness.

Releases are triggered by Git tags. The version script updates every package in the workspace, creates a commit, and creates the tag:

node scripts/version.mjs <version>

The version must be a valid semver value (e.g. 10.1.0 or 10.2.0-alpha.1). The script will:

  1. Update version in every package.json across the workspace
  2. Update internal workspace dependency references
  3. Update version in src/python/core/pyproject.toml, src/rust/core/Cargo.toml, and src/dart/core/pubspec.yaml (the Python, Rust, and Dart cores are not npm workspaces, so they are bumped explicitly to stay in lockstep)
  4. Sync package-lock.json
  5. Create a Git commit and tag (e.g. v10.1.0)

Push the commit and the tag:

git push && git push --tags

The tag triggers the Publish workflow, which:

  1. Runs the test suite on Node 20, 22, 24, and 25
  2. Builds every package
  3. Picks the npm dist-tag:
    • Tags containing alpha, beta, or rc go out as next
    • Other tags on the default branch go out as latest
    • Other tags on any other branch go out as v<major>-lts (so that backporting a patch to 9.x after 10.x has shipped does not overwrite latest)
  4. Publishes all changed packages to npm via scripts/publish.mjs
  5. Builds src/python/core and publishes dicebear-core to PyPI via trusted publishing (the publish-python job): no token and no separate repository, the same way the npm packages go out
  6. Publishes the Rust core dicebear-core to crates.io via trusted publishing (the publish-rust job): likewise no token; cargo publish builds and uploads src/rust/core in one step
  7. Publishes the Dart core dicebear_core to pub.dev via automated publishing (the publish-pub job): likewise no token; pub.dev verifies the v<version> tag against the pubspec version and publishes src/dart/core straight from the monorepo

The PHP port is the exception: Composer/Packagist consumes one Git repository per package rather than a monorepo subdirectory, so split-php-core.yml mirrors src/php/core (tags included) to the standalone dicebear/dicebear-php repository, and Packagist publishes dicebear/core from that mirror. All five ports ride the same version the monorepo tagged.

Licensing

By opening a pull request you agree that your contribution is released under the repository's MIT license. Avatar style artwork in dicebear/styles may carry other licenses; see that repo's LICENSE.md for details.