The BBModel (.bbmodel) format is a JSON-based 3D model format created by Blockbench. It supports hierarchical scene graphs, multiple element types (cubes, meshes), animations, textures, and various model formats (Java Block, Bedrock, Free).
Current Format Version: 5.0 (with backwards compatibility to 3.2+)
{
"meta": { /* Metadata */ },
"resolution": { /* Texture resolution */ },
"elements": [ /* Geometry elements */ ],
"groups": [ /* Hierarchy groups (v5.0+) */ ],
"outliner": [ /* Scene hierarchy */ ],
"textures": [ /* Texture definitions */ ],
"animations": [ /* Animation data */ ],
"animation_controllers": [ /* Animation state machines */ ],
"display": { /* Display transformations */ }
}Defines the format version and model type.
{
"meta": {
"format_version": "5.0", // Format specification version
"model_format": "free", // Model type: "free", "java_block", "bedrock", "bedrock_old"
"box_uv": false, // UV mapping mode
"backup": false // Optional: backup flag
}
}Key Fields:
format_version: Must be ≤5.0for compatibilitymodel_format: Determines available features and constraintsbox_uv:truefor box UV mapping,falsefor per-face UV
Texture dimensions for UV mapping.
{
"resolution": {
"width": 64, // Texture width in pixels
"height": 64 // Texture height in pixels
}
}Elements are the geometric primitives. Two main types: Cube and Mesh.
Box primitive with 6 faces.
{
"name": "cube_name",
"type": "cube",
"uuid": "c8eb7afc-1e80-c3b9-07e9-0e96b2c28979",
"from": [-3, 18.9, -1.5], // Corner position [x, y, z]
"to": [3, 22.9, 1.5], // Opposite corner [x, y, z]
"origin": [0, 10.9, 0], // Pivot point [x, y, z]
"rotation": [0, 0, 0], // Rotation [x, y, z] in degrees
"color": 0, // Color index (0-7)
"export": true, // Include in export
"visibility": true, // Visibility flag
"locked": false, // Lock editing
"box_uv": false, // UV mapping mode
"rescale": false, // Scale flag
"autouv": 0, // Auto UV generation
"faces": { // Face definitions
"north": {
"uv": [8, 16, 14, 20], // UV coordinates [u_min, v_min, u_max, v_max]
"texture": 0 // Texture index (or null)
},
"east": { /* ... */ },
"south": { /* ... */ },
"west": { /* ... */ },
"up": { /* ... */ },
"down": { /* ... */ }
}
}Coordinate System:
- Origin: Bottom-left-back corner
- Axes: X (right), Y (up), Z (forward)
- Units: Minecraft-style units (1 unit = 1/16 block)
Rotation:
- Applied around the
originpoint - Order: X → Y → Z (Euler angles)
- In degrees (not radians)
Freeform mesh with arbitrary vertices and faces.
{
"name": "cuboid",
"type": "mesh",
"uuid": "b102ab22-619d-e67b-f6ed-89755675cdad",
"origin": [44, 0, 0],
"rotation": [0, 0, 0],
"color": 3,
"export": true,
"visibility": true,
"locked": false,
"render_order": "default",
"allow_mirror_modeling": true,
"vertices": { // Vertex positions
"s1u1": [8, 8, 8], // vertex_id: [x, y, z]
"pzwl": [8, 8, -8],
"zjiK": [8, 0, 8],
/* ... more vertices ... */
},
"faces": { // Face definitions
"ZgMOQeDR": { // face_id
"uv": { // Per-vertex UV mapping
"s1u1": [0, 0], // vertex_id: [u, v]
"zjiK": [0, 8],
"pzwl": [16, 0],
"7KuF": [16, 8]
},
"vertices": [ // Vertex order (CCW for front-facing)
"s1u1", "zjiK", "pzwl", "7KuF"
],
"texture": 0 // Texture index (or null)
}
/* ... more faces ... */
}
}Mesh Topology:
- Vertices: Dictionary of unique IDs → 3D positions
- Faces: Quads (4 vertices) or triangles (3 vertices)
- Winding Order: Counter-clockwise for outward-facing normals
- UV Mapping: Per-vertex, allowing arbitrary UV layouts
Groups organize elements hierarchically. In v5.0+, groups are stored separately from the outliner.
{
"name": "arm_right",
"uuid": "afd758ba-59c8-6090-8f81-628d06cca15c",
"origin": [4, 21.9, 0], // Pivot point [x, y, z]
"rotation": [0, 0, 0], // Rotation [x, y, z] in degrees
"color": 0,
"export": true,
"mirror_uv": false,
"isOpen": true, // Expanded in UI
"locked": false,
"visibility": true,
"autouv": 0,
"nbt": "{}" // NBT data (Minecraft)
}Note: Groups don't store children directly; hierarchy is in outliner.
Defines the scene hierarchy tree. Elements can be:
- String (UUID): Reference to an element
- Object: Group with nested children
"outliner": [
"element-uuid-1", // Direct element reference
{
"name": "upper",
"uuid": "19029bcd-1a1d-f0b9-e7d8-1e4b9cf49e26",
"origin": [0, 18.9, 0],
"rotation": [0, 0, 0],
/* ... group properties ... */
"children": [
"element-uuid-2",
{
"name": "nested_group",
"uuid": "b9fe46b0-8468-c9f1-9f7b-6de7fa5d2268",
/* ... */
"children": [
"element-uuid-3"
]
}
]
}
]Hierarchy Rules:
- Root level can contain elements and groups
- Groups can nest infinitely
- Elements are leaves (no children)
- UUIDs must be unique across elements and groups
Transformations accumulate down the hierarchy:
World_Transform = Parent_Transform × Local_Transform
Where Local_Transform:
- Translate to origin (pivot point)
- Rotate around origin (X → Y → Z order)
- Translate back from origin
- Position element geometry
- Purpose: Rotation center for the node
- Inheritance: Child transforms are relative to parent's pivot
- Default:
[0, 0, 0]if not specified
Example: Rotating an arm
{
"name": "upper_arm",
"origin": [4, 20, 0], // Shoulder position
"rotation": [0, 0, -30] // Rotate 30° around Z-axis at shoulder
}For Cubes:
fromandtodefine AABB in local space- Center =
(from + to) / 2
For Meshes:
- Vertices are in local space
- Transformed by group hierarchy
Blockbench uses Extrinsic XYZ Euler angles:
- Rotate around X-axis
- Rotate around Y-axis
- Rotate around Z-axis
Matrix: R = Rz × Ry × Rx
Version Note: Pre-v3.2 used inverted Z-axis; compatibility layer applies -z correction.
{
"textures": [
{
"uuid": "096a4435-3bfa-4f75-6f44-41e728ea692e",
"name": "texture_name",
"id": "0", // Texture index
"path": "/absolute/path.png", // Absolute path
"relative_path": "textures/skin.png", // Relative path (v4.10+)
"width": 64,
"height": 64,
"uv_width": 64, // UV coordinate space
"uv_height": 64,
"particle": false, // Particle texture (Minecraft)
"render_mode": "default", // "default", "emissive", "layered"
"render_sides": "auto", // "auto", "front", "double"
"internal": true, // Embedded in file
"source": "data:image/png;base64,...", // Base64 image data
"mode": "bitmap",
"saved": false,
"visible": true,
"folder": "block",
"namespace": ""
}
]
}Texture Resolution:
path/relative_path: External texture filesource: Embedded Base64-encoded PNG/JPEG- Priority:
source>relative_path>path
{
"animations": [
{
"uuid": "b1498c3c-f8f0-4983-0d22-155f5b0cc2bf",
"name": "walk",
"loop": "loop", // "once", "loop", "hold"
"override": false,
"length": 1.0, // Duration in seconds
"snapping": 20, // Keyframe snap interval
"selected": false,
"anim_time_update": "", // Molang expression
"blend_weight": "", // Molang expression
"start_delay": "",
"loop_delay": "",
"animators": {
"bone-uuid": { // Animated node UUID
"name": "leg_right",
"type": "bone",
"keyframes": [ /* ... */ ]
}
}
}
]
}{
"channel": "rotation", // "position", "rotation", "scale"
"uuid": "e41ca207-18ba-c337-6455-12d474c59fc8",
"time": 0.5, // Time in seconds
"color": -1, // UI color
"interpolation": "linear", // "linear", "step", "catmullrom", "bezier"
"data_points": [
{
"x": 45.0, // Channel-specific value
"y": 0.0,
"z": 0.0
}
],
"bezier_linked": true,
"bezier_left_time": [-0.1, -0.1, -0.1],
"bezier_left_value": [0, 0, 0],
"bezier_right_time": [0.1, 0.1, 0.1],
"bezier_right_value": [0, 0, 0]
}Channels:
position: Translation in local space (units)rotation: Euler angles (degrees)scale: Scale factors (unitless)
Interpolation:
linear: Linear interpolationstep: Hold value until next keyframecatmullrom: Smooth spline (overshoots)bezier: Cubic Bezier with custom handles
Data Points:
- Single value: Continuous keyframe
- Two values: Discontinuous (instant jump)
For time t between keyframes kf1 (time t1) and kf2 (time t2):
Linear:
alpha = (t - t1) / (t2 - t1)
value = lerp(kf1.value, kf2.value, alpha)
Bezier (per-component):
Solve: t = cubicBezier(alpha, t1, t1+right_time, t2+left_time, t2)
value = cubicBezier(alpha, v1, v1+right_value, v2+left_value, v2)
Rotation: In v5.0+, rotations use inverted X and Y components for internal consistency (exported correctly).
Item display transformations for Minecraft formats.
{
"display": {
"thirdperson_righthand": {
"rotation": [75, 45, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"firstperson_righthand": { /* ... */ },
"gui": { /* ... */ },
"ground": { /* ... */ },
"fixed": { /* ... */ },
"head": { /* ... */ }
}
}Slots: thirdperson_righthand, thirdperson_lefthand, firstperson_righthand, firstperson_lefthand, gui, ground, fixed, head
Organize elements into exportable groups.
{
"collections": [
{
"uuid": "collection-uuid",
"name": "LOD0",
"children": ["element-uuid-1", "element-uuid-2"],
"export_path": "/path/to/export.gltf"
}
]
}The .bbmodel format may be subject to breaking changes. Each file contains a format_version field to identify and handle these changes. This section describes breaking changes only; additions and non-breaking changes are not listed.
| Version | Release Date | Key Changes |
|---|---|---|
| 5.0 | October 12, 2025 (Blockbench 5.0) | Groups/outliner separation; keyframe value direction fixed |
| 4.10 | May 9, 2024 (Blockbench 4.10) | Relative texture paths fixed; name field handling; default texture handling |
| 4.9 | December 2, 2023 (Blockbench 4.9) | Per-texture UV size (formats with per_texture_uv_size feature) |
| 4.5 | Earlier | shade attribute replaced by mirror_uv |
| 3.2 | Earlier | Z-axis rotation sign convention changed |
Groups and Outliner Separation
Groups and outliner hierarchy are now saved as separate fields:
{
"groups": [
{
"uuid": "b147a4cf-3f58-9e94-8f4c-b7b0657c3c56",
"name": "group_name",
"origin": [0, 0, 0],
/* ... group properties ... */
}
],
"outliner": [
{
"uuid": "0396ecb8-33d8-ca4a-d1d2-d756c50123ae",
"isOpen": true,
"children": [
{
"uuid": "b147a4cf-3f58-9e94-8f4c-b7b0657c3c56",
"isOpen": true,
"children": [
"36d606d8-1baa-08f0-e4e6-f70f2f2e3657",
"7594f374-dece-aa7b-e553-4dca3c243b1f"
]
}
]
}
]
}Pre-5.0 format (groups embedded in outliner):
{
"outliner": [
{
"name": "group_name",
"origin": [0, 0, 0],
/* ... all group properties inline ... */
"children": [ /* ... */ ]
}
]
}Keyframe Value Direction Fixed
Animation keyframe values were previously inverted:
- Position keyframes: X-axis values were inverted
- Rotation keyframes: X and Y axis values were inverted
Starting with v5.0, values are stored correctly without inversion.
Migration: When loading pre-5.0 files, apply corrections:
// For position and rotation keyframes
if (keyframe.channel == 'position' || keyframe.channel == 'rotation') {
data_point.x = -data_point.x; // Invert X
}
// For rotation keyframes only
if (keyframe.channel == 'rotation') {
data_point.y = -data_point.y; // Invert Y
}Relative Texture Paths Fixed
Relative paths are now correctly saved relative to the directory containing the .bbmodel file, not relative to the file as if it were a directory.
Pre-4.10:
{
"path": "/project/model.bbmodel",
"relative_path": "/textures/skin.png" // Wrong: treated model.bbmodel as directory
}4.10+:
{
"path": "/project/model.bbmodel",
"relative_path": "textures/skin.png" // Correct: relative to /project/
}Name Field Handling
The name field is now ignored when loading and overwritten with the .bbmodel filename (without extension).
Default Texture Handling
In formats with a default texture, textures are no longer specified per face if they were not explicitly assigned to that face.
Per-Texture UV Size
In generic models and formats with the per_texture_uv_size feature enabled, UV size is now set per texture instead of globally.
Pre-4.9 format (global resolution):
{
"resolution": {
"width": 16,
"height": 16
},
"textures": [
{ "id": "0", /* ... */ },
{ "id": "1", /* ... */ }
]
}4.9+ format (per-texture):
{
"resolution": {
"width": 16, // Default/fallback
"height": 16
},
"textures": [
{
"id": "0",
"uv_width": 32, // Individual texture UV space
"uv_height": 32,
/* ... */
},
{
"id": "1",
"uv_width": 64,
"uv_height": 64,
/* ... */
}
]
}Migration: When UV coordinates are stored per-face, they must be rescaled:
// Old UV space: project.resolution.width × project.resolution.height
// New UV space: texture.uv_width × texture.uv_height
scale_u = texture.uv_width / old_resolution.width;
scale_v = texture.uv_height / old_resolution.height;
face.uv = [u0 * scale_u, v0 * scale_v, u1 * scale_u, v1 * scale_v];Mirror UV Attribute
The shade attribute on elements was replaced by mirror_uv:
// Pre-4.5
{ "shade": false } // Used for UV mirroring in box_uv mode
// 4.5+
{ "mirror_uv": true } // Explicit mirror UV flagZ-Axis Rotation Sign
Z-axis rotation sign convention changed. Pre-3.2 files use inverted Z rotations.
Migration:
if (format_version < 3.2) {
group.rotation[2] *= -1; // Invert Z rotation
}When reading files from different versions, apply the appropriate compatibility transforms:
Version Detection:
if (!model.meta.format_version) {
model.meta.format_version = model.meta.format || "3.0";
}Upgrade Path for Pre-5.0 Files:
- Extract groups from
outliner→groupsarray - Replace group objects with
{ uuid, children } - Apply rotation sign corrections for animations
- Fix Z-axis rotations if pre-3.2
- Handle relative texture paths if pre-4.10
- Rescale UVs if pre-4.9 and using per-texture UV sizes
Blockbench → Standard 3D:
// Blockbench uses Y-up, same as most 3D engines
position = [x, y, z] // No conversion needed
// Rotations: degrees → radians
rotation_rad = [rx, ry, rz] * (PI / 180)Matrix Construction (Column-major):
T = translate(origin)
R = rotateZ(rz) * rotateY(ry) * rotateX(rx)
T_inv = translate(-origin)
LocalMatrix = T * R * T_invQuads must be triangulated for rendering:
Quad [v0, v1, v2, v3] →
Triangle 1: [v0, v1, v2]
Triangle 2: [v0, v2, v3]
Ensure winding order preserves CCW normals.
- Origin: Top-left corner
- Axes: U (right), V (down)
- Range:
[0, texture.uv_width] × [0, texture.uv_height] - Normalization (for GPU): Divide by texture dimensions
UUIDs are lowercase hexadecimal with hyphens:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Example: c8eb7afc-1e80-c3b9-07e9-0e96b2c28979
{
"meta": {
"format_version": "5.0",
"model_format": "free",
"box_uv": false
},
"name": "character",
"resolution": { "width": 64, "height": 64 },
"elements": [
{
"type": "cube",
"uuid": "head-uuid",
"name": "head",
"from": [-4, 24, -4],
"to": [4, 32, 4],
"origin": [0, 24, 0],
"faces": {
"north": { "uv": [0, 0, 8, 8], "texture": 0 }
/* ... other faces ... */
}
}
],
"groups": [
{
"uuid": "body-group-uuid",
"name": "body",
"origin": [0, 24, 0],
"rotation": [0, 0, 0]
}
],
"outliner": [
{
"uuid": "body-group-uuid",
"children": ["head-uuid"]
}
],
"textures": [
{
"uuid": "tex-uuid",
"id": "0",
"name": "skin",
"width": 64,
"height": 64,
"source": "data:image/png;base64,..."
}
],
"animations": [
{
"uuid": "anim-uuid",
"name": "nod",
"loop": "loop",
"length": 1.0,
"animators": {
"head-uuid": {
"name": "head",
"type": "bone",
"keyframes": [
{
"channel": "rotation",
"time": 0.0,
"data_points": [{ "x": 0, "y": 0, "z": 0 }],
"interpolation": "linear"
},
{
"channel": "rotation",
"time": 0.5,
"data_points": [{ "x": 15, "y": 0, "z": 0 }],
"interpolation": "linear"
},
{
"channel": "rotation",
"time": 1.0,
"data_points": [{ "x": 0, "y": 0, "z": 0 }],
"interpolation": "linear"
}
]
}
}
}
]
}- Parse Elements: Create geometry for each element
- Build Hierarchy: Process
outlinerto construct tree - Apply Transforms:
- Start from root
- Accumulate transforms down the tree
- Apply to element vertices/bounds
- Handle Animations: Link animators to scene nodes by UUID
def apply_transform(node, parent_matrix):
# Local transformation
T = translate(node.origin)
R = rotate(node.rotation) # XYZ order
T_inv = translate(-node.origin)
local_matrix = T @ R @ T_inv
# World transformation
world_matrix = parent_matrix @ local_matrix
# Apply to geometry
if node.is_element():
transform_geometry(node.geometry, world_matrix)
# Recurse to children
for child in node.children:
apply_transform(child, world_matrix)For real-time engines (glTF, FBX):
- Sample keyframes at fixed intervals (e.g., 30 FPS)
- Bake transformations for non-linear interpolations
- Convert rotations to quaternions to avoid gimbal lock
- Pack data into animation tracks per-channel
- Rotation Order: Always XYZ, never ZYX
- Version Checks: Apply compatibility transforms for pre-5.0 files
- UUID Resolution: Match animators to nodes, textures to faces
- UV Normalization: Divide by texture dimensions for shader input
- Mesh Winding: Maintain CCW winding during triangulation
- Transform Order: Translate to origin → Rotate → Translate back
- Float Precision: Use sufficient precision for small rotations/scales
- Blockbench Source: https://github.com/JannisX11/blockbench
- Format Codec:
blockbench/js/io/formats/bbmodel.js - GLTF Exporter:
blockbench/js/io/formats/gltf.js - Version History: See
FORMATVconstant (currently5.0)
Document Version: 1.0 Last Updated: 2025-10-30 Specification Coverage: Blockbench Format v3.2–5.0