---
title: Implement DiceBear Core
description: >
Step-by-step guide for implementing DiceBear Core in any programming language.
Covers the PRNG contract, options resolution, and SVG rendering pipeline.
---
# Implement DiceBear Core
This guide explains how to implement DiceBear Core in any programming language.
A correct implementation produces **byte-identical SVGs** to the
[JavaScript](https://github.com/dicebear/dicebear/tree/10.x/src/js/core),
[PHP](https://github.com/dicebear/dicebear/tree/10.x/src/php/core),
[Python](https://github.com/dicebear/dicebear/tree/10.x/src/python/core),
[Rust](https://github.com/dicebear/dicebear/tree/10.x/src/rust/core),
[Go](https://github.com/dicebear/dicebear/tree/10.x/src/go/core) and
[Dart](https://github.com/dicebear/dicebear/tree/10.x/src/dart/core) reference
implementations for the same seed and style definition.
## Architecture overview
```
Avatar(definition, options)
│
├── Style Parse and validate the definition JSON
├── Options Resolve options using the PRNG
└── Renderer Generate SVG from resolved style + options
│
├── Prng Deterministic random number generator
│ ├── Fnv1a FNV-1a 32-bit hash
│ └── Mulberry32 Stateful PRNG
│
└── SVG output
```
The core is intentionally minimal. It takes a
[style definition](/specification/definition-schema/) and user options, resolves
randomizable values through a deterministic PRNG, and renders an SVG string.
## PRNG contract
The PRNG is the interoperability surface. If your PRNG produces the same outputs
as the reference for the same inputs, your implementation will produce identical
SVGs. Get this right first.
### FNV-1a 32-bit hash
[FNV-1a](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function)
converts a string into a 32-bit unsigned integer. DiceBear iterates over
**UTF-16 code units** (not bytes, not code points).
```
offset_basis = 0x811c9dc5
prime = 0x01000193
function fnv1a_hash(input: string) -> uint32:
hash = offset_basis
for each UTF-16 code unit c in input:
hash = hash XOR c
hash = hash * prime (32-bit multiply, discard overflow)
return hash as unsigned 32-bit
```
In JavaScript, `input.charCodeAt(i)` returns UTF-16 code units directly.
Languages without native UTF-16 strings must convert first. Outside the Basic
Multilingual Plane (e.g. emoji), code units and code points diverge, and using
code points instead produces wrong hashes for those inputs. The PHP reference
does the conversion explicitly:
```php
unpack('v*', mb_convert_encoding($input, 'UTF-16LE', 'UTF-8'))
```
Java and C# can iterate `string.charAt(i)` / `char` directly. For other
languages, transcode to UTF-16 and read 16-bit units.
**Reference (JS):**
```js
static hash(input) {
let hash = 0x811c9dc5;
for (let i = 0; i < input.length; i++) {
hash ^= input.charCodeAt(i);
hash = Math.imul(hash, 0x01000193);
}
return hash >>> 0;
}
```
### Mulberry32
[Mulberry32](https://gist.github.com/tommyettinger/46a874533244883189143505d203312c)
is a stateful PRNG that converts a 32-bit seed into a sequence of pseudo-random
numbers. The implementation must match Tommy Ettinger's C reference exactly.
```
function mulberry32_next(state) -> (uint32, new_state):
state = (state + 0x6D2B79F5) as signed 32-bit
z = state
z = (z XOR (z >>> 15)) * (z OR 1) (32-bit multiply)
z = z XOR (z + ((z XOR (z >>> 7)) * (z OR 61))) (32-bit multiply)
return (z XOR (z >>> 14)) as unsigned 32-bit
function mulberry32_next_float(state) -> (float, new_state):
(value, new_state) = mulberry32_next(state)
return value / 2^32
```
**Reference (JS):**
```js
next() {
const z = (this.#state = (this.#state + 0x6d2b79f5) | 0);
let t = Math.imul(z ^ (z >>> 15), z | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0);
}
nextFloat() {
return this.next() / 4294967296; // 2^32
}
```
Key details:
- `| 0` forces 32-bit signed integer (handles overflow)
- `>>> 0` converts back to unsigned 32-bit
- `Math.imul` performs 32-bit integer multiplication
- `nextFloat()` returns a value in `[0, 1)` by dividing by `2^32`
- The state is **stateful**: it advances with each call to `next()`
- Languages with 64-bit integers but no native `uint32_t` (e.g. PHP, Lua) must
implement the 32-bit multiply manually: a naïve `uint32 * uint32` exceeds
`2^63 - 1` and silently overflows. The PHP reference splits one operand into
16-bit halves; see
[`Prng/Mulberry32.php::mul`](https://github.com/dicebear/dicebear/blob/10.x/src/php/core/src/Prng/Mulberry32.php).
### Key-based value generation
DiceBear does not call the PRNG sequentially. Instead, each random decision uses
a **key** to derive an independent value. This makes the output independent of
call order.
```
function getValue(seed: string, key: string) -> float:
hash = fnv1a_hash(seed + ":" + key)
prng = new Mulberry32(hash)
return prng.nextFloat()
```
For example, `getValue("alice", "eyesVariant")` always returns the same float,
regardless of whether `getValue("alice", "mouthVariant")` was called before or
after.
### Selection methods
For inputs with more than one entry, every selection method first normalizes:
1. **Deduplicate** by the item's string representation, keeping the first
occurrence (`pick` and `shuffle` only; `weightedPick` operates on a map and
has unique keys by construction).
2. **Sort** by the item's string representation using UTF-16 code unit
comparison (JavaScript's default `.sort()` order).
Empty inputs return `undefined` (or an empty array for `shuffle`); single-entry
inputs are returned verbatim without deduplication or sorting. Both
normalization steps make multi-entry output independent of caller ordering and
duplicates. In practice the only values ever sorted are component variant names
and hex color strings (both guaranteed to be ASCII), so an implementation may
compare with `strcmp` and stay parity-correct, even though the JavaScript
reference compares full UTF-16 code units. The PHP reference does exactly this.
#### `pick(key, items) -> item | undefined`
Selects one item from an array.
```
function pick(seed, key, items):
if items is empty: return undefined
if items has 1 item: return items[0]
unique = deduplicate items by string representation
if unique has 1 item: return unique[0]
sorted = sort unique by string representation
index = floor(getValue(seed, key) * length(sorted))
return sorted[index]
```
#### `weightedPick(key, weights) -> key | undefined`
Takes a map of `string → weight` and returns one of the map's keys, biased by
weight. When every weight is `0`, falls back to an unweighted `pick` across the
keys.
```
function weightedPick(seed, key, weights):
keys = keys of weights
if keys is empty: return undefined
if keys has 1 item: return keys[0]
sorted = sort keys by string representation
totalWeight = sum of weights[k] for k in sorted
if totalWeight == 0: return pick(seed, key, sorted)
threshold = getValue(seed, key) * totalWeight
cumulative = 0
for each k in sorted:
cumulative += weights[k]
if threshold < cumulative: return k
return last(sorted)
```
#### `bool(key, likelihood) -> boolean`
Returns `true` with probability `likelihood / 100`. `likelihood` defaults to
`50`.
```
function bool(seed, key, likelihood = 50):
return getValue(seed, key) * 100 < likelihood
```
#### `float(key, range) -> number`
Returns a float in the closed range, rounded to four decimal places. `range` is
the schema's `{ min, max, step? }` object. If `min > max`, swap them internally.
With `step > 0`, sample uniformly from
`{ min + i × step | 0 ≤ i ≤ ⌊(max − min) / step⌋ }`, so when `(max − min)` is
not a multiple of `step`, the last bucket is `≤ max` and `max` itself is only
hit when the division is exact. Without `step`, the range is continuous.
```
function float(seed, key, range):
min = min(range.min, range.max)
max = max(range.min, range.max)
step = range.step if range.step > 0 else 0
if step > 0:
buckets = floor((max - min) / step) + 1
i = floor(getValue(seed, key) * buckets)
raw = min + i * step
else:
raw = min + getValue(seed, key) * (max - min)
return round(raw * 10000) / 10000 # round halves toward +Infinity
```
The `round` here is the same as in [number formatting](#number-formatting):
halves round **toward +Infinity** (JavaScript's `Math.round`), not your
language's native rounding. PHP's `round()` and many others round halves _away
from zero_, which diverges for negative values landing exactly on a `.5`
boundary (e.g. `round(-0.40625 × 10000) / 10000` is `-0.4062`, not `-0.4063`).
#### `integer(key, range) -> number`
Returns an integer in the closed range, inclusive on both ends. Accepts the same
`{ min, max, step? }` object as `float`; `step` is accepted for symmetry but
ignored, since integers already step by 1.
```
function integer(seed, key, range):
min = min(range.min, range.max)
max = max(range.min, range.max)
return floor(getValue(seed, key) * (max - min + 1)) + min
```
#### `shuffle(key, items) -> items[]`
Fisher-Yates shuffle using a **stateful** Mulberry32 instance (not key-based).
For inputs of length ≤ 1 the items are returned as a copy without deduplication.
```
function shuffle(seed, key, items):
if length(items) <= 1: return copy of items
unique = deduplicate items by string representation
sorted = sort unique by string representation
result = copy of sorted
prng = new Mulberry32(fnv1a_hash(seed + ":" + key))
for i from length(result) - 1 down to 1:
j = floor(prng.nextFloat() * (i + 1))
swap result[i] and result[j]
return result
```
Note: `shuffle` is the only method that uses a stateful PRNG instance directly
(calling `nextFloat()` multiple times). All other methods call `getValue()`
which creates a fresh PRNG for each key.
## Options resolution
The `Options` class resolves raw user options into concrete values used by the
renderer. Each resolution uses the PRNG with a specific key.
### Core options
| Option | PRNG key | Resolution |
| ----------------- | -------------- | -------------------------------------------------------------------------- |
| `seed` | — | Literal string; defaults to `''` if not provided. Not memoized. |
| `size` | — | Literal number; defaults to unset (renderer omits `width`/`height`). |
| `idRandomization` | — | Boolean; defaults to `false`. Uses host RNG, not the DiceBear PRNG. |
| `title` | — | Literal string; defaults to unset (omits `
`, uses `aria-hidden`). |
| `flip` | `flip` | `pick` from `['none', 'horizontal', 'vertical', 'both']`, default `'none'` |
| `rotate` | `rotate` | `float` from range, default `0` |
| `scale` | `scale` | `float` from range, default `1` |
| `borderRadius` | `borderRadius` | `float` from range, default `0` |
| `translateX` | `translateX` | `float` from range, default `0` |
| `translateY` | `translateY` | `float` from range, default `0` |
| `fontFamily` | `fontFamily` | `pick` from array, default `'system-ui'` |
| `fontWeight` | `fontWeight` | `pick` from array, default `400` |
Options with no PRNG key are read directly from the user input. The rest sample
from a user-supplied range/list under the given key, falling back to the listed
default.
The range options (`rotate`, `scale`, `borderRadius`, `translateX`,
`translateY`, and the per-color `${name}ColorAngle` / `${name}ColorFillStops`)
accept a number or an array, normalized to a `{ min, max }` range before
`float`/`integer` sampling:
- a bare number `n` → `{ min: n, max: n }` (a fixed value);
- a single-element array `[n]` → `{ min: n, max: n }` (same as the bare number);
- a two-element array → `{ min, max }` taken as the smaller/larger of the two
(order does not matter, sampling swaps them anyway);
- an empty array `[]`, or the option unset, → fall back to the listed default.
Note the edge cases: `[n]` is a fixed value (**not** the default), and `[]`
falls back to the default (**not** a range with a missing bound). A fixed range
where `min === max` always samples that exact value.
### Component options
For each component (e.g. `eyes`) the user can supply exactly two options:
| Option | PRNG key | Resolution |
| ----------------- | ----------------- | -------------------------------------------------------------------------------------------------------------- |
| `eyesProbability` | `eyesProbability` | `bool` with likelihood from the user option, falling back to the component's `probability` (or `100` if unset) |
| `eyesVariant` | `eyesVariant` | `weightedPick` over a weighted map (see below) |
If the probability check fails, the component is not rendered and `variant`
returns `undefined`.
`eyesVariant` accepts three shapes from the user: a single variant name, an
array of names, or a `Record` weight map. Normalize the first
two to a map where each named variant has weight `1`. Then drop any keys that
are not declared in the component's `variants` block, and feed the remaining map
to `weightedPick`. When the user did not supply the option, build the map from
the variants' own `weight` values (defaulting to `1`).
For **component aliases** (declared via `extends` in the definition), the user
side is shared and only the PRNG side is independent. An alias does not expose
its own `${aliasName}Probability` or `${aliasName}Variant` user option. Both are
read from the source component's `${sourceName}Probability` and
`${sourceName}Variant`. The PRNG, however, uses the alias's own name as the key
(`${aliasName}Probability`, `${aliasName}Variant`), so each alias rolls its
visibility and variant independently while still being constrained by the same
user-set weights.
### Per-component transforms (render-time)
Each component reference also has a rotation, two translations, and a scale
applied at render time. These are **not user options**: they are sampled per
render from the component definition's `rotate`/`translate`/`scale` ranges. They
land in the introspective `resolvedOptions` snapshot under `${name}Rotate` /
`${name}TranslateX` / `${name}TranslateY` / `${name}Scale`, but they are not
part of the user-facing `StyleOptions` type and feeding them back into a new
`Avatar` is not supported.
| Value | PRNG key | Sampling |
| ---------- | ---------------- | ------------------------------------------------- |
| rotate | `eyesRotate` | `float` from `component.rotate`, default `0` |
| translateX | `eyesTranslateX` | `float` from `component.translate.x`, default `0` |
| translateY | `eyesTranslateY` | `float` from `component.translate.y`, default `0` |
| scale | `eyesScale` | `float` from `component.scale`, default `1` |
The translate values are percentages of the **component's own** `width` and
`height` (not the avatar canvas); multiply by the component dimension to get the
offset. Like every emitted number it is then run through
[`formatNumber`](#number-formatting) (which caps it at 5 decimal places). The
transform center `(cx, cy)` for rotate and scale is the component's own center:
`(width / 2, height / 2)`.
In the emitted SVG, the non-identity values are concatenated (space-separated)
into a single `transform` attribute on the `