Getting Started
Installation
Jepia is built with Bun. Make sure you have Bun installed (version >=1.0.0).
# Clone the repository
git clone #.git
cd jepia
# Install dependencies
bun install
Create Your First Add-On
Create a new TypeScript file (e.g., my-addon.ts) and import Jepia:
import { Addon, Item } from 'jepia';
const addon = new Addon('MyAddon', 'My first add-on');
const sword = new Item('Dragon Sword', 'myaddon:dragon_sword');
sword.addTexture('textures/items/dragon_sword.png');
addon.addItem(sword);
await addon.generate('./output');
Run the script with Bun:
bun run my-addon.ts
This will generate a complete add‑on structure in the ./output folder, ready to be imported into Minecraft Bedrock Edition.
Core Concepts
Addon
The Addon class is the main entry point. It automatically creates both a behavior pack and a resource pack, and coordinates component registration across them.
Packs
- Behavior Pack – Contains game logic: items, blocks, entities, recipes, loot tables.
- Resource Pack – Contains assets: textures, models, materials, render controllers, animations, sounds.
Both packs are created automatically when you instantiate an Addon.
Components
Components are the building blocks of your add‑on. Each component corresponds to a Minecraft JSON definition (item, block, entity, texture, model, material, render controller, etc.). Jepia provides a fluent, object‑oriented API for creating and configuring components.
All components are validated against official Minecraft JSON schemas using AJV before file generation.
Rendering Pipeline (Phase 3)
For custom entity visuals, Jepia now supports the full rendering pipeline:
- TextureLayer – A single image with a PBR role (color, normal, MER, overlay, etc.).
- TextureSet – Groups layers into a PBR texture set (
.texture_set.json). - Material – Defines shader behavior (
.materialfile with blend factors, defines, states). - RenderController – Wires geometry, textures, and materials together at runtime using Molang expressions.
- Molang – Type-safe helpers for building Molang expression strings.
Automatic Render Controller Generation (Phase 4)
Jepia can automatically generate render controllers based on an entity's texture layers and UV atlas configuration via addon.generateRenderControllerForEntity(). It handles single‑texture, multi‑variant, and UV atlas entities, producing appropriate render controllers and materials.
ResourcePack File System Integration (Phase 4)
The ResourcePack class now provides file management APIs for materials, render controllers, and PBR texture sets. Use addMaterialFile(), addRenderControllerFile(), addTextureSetFile() and writeFiles() to register assets for automatic writing to disk.
Addon API
The Addon class provides the following methods:
| Method | Description | Returns |
|---|---|---|
constructor(name, description?, version?, minEngineVersion?) |
Creates a new add‑on instance with auto‑generated behavior and resource packs. | Addon |
getName() |
Returns the add‑on name. | string |
getDescription() |
Returns the add‑on description. | string |
getVersion() |
Returns the version tuple. | [number, number, number] |
getMinEngineVersion() |
Returns the minimum engine version tuple. | [number, number, number] |
getBehaviorPack() |
Returns the underlying behavior pack instance. | BehaviorPack |
getResourcePack() |
Returns the underlying resource pack instance. | ResourcePack |
addItem(item) |
Adds an Item component to the add‑on. |
this (fluent) |
addBlock(blockOrId, blockData?, resourceData?) |
Adds a block definition. Accepts either a Block component instance, or a block identifier string with behavior data. Optional resource data for the resource pack. | this |
addEntity(entityOrId, entityData?, clientEntityData?) |
Adds an entity definition. Accepts either an Entity component instance, or an entity identifier string with behavior data. Optional client entity data for the resource pack. | this |
addRecipe(recipe) |
Adds a Recipe component (or legacy (id, data) pair) to the behavior pack. Writes to recipes/. |
this |
addLootTable(lootTable) |
Adds a LootTable component (or legacy (id, data) pair) to the behavior pack. Writes to loot_tables/. |
this |
addSpawnRule(spawnRule) |
Adds a SpawnRule component (or legacy (id, data) pair) to the behavior pack. Writes to spawn_rules/. |
this |
addTradeTable(tradeTable) |
Adds a TradeTable component (or legacy (id, data) pair) to the behavior pack. Writes to trading/. |
this |
addBiome(biome) |
Adds a Biome component (or legacy (id, data) pair) to the behavior pack. Writes to biomes/. |
this |
addFeature(feature) |
Adds a Feature component (or legacy (id, data) pair) to the behavior pack. Writes to features/. |
this |
addFeatureRule(rule) |
Adds a FeatureRule component (or legacy (id, data) pair) to the behavior pack. Writes to feature_rules/. |
this |
addDimension(dimension) |
Adds a Dimension component (or legacy (id, data) pair) to the behavior pack. Writes to dimensions/. |
this |
generate(outputPath) |
Generates the complete add‑on file structure at the given path. | Promise<void> |
getComponentCount() |
Returns the total number of registered components. | number |
getSummary() |
Returns an object with add‑on metadata and statistics. | object |
generateRenderControllerForEntity(entity, arrayName?) |
Automatically generates a RenderController from an entity's texture layers and UV atlas configuration. Returns an object with rc (RenderController) and optional material (for UV atlas). |
{ rc: RenderController; material?: Material } |
ResourcePack API
The ResourcePack class (accessible via addon.getResourcePack()) manages asset files for the resource pack. New file management methods allow registering materials, render controllers, and PBR texture sets for automatic writing.
| Method | Description | Returns |
|---|---|---|
addMaterialFile(localName, material) |
Registers a Material to be written as resource_pack/materials/<localName>.material. |
this |
getMaterialFile(localName) |
Retrieves a registered material by local name. | Material | undefined |
hasMaterialFile(localName) |
Checks if a material file is registered. | boolean |
getMaterialFiles() |
Returns all registered material file entries. | Array<{ localName: string, material: Material }> |
addRenderControllerFile(localName, renderController) |
Registers a RenderController to be written as resource_pack/render_controllers/<localName>.render_controllers.json. |
this |
getRenderControllerFile(localName) |
Retrieves a registered render controller by local name. | RenderController | undefined |
hasRenderControllerFile(localName) |
Checks if a render controller file is registered. | boolean |
getRenderControllerFiles() |
Returns all registered render controller file entries. | Array<{ localName: string, renderController: RenderController }> |
addTextureSetFile(textureDir, textureBaseName, textureSet) |
Registers a TextureSet to be written as <textureDir>/<textureBaseName>.texture_set.json next to the colour image. |
this |
getTextureSetFile(textureDir, textureBaseName) |
Retrieves a registered TextureSet by output location. | TextureSet | undefined |
hasTextureSetFile(textureDir, textureBaseName) |
Checks if a texture set file is registered. | boolean |
getTextureSetFiles() |
Returns all registered texture set file entries. | Array<{ dir: string, baseName: string, textureSet: TextureSet }> |
writeFiles(resourcePackPath) |
Writes all registered special resource files to disk. Returns counts of files written. | Promise<{ materialsWritten: number, renderControllersWritten: number, textureSetsWritten: number }> |
Component API
Item
import { Item } from 'jepia';
const ruby = new Item('Ruby', 'myaddon:ruby', 'A precious gemstone');
ruby
.setMaxStackSize(64)
.setMenuCategory('items')
.addComponent('minecraft:durability', { max_durability: 100 })
.addTexture('textures/items/ruby.png');
addon.addItem(ruby);
Key methods: setMaxStackSize, setMenuCategory, addComponent, addTexture, addModel.
addComponent() — durability, food, combat, equipment, and more.Block
Blocks can be added via the add‑on's addBlock method using either a Block component instance or a block identifier string with behavior data. The Block component API is under development; currently the string‑based API is recommended.
addon.addBlock('myaddon:ruby_ore', {
display_name: 'Ruby Ore',
material: 'stone',
hardness: 3,
blast_resistance: 3,
});
You can also provide optional resource data for the resource pack:
addon.addBlock('myaddon:ruby_ore', behaviorData, resourceData);
addComponent() — geometry, destruction, lighting, redstone, and more.Entity
Entities can be added using either an Entity component instance or a raw JSON definition. The component API provides better integration with the resource pack (client entity generation) and the rendering pipeline.
// Using raw JSON (legacy API)
addon.addEntity('myaddon:ruby_golem', {
format_version: '1.21.40',
'minecraft:entity': {
description: {
identifier: 'myaddon:ruby_golem',
is_summonable: true,
is_spawnable: true,
},
components: {
'minecraft:physics': {},
'minecraft:movement': { value: 0.25 },
'minecraft:health': { value: 20, max: 20 },
},
},
});
// Using Entity component (recommended)
import { Entity } from 'jepia';
const mob = new Entity('Ruby Golem', 'myaddon:ruby_golem');
mob.addComponent('minecraft:health', { value: 20 });
addon.addEntity(mob);
The Entity component also supports attaching a Material for full pipeline integration:
import { Entity, Material, VanillaMaterials } from 'jepia';
const mob = new Entity('Ruby Golem', 'myaddon:ruby_golem');
const mat = Material.fromVanilla('myaddon:golem_mat', VanillaMaterials.ENTITY_ALPHATEST);
mob.setMaterial(mat);
addon.addEntity(mob);
You can also attach a RenderController to customize how the entity is rendered at runtime (geometry, textures, materials, part visibility, color tinting, UV animation). The controller is automatically written to resource_pack/render_controllers/ when the add‑on is generated.
import { Entity, RenderController, Molang } from 'jepia';
const mob = new Entity('Robot', 'myaddon:robot');
const rc = new RenderController('Robot Controller', 'myaddon:robot')
.setGeometry('Geometry.default')
.setMaterials([{ '*': 'Material.default' }])
.setTextures(['Texture.default']);
mob.setRenderController(rc);
addon.addEntity(mob);
Texture Layer Management
Entities support multiple texture layers for variant skins, overlays, and PBR texture sets. Use addTextureLayer(), removeTextureLayer(), getTextureLayers(), and clearTextureLayers() to manage layers.
UV Atlas Variables
For texture atlasing, set UV atlas variables with setUvAtlasVariables(). This automatically injects Molang pre‑animation scripts into the client entity, enabling uv_anim render controllers.
Script Animation
Control animation playback via setScriptAnimate(), getScriptAnimate(), and clearScriptAnimate(). Entries can be unconditional animation short names or conditional Molang expressions.
Automatic Render Controller Generation
The add‑on's generateRenderControllerForEntity() method automatically creates an appropriate render controller based on the entity's texture layers and UV atlas configuration. See the Addon API table for details.
addComponent() — health, movement, navigation, combat, AI goals, and more.Texture & Model
Textures and models are automatically registered when you attach them to items, blocks, or entities. You can also create standalone texture and model components for advanced scenarios.
import { Texture, Model } from 'jepia';
const tex = new Texture('myaddon:special_texture', './assets/special.png');
const model = new Model('myaddon:special_model', './models/special.geo.json');
For PBR and multi-layer textures, use TextureLayer and TextureSet instead.
TextureLayer
A TextureLayer represents a single image with a PBR role. Layers are the building blocks for TextureSet and render controller texture arrays.
| Layer Type | Description |
|---|---|
"color" | RGB(A) albedo/diffuse — required by TextureSet. Default type. |
"normal" | 3-channel normal map. Mutually exclusive with "heightmap". |
"mer" | Metalness/Emissive/Roughness packed in R/G/B. Mutually exclusive with "mers". |
"mers" | Metalness/Emissive/Roughness/Subsurface (Vibrant Visuals only). Mutually exclusive with "mer". |
"heightmap" | Single-channel depth/parallax map. Mutually exclusive with "normal". |
"overlay" | Extra composited layer (e.g. llama decor, glow effects). |
| Blending Mode | Description |
|---|---|
"opaque" | No blending; pixels fully written (default for color/normal/mer layers). |
"alpha" | Standard alpha-transparency compositing. |
"additive" | Source added to destination — useful for glow/emissive overlays. |
"multiply" | Source × destination — darkening effect. |
"screen" | Inverse multiply — lightening effect. |
import { TextureLayer } from 'jepia';
// Basic color layer (default type)
const color = new TextureLayer('Creamy', 'myaddon:llama_creamy',
'textures/entity/llama_creamy.png');
// Normal map layer
const normal = new TextureLayer('Stone Normal', 'myaddon:stone_normal',
'textures/blocks/stone_normal.png', 'normal');
// Additive glow overlay
const glow = new TextureLayer('Ghost Glow', 'myaddon:ghost_glow',
'textures/entity/ghost_glow.png')
.setBlending('additive');
// Atlas sub-region (top-left quadrant of a 2x2 atlas)
const atlasLayer = new TextureLayer('Mob 0', 'myaddon:mob_0',
'textures/entity/mob_atlas.png')
.setUvOffset([0, 0])
.setUvScale([0.5, 0.5]);
| Method | Description | Returns |
|---|---|---|
constructor(displayName, id, imagePath?, type?) | Creates a layer. type defaults to "color". | TextureLayer |
setPath(path) | Sets the image file path. | this |
getPath() | Gets the image file path. | string |
setType(type) | Sets the PBR layer type. | this |
getType() | Gets the PBR layer type. | TextureLayerType |
setBlending(mode) | Sets the blending mode for compositing. | this |
getBlending() | Gets the blending mode. | TextureLayerBlending |
setUvOffset([u, v]) | Sets the UV offset for atlas sub-regions. | this |
setUvScale([u, v]) | Sets the UV scale for atlas sub-regions. | this |
validate() | Validates the layer configuration. | { valid, errors } |
toJSON() | Serializes to JSON. | object |
TextureSet
A TextureSet groups PBR texture layers into a .texture_set.json file (format_version 1.16.100 / 1.21.30). It enforces mutual exclusion rules (normal vs heightmap, mer vs mers). Each slot accepts either a TextureLayer instance or a raw value (texture name string, uniform numeric array, or hex string).
import { TextureLayer, TextureSet } from 'jepia';
// Create texture layers
const color = new TextureLayer('Base', 'myaddon:base_color', 'textures/entity/base.png');
const normal = new TextureLayer('Normal', 'myaddon:base_normal', 'textures/entity/base_normal.png', 'normal');
const mer = new TextureLayer('MER', 'myaddon:base_mer', 'textures/entity/base_mer.png', 'mer');
// Create texture set and assign layers
const set = new TextureSet('Base PBR', 'myaddon:base_pbr');
set.setColor(color)
.setNormal(normal)
.setMer(mer);
// Alternative: assign raw values
const set2 = new TextureSet('Uniform PBR', 'myaddon:uniform_pbr');
set2.setColor([255, 0, 0, 255]) // RGBA array
.setHeightmap(128); // uniform height value
| Method | Description | Returns |
|---|---|---|
constructor(displayName, id, description?) | Creates an empty texture set. | TextureSet |
setColor(layerOrValue) | Sets the color (albedo) layer. Accepts TextureLayer, texture name string, RGBA array, or hex string. | this |
getColor() | Gets the color layer. | TextureLayer | ColorValue | undefined |
clearColor() | Removes the color layer. | this |
setNormal(layerOrString) | Sets the normal map layer. Accepts TextureLayer or texture name string. | this |
getNormal() | Gets the normal layer. | TextureLayer | string | undefined |
clearNormal() | Removes the normal layer. | this |
setMer(layerOrValue) | Sets the MER (metalness/emissive/roughness) layer. Accepts TextureLayer, texture name string, RGB array, or hex string. | this |
getMer() | Gets the MER layer. | TextureLayer | MerValue | undefined |
clearMer() | Removes the MER layer. | this |
setMers(layerOrString) | Sets the MERS (metalness/emissive/roughness/subsurface) layer (Vibrant Visuals). Accepts TextureLayer or texture name string only. | this |
getMers() | Gets the MERS layer. | TextureLayer | string | undefined |
clearMers() | Removes the MERS layer. | this |
setHeightmap(layerOrValue) | Sets the heightmap layer. Accepts TextureLayer, texture name string, or uniform numeric value (0–255). | this |
getHeightmap() | Gets the heightmap layer. | TextureLayer | HeightmapValue | undefined |
clearHeightmap() | Removes the heightmap layer. | this |
hasColor() | Returns whether a color layer is set. | boolean |
hasNormal() | Returns whether a normal layer is set. | boolean |
hasMer() | Returns whether a MER layer is set. | boolean |
hasMers() | Returns whether a MERS layer is set. | boolean |
hasHeightmap() | Returns whether a heightmap layer is set. | boolean |
isVibrantVisuals() | Returns true if MERS layer is set (indicates format_version 1.21.30). | boolean |
validate() | Validates mutual exclusion rules and layer consistency. | { valid, errors } |
validateAgainstSchema() | Validates against Minecraft JSON schema. | { valid, errors } |
toTextureSetJSON() | Serializes to .texture_set.json format. | object |
toJSON() | Serializes to Jepia internal format. | object |
getSummary() | Returns a human-readable summary. | string |
Material
The Material component generates a Minecraft .material file in resource_pack/materials/. Materials define how the shader renders an entity: transparency, blending, emissive glow, etc.
Materials use inheritance: a custom material inherits from a vanilla or custom parent material. The key in the output JSON is "<name>:<parent>".
import { Material, MaterialDefine, MaterialState, BlendFactor, VanillaMaterials } from 'jepia';
// Inherit from a vanilla material
const mat = new Material('Glow Material', 'myaddon:glow')
.setParent(VanillaMaterials.ENTITY_ALPHABLEND)
.addDefine(MaterialDefine.USE_EMISSIVE)
.addState(MaterialState.Blending)
.setBlendSrc(BlendFactor.SourceAlpha)
.setBlendDst(BlendFactor.One);
// Convenience: create from vanilla shorthand
const mat2 = Material.fromVanilla('myaddon:cutout', VanillaMaterials.ENTITY_ALPHATEST);
VanillaMaterials presets
The VanillaMaterials class provides 25+ named presets matching vanilla Minecraft materials:
VanillaMaterials.ENTITY // "entity"
VanillaMaterials.ENTITY_ALPHATEST // "entity_alphatest"
VanillaMaterials.ENTITY_ALPHABLEND // "entity_alphablend"
VanillaMaterials.ENTITY_CHANGE_COLOR // "entity_change_color"
VanillaMaterials.ENTITY_EMISSIVE // "entity_emissive"
VanillaMaterials.PARTICLES // "particles"
// ... and many more — see VanillaMaterials.ALL for the full list
Material methods
| Method | Description | Returns |
|---|---|---|
constructor(displayName, id, description?) | Creates a new material component. | Material |
Material.fromVanilla(id, vanillaName, displayName?) | Static factory: creates a material inheriting from a vanilla parent. | Material |
setParent(parent) | Sets the parent material (vanilla name or custom id). | this |
getParent() | Gets the parent material name. | string | undefined |
clearParent() | Removes the parent (makes this a root material). | this |
addDefine(define) | Adds a shader define to +defines. | this |
removeDefine(define) | Schedules a define for removal (-defines). | this |
addState(state) | Adds a renderer state to +states. | this |
removeState(state) | Schedules a state for removal (-states). | this |
setBlendFactors(src, dst) | Sets color-channel blend src and dst in one call. | this |
setBlendSrc(factor) | Sets the source blend factor (color channels). | this |
setBlendDst(factor) | Sets the destination blend factor (color channels). | this |
setAlphaSrc(factor) | Sets the source blend factor (alpha channel). | this |
setAlphaDst(factor) | Sets the destination blend factor (alpha channel). | this |
getMaterialKey() | Returns the serialized key ("name:parent" or "name"). | string |
validate() | Validates defines/states/blend factors, including AJV schema check. | { valid, errors } |
toJSON() | Serializes to complete .material file JSON. | MaterialFileJSON |
toDefinitionJSON() | Serializes just the definition block (for multi-material files). | MaterialDefinitionJSON |
BlendFactor enum
Controls how source and destination colors are combined when MaterialState.Blending is active:
BlendFactor.Zero // multiplies by zero
BlendFactor.One // passes through unchanged
BlendFactor.SourceColor // uses source color value
BlendFactor.DestColor // uses destination color value
BlendFactor.SourceAlpha // uses source alpha
BlendFactor.DestAlpha // uses destination alpha
BlendFactor.OneMinusSrcAlpha // 1 minus source alpha (common for standard transparency)
MaterialDefine enum
MaterialDefine.ALPHA_TEST // cutout transparency
MaterialDefine.GLINT // enchantment-glint effect
MaterialDefine.USE_EMISSIVE // emissive texture support
MaterialDefine.USE_OVERLAY // overlay tinting support
MaterialDefine.TINTED_ALPHA_TEST // tinted alpha test
MaterialState enum
MaterialState.Blending // enable alpha blending
MaterialState.DisableCulling // render both faces (no backface culling)
MaterialState.DisableDepthWrite // do not write to depth buffer
RenderController
A RenderController defines how an entity is visually rendered at runtime: which geometry, textures, and materials to use; how to select them dynamically via Molang expressions; part visibility; UV animation; and color tinting.
Files are written to resource_pack/render_controllers/ with format_version "1.8.0".
import { RenderController } from 'jepia';
// Simple single-texture entity
const rc = new RenderController('Robot Controller', 'myaddon:robot')
.setGeometry('Geometry.default')
.setMaterials([{ '*': 'Material.default' }])
.setTextures(['Texture.default']);
// Multi-variant entity (ocelot pattern — texture array + q.variant)
const rc2 = new RenderController('Ocelot Controller', 'myaddon:ocelot')
.setGeometry('Geometry.default')
.setMaterials([{ '*': 'Material.default' }])
.addTextureArray('skins', ['Texture.wild', 'Texture.black', 'Texture.red'])
.setTextures(['Array.skins[q.variant]']);
// Static factory from TextureLayer instances
import { TextureLayer, RenderController } from 'jepia';
const layers = [wildLayer, blackLayer, redLayer];
const rc3 = RenderController.fromTextureLayers('Ocelot RC', 'myaddon:ocelot', layers, 'skins');
RenderController methods
| Method | Description | Returns |
|---|---|---|
constructor(displayName, id, description?) | Creates a new render controller. | RenderController |
RenderController.fromTextureLayers(displayName, id, layers, arrayName?) | Static factory: builds a controller pre-configured for one or more TextureLayer variants. Uses a texture array when N>1 layers, Texture.default for 1 layer. | RenderController |
getControllerKey() | Returns the controller key used in the JSON map (e.g. "controller.render.myaddon_robot"). | string |
setGeometry(expr) | Sets the geometry Molang expression (e.g. "Geometry.default" or "Array.geos[q.is_sheared]"). | this |
setMaterials(mappings) | Sets the materials array — ordered list of { bonePattern: molangExpr } objects. Use "*" to match all bones. | this |
setTextures(exprs) | Sets the textures array — ordered list of Molang texture references. Index 0 is the base texture. | this |
addTextureArray(name, textures) | Adds a named texture array (arrays.textures). Name is auto-prefixed with "Array." if needed. Elements must follow "Texture.<name>" convention. | this |
addMaterialArray(name, materials) | Adds a named material array (arrays.materials). | this |
addGeometryArray(name, geometries) | Adds a named geometry array (arrays.geometries). | this |
removeTextureArray(name) | Removes a named texture array. | this |
removeMaterialArray(name) | Removes a named material array. | this |
removeGeometryArray(name) | Removes a named geometry array. | this |
addTextureArrayFromLayers(arrayName, layers) | Populates a texture array from TextureLayer instances, generating "Texture.<name>" references automatically. | this |
setPartVisibility(entries) | Sets part visibility — ordered list of { partPattern: molangCondition }. Supports * wildcards. | this |
setColor(color) | Sets the base color tint (RGBA Molang expressions). | this |
setOverlayColor(color) | Sets the overlay color (e.g. invulnerability tint). | this |
setOnFireColor(color) | Sets the color applied when the entity is on fire. | this |
setIsHurtColor(color) | Sets the color applied when the entity takes damage. | this |
setUvAnim(offset, scale) | Sets UV animation for atlas scrolling/scaling. Both offset and scale are [U, V] Molang expression pairs. | this |
setFilterLighting(value) | When true, lighting is converted to grayscale before applying. | this |
setIgnoreLighting(value) | When true, entity renders at full brightness regardless of ambient light. | this |
setLightColorMultiplier(expr) | Sets a Molang expression controlling ambient light blending. | this |
setRebuildAnimationMatrices(value) | When true, animation matrices are rebuilt every frame. | this |
validate() | Validates geometry, array references, element naming conventions, and AJV schema. | { valid, errors } |
toJSON() | Serializes to the full .render_controllers.json file format. | RenderControllerFileJSON |
toDefinitionJSON() | Serializes just the definition block (for bundling multiple controllers in one file). | RenderControllerDefinitionJSON |
getSummary() | Returns a human-readable summary string. | string |
Part visibility
// Hide all bones by default, then show selectively
rc.setPartVisibility([
{ '*': 0 }, // hide everything
{ 'body': 'query.has_armor_slot(1)' }, // show body when chest armor present
{ 'leg*': '!query.is_sleeping' }, // show legs when not sleeping
{ 'chest*': 'query.is_chested' }, // show chest parts when carrying chest
]);
Color tinting
// Hurt flash (blue tint at 50% alpha)
rc.setIsHurtColor({ r: '0.0', g: '0.0', b: '1.0', a: '0.5' });
// Suppress fire overlay on fireballs
rc.setOnFireColor({ r: '0.0', g: '0.0', b: '0.0', a: '0.0' });
// Overlay driven by Molang
rc.setOverlayColor({ r: '1.0', g: '1.0', b: '1.0', a: 'v.overlay_alpha' });
UV animation (atlas textures)
import { Molang } from 'jepia';
const uvAnim = Molang.uvAtlas();
// → { offset: ["v.offset_x", "v.offset_y"], scale: ["v.scale_x", "v.scale_y"] }
rc.setUvAnim(uvAnim.offset, uvAnim.scale);
Static element validation
RenderController.isValidTextureElement('Texture.default') // true
RenderController.isValidMaterialElement('Material.default') // true
RenderController.isValidGeometryElement('Geometry.default') // true
TextureAtlas
The TextureAtlas component packs multiple textures into a single atlas image, reducing draw calls and memory usage. Supports both uniform grid layouts and automatic bin-packing via shelf or maxrects strategies.
import { TextureAtlas, Texture, Entity } from 'jepia';
// Create a 1024x1024 packed atlas with 8px padding
const atlas = new TextureAtlas('Mob Atlas', 'mymod:mob_atlas', 1024, 1024, 'packed', 8);
// Add texture entries with their pixel dimensions
const skin1 = new Texture('Skin 1', 'mymod:skin1', 'textures/entity/skin1.png');
const skin2 = new Texture('Skin 2', 'mymod:skin2', 'textures/entity/skin2.png');
atlas.addEntry(skin1, 256, 256);
atlas.addEntry(skin2, 256, 512);
// Pack and get UV coordinates
const uvMap = await atlas.pack('shelf'); // or 'maxrects'
// Apply UV coordinates to an entity (sets up uv_anim render controller)
const mob = new Entity('Multi-skin Mob', 'mymod:mob');
await atlas.applyToEntity(mob);
// Reports
const opt = atlas.getOptimizationReport();
// { originalArea, atlasArea, usedArea, wastedArea, efficiency, textureCount, atlasSize }
const mem = atlas.getMemoryReport();
// { originalMemory, atlasMemory, savedMemory, reductionPercentage }
TextureAtlas methods
| Method | Description | Returns |
|---|---|---|
constructor(displayName, id, width, height, mode?, padding?) | Creates an atlas. mode is "grid" or "packed" (default). padding defaults to 8. | TextureAtlas |
setGridLayout(cols, rows) | Sets a uniform grid layout (for grid mode). | this |
getGridLayout() | Gets the grid layout dimensions. | { cols, rows } | undefined |
addEntry(texture, width, height) | Adds a texture to be packed into the atlas. | this |
getEntries() | Returns all atlas entries. | AtlasEntry[] |
pack(strategy?) | Packs all entries using the given strategy ("shelf" or "maxrects"). Returns UV coordinate map. | Promise<Map<Texture, UvCoordinates>> |
applyToEntity(entity, uvArrayName?) | Packs the atlas and applies UV coordinates to an entity, setting up uv_anim render controller variables. | Promise<void> |
applyToEntityRaw(entity, uvMap) | Applies pre-computed UV coordinates to an entity without re-packing. | void |
getOptimizationReport() | Returns packing efficiency metrics. | AtlasOptimizationReport |
getMemoryReport() | Returns memory usage comparison (original vs atlas). | AtlasMemoryReport |
toJSON() | Serializes to JSON. | object |
AtlasPacker (standalone utility)
The AtlasPacker can be used independently for bin-packing rectangles into a fixed-size atlas.
import { AtlasPacker } from 'jepia';
const packer = new AtlasPacker(1024, 1024, 4); // width, height, padding
const rects = [
{ id: 'a', width: 256, height: 256 },
{ id: 'b', width: 128, height: 512 },
{ id: 'c', width: 64, height: 64 },
];
const result = packer.pack(rects, 'maxrects');
// Map<TextureData, { x, y, width, height }>
| Strategy | Description | Best for |
|---|---|---|
"shelf" | Sorts by height, places textures in horizontal rows. Fast and predictable. | Uniform or similar-sized textures. |
"maxrects" | Best short-side fit into free rectangles. Better space utilization. | Mixed sizes, maximizing efficiency. |
Recipe
Generates crafting recipe JSON files in behavior_pack/recipes/. Supports shaped crafting, shapeless crafting, furnace smelting, and brewing. Use one of the set* methods to define the recipe type, then pass the instance to addon.addRecipe().
| Method | Description |
|---|---|
setShaped(config) | 3×3 (or smaller) crafting table recipe. Config: pattern, key, result, optional tags and assumeSymmetry. |
setShapeless(config) | Order-independent crafting recipe. Config: ingredients, result, optional tags. |
setFurnace(config) | Furnace/smoker/campfire smelting. Config: input, output, optional tags (default: ["furnace"]). |
setBrewing(config) | Brewing stand recipe. Config: input, reagent, output, optional tags (default: ["brewing_stand"]). |
getRecipeType() | Returns the configured recipe type string, or undefined if not yet set. |
import { Recipe } from 'jepia';
// Shaped crafting
const pickaxe = new Recipe('Iron Pickaxe', 'myaddon:iron_pickaxe');
pickaxe.setShaped({
pattern: ['III', ' S ', ' S '],
key: {
I: { item: 'minecraft:iron_ingot' },
S: { item: 'minecraft:stick' }
},
result: { item: 'myaddon:iron_pickaxe', count: 1 }
});
addon.addRecipe(pickaxe);
// Furnace smelting
const smelt = new Recipe('Smelt Ore', 'myaddon:smelt_ore');
smelt.setFurnace({
input: { item: 'myaddon:raw_ore' },
output: 'myaddon:ingot',
tags: ['furnace', 'smoker', 'blast_furnace']
});
addon.addRecipe(smelt);
// Brewing
const brew = new Recipe('Brew Speed Potion', 'myaddon:brew_speed');
brew.setBrewing({
input: 'minecraft:potion',
reagent: 'myaddon:speed_herb',
output: 'myaddon:speed_potion'
});
addon.addRecipe(brew);
LootTable
Generates loot table JSON files in behavior_pack/loot_tables/. Add one or more pools — each pool draws a number of items from its entry list. Attach to entities via the minecraft:loot component, or to blocks via minecraft:loot.
| Method | Description |
|---|---|
addPool(pool) | Adds a loot pool. Pool has rolls (number or { min, max }), entries array, optional bonus_rolls, functions, and conditions. |
getPools() | Returns a copy of all configured pools. |
Entry types: "item" (requires name), "loot_table" (requires name path), "empty" (no drop). All entries accept weight, functions, and conditions.
import { LootTable } from 'jepia';
const golemLoot = new LootTable('Iron Golem Drops', 'myaddon:iron_golem');
// Always drop some iron
golemLoot.addPool({
rolls: { min: 3, max: 5 },
entries: [
{ type: 'item', name: 'minecraft:iron_ingot', weight: 1 }
]
});
// Chance at a rare drop
golemLoot.addPool({
rolls: 1,
entries: [
{ type: 'item', name: 'myaddon:golem_core', weight: 1 },
{ type: 'empty', weight: 4 }
]
});
addon.addLootTable(golemLoot);
SpawnRule
Generates spawn rule JSON files in behavior_pack/spawn_rules/. Controls where and when an entity can naturally spawn. Each condition block groups multiple minecraft:* spawn conditions that must all pass.
| Method | Description |
|---|---|
setPopulationControl(control) | Sets the spawn population category: "animal", "monster", "water_animal", "ambient", etc. Default: "animal". |
addCondition(condition) | Adds a spawn condition block. Each object may contain any number of minecraft:* conditions (biome filter, brightness filter, weight, etc.). |
import { SpawnRule } from 'jepia';
const rule = new SpawnRule('Forest Critter Spawning', 'myaddon:forest_critter');
rule
.setPopulationControl('animal')
.addCondition({
'minecraft:spawns_on_surface': {},
'minecraft:brightness_filter': { min: 7, max: 15, adjust_for_weather: true },
'minecraft:weight': { default: 80 },
'minecraft:biome_filter': { test: 'has_biome_tag', value: 'forest' }
});
addon.addSpawnRule(rule);
Common condition keys: minecraft:weight, minecraft:biome_filter, minecraft:brightness_filter, minecraft:difficulty_filter, minecraft:height_filter, minecraft:spawns_on_surface, minecraft:spawns_underground, minecraft:spawns_underwater.
TradeTable
Generates trade table JSON files in behavior_pack/trading/. Tiers unlock as the merchant gains XP. Reference the file in an entity's minecraft:economy_trade_table component.
| Method | Description |
|---|---|
addTier(tier) | Adds a merchant tier. Tier has optional total_exp_required, and either a trades array or a groups array (for random selection). |
getTiers() | Returns a copy of all configured tiers. |
Each trade has wants and gives arrays of TradeItem (item, optional quantity or { min, max }, optional price_multiplier). Optional: max_uses (default 12), reward_exp (default true), trader_exp.
import { TradeTable } from 'jepia';
const trades = new TradeTable('Scrap Merchant', 'myaddon:scrap_merchant');
// Novice tier — always available
trades.addTier({
total_exp_required: 0,
trades: [
{
wants: [{ item: 'minecraft:emerald', quantity: 1 }],
gives: [{ item: 'myaddon:composite_ingot', quantity: 2 }],
max_uses: 8,
reward_exp: true,
trader_exp: 1
}
]
});
// Expert tier — unlocks after earning XP
trades.addTier({
total_exp_required: 70,
groups: [
{
num_to_select: 1,
trades: [
{
wants: [{ item: 'minecraft:emerald', quantity: 5 }],
gives: [{ item: 'myaddon:powered_drill', quantity: 1 }]
},
{
wants: [{ item: 'minecraft:emerald', quantity: 4 }],
gives: [{ item: 'myaddon:electric_motor', quantity: 3 }]
}
]
}
]
});
addon.addTradeTable(trades);
Biome
Generates biome JSON files in behavior_pack/biomes/ (format_version 1.21.110). Biomes define climate, surface materials, and generation tags for a region of the world.
| Method | Description |
|---|---|
setClimate(climate) | Sets temperature, downfall, and snow accumulation. |
setSurfaceBuilder(type, materials?) | Sets the surface builder type (e.g. "minecraft:overworld") and optional material layers. |
addTag(tag) | Adds a biome tag (e.g. "forest", "cold"). Duplicates are ignored. |
setCreatureSpawnProbability(p) | Sets mob spawn probability at chunk generation (0–0.75). |
setHumid(bool) | When true, fire cannot spread in this biome. |
setComponent(name, value) | Sets an arbitrary extra component (e.g. minecraft:mountain_parameters). |
import { Biome } from 'jepia';
const crystalForest = new Biome('Crystal Forest', 'myaddon:crystal_forest');
crystalForest
.setClimate({ temperature: 0.4, downfall: 0.5 })
.setSurfaceBuilder('minecraft:overworld', {
top_material: 'myaddon:crystal_grass',
mid_material: 'minecraft:dirt',
sea_material: 'minecraft:water',
})
.addTag('forest')
.addTag('cold')
.setCreatureSpawnProbability(0.2)
.setHumid(true);
addon.addBiome(crystalForest);
Feature
Generates feature JSON files in behavior_pack/features/ (format_version 1.13.0). Features are world generation elements such as ore veins, trees, plants, and geodes. Feature rules control when and where they are placed.
| Method | Description |
|---|---|
setType(featureType) | Sets the feature type root key (e.g. "minecraft:ore_feature", "minecraft:tree_feature"). |
setData(data) | Sets feature-specific configuration fields. Fields depend on the feature type. |
Supported types include: minecraft:ore_feature, minecraft:tree_feature, minecraft:scatter_feature, minecraft:geode_feature, minecraft:single_block_feature, minecraft:aggregate_feature, minecraft:weighted_random_feature, minecraft:growing_plant_feature, and more.
import { Feature } from 'jepia';
// Ore feature
const crystalOre = new Feature('Crystal Ore', 'myaddon:crystal_ore_feature');
crystalOre
.setType('minecraft:ore_feature')
.setData({
count: 6,
replace_rules: [
{ places_block: 'myaddon:crystal_ore', may_replace: ['minecraft:stone', 'minecraft:deepslate'] },
],
});
addon.addFeature(crystalOre);
FeatureRule
Generates feature rule JSON files in behavior_pack/feature_rules/ (format_version 1.13.0). Feature rules attach features to biomes and control the placement pass and scatter distribution.
| Method | Description |
|---|---|
setPlacesFeature(id) | Sets the feature identifier this rule places. |
addBiomeFilter(filter) | Adds a biome filter test ({ test, operator, value }). Most common: has_biome_tag. |
setPlacementPass(pass) | Sets the generation pass: pregeneration_pass, feature_placement_pass, after_surface_pass, or final_pass. |
setDistribution(dist) | Sets scatter distribution — iterations, scatter_chance, and x/y/z coordinate distributions. |
import { FeatureRule } from 'jepia';
const crystalOreRule = new FeatureRule('Crystal Ore Rule', 'myaddon:crystal_ore_overworld');
crystalOreRule
.setPlacesFeature('myaddon:crystal_ore_feature')
.addBiomeFilter({ test: 'has_biome_tag', operator: '==', value: 'overworld' })
.setPlacementPass('feature_placement_pass')
.setDistribution({
iterations: 8,
scatter_chance: { numerator: 2, denominator: 3 },
x: { distribution: 'uniform', extent: [0, 16] },
y: { distribution: 'uniform', extent: [0, 64] },
z: { distribution: 'uniform', extent: [0, 16] },
});
addon.addFeatureRule(crystalOreRule);
Dimension
Generates dimension JSON files in behavior_pack/dimensions/ (format_version 1.20.0). Dimensions define a custom dimension's vertical bounds and world generator type.
| Method | Description |
|---|---|
setBounds(min, max) | Sets the accessible Y range (minecraft:dimension_bounds). Blocks outside this range become inaccessible voids. |
setGeneratorType(type) | Sets the generator: "overworld", "nether", "the_end", or a custom string. |
setComponent(name, value) | Sets an arbitrary extra component. |
import { Dimension } from 'jepia';
const crystalVoid = new Dimension('Crystal Void', 'myaddon:crystal_void');
crystalVoid
.setBounds(0, 256)
.setGeneratorType('overworld');
addon.addDimension(crystalVoid);
Molang Utilities
The Molang class provides static helpers for building type-safe Molang expression strings used in render controllers, animations, and particle definitions. The MolangQuery constant object provides named constants for common query properties.
import { Molang, MolangQuery } from 'jepia';
// Query expressions
Molang.query('variant') // → "q.variant"
Molang.query('is_invisible') // → "q.is_invisible"
// Query with arguments
Molang.queryCall('has_armor_slot', 1) // → "q.has_armor_slot(1)"
Molang.queryCall('armor_color_slot', 1, 0) // → "q.armor_color_slot(1, 0)"
// Variables
Molang.variable('decor_index') // → "v.decor_index"
Molang.variable('offset_x') // → "v.offset_x"
// Entity property query (shapeshifter pattern)
Molang.property('minecraft:shape') // → "q.property('minecraft:shape')"
// Array access
Molang.arrayIndex('Array.skins', Molang.query('variant'))
// → "Array.skins[q.variant]"
// Convenience: variant array selection
Molang.variantArray('skins') // → "Array.skins[q.variant]"
// Ternary
Molang.ternary(Molang.query('is_sheared'), 'Geometry.sheared', 'Geometry.woolly')
// → "(q.is_sheared ? Geometry.sheared : Geometry.woolly)"
// Logical NOT
Molang.not(Molang.query('is_sleeping')) // → "!q.is_sleeping"
// Equality comparison
Molang.equals(Molang.property('minecraft:shape'), "'chicken'")
// → "q.property('minecraft:shape') == 'chicken'"
// Property-to-index (shapeshifter nested ternary)
Molang.propertyToIndex('minecraft:shape', ['shapeshifter', 'chicken', 'cat'])
// → "(q.property('minecraft:shape') == 'shapeshifter' ? 0 : ...)"
// Math helpers
Molang.mathCos('q.anim_time * 12.3') // → "math.cos(q.anim_time * 12.3)"
Molang.mathFloor('q.variant * 0.5') // → "math.floor(q.variant * 0.5)"
// UV atlas helpers
const uvAnim = Molang.uvAtlas();
// → { offset: ["v.offset_x", "v.offset_y"], scale: ["v.scale_x", "v.scale_y"] }
const [uOffset, uScale] = Molang.uvAtlasAxis();
// uOffset → "v.offset_x", uScale → "v.scale_x"
MolangQuery constants
| Constant | Query | Description |
|---|---|---|
MolangQuery.VARIANT | q.variant | Numeric variant index (ocelot skin, villager type, …) |
MolangQuery.MARK_VARIANT | q.mark_variant | Secondary variant index |
MolangQuery.COLOR | q.color | Color index (horse coat, etc.) |
MolangQuery.IS_SHEARED | q.is_sheared | Whether the entity is sheared (0/1) |
MolangQuery.IS_INVISIBLE | q.is_invisible | Whether the entity is invisible (0/1) |
MolangQuery.IS_SLEEPING | q.is_sleeping | Whether the entity is sleeping (0/1) |
MolangQuery.IS_CHESTED | q.is_chested | Whether the entity has a chest equipped (0/1) |
MolangQuery.ANIM_TIME | q.anim_time | Current animation time in seconds |
MolangQuery.OVERLAY_ALPHA | q.overlay_alpha | Alpha component for the hurt/overlay effect |
MolangQuery.HAS_ARMOR_SLOT | q.has_armor_slot | Whether a given armor slot is occupied |
Packager
The Packager class creates distributable .mcaddon and .mcpack archives from generated pack folders using ZIP compression.
import { Packager, packageAddon } from 'jepia';
// Using the Packager class
const packager = new Packager({
outputPath: './dist',
addonName: 'MyAddon',
format: 'mcaddon', // 'mcaddon' | 'mcpack' | 'both'
compressionLevel: 9, // 0-9 (optional)
});
const result = await packager.package(
'./output/behavior_pack',
'./output/resource_pack'
);
console.log(result.success); // true
console.log(result.totalSize); // bytes
console.log(result.addonPackPath); // './dist/MyAddon.mcaddon'
// Convenience function (one-liner)
await packageAddon(
'./output/behavior_pack',
'./output/resource_pack',
'./dist',
'MyAddon'
);
| Method / Function | Description | Returns |
|---|---|---|
constructor(options) | Creates a packager with output path, addon name, format, and compression options. | Packager |
package(behaviorPackPath, resourcePackPath) | Creates the archive(s). Returns a result object with paths, sizes, and success status. | Promise<PackageResult> |
packageAddon(bp, rp, output, name) | Convenience function: creates a .mcaddon containing both packs. | Promise<PackageResult> |
packageAssets(bp?, rp?, output, name) | Creates individual .mcpack files for each pack provided. | Promise<PackageResult> |
packageAsBundle(bp, rp, output, name) | Creates both .mcaddon and individual .mcpack files. | Promise<PackageResult> |
PackageResult
interface PackageResult {
behaviorPackPath?: string; // path to .mcpack (behavior)
resourcePackPath?: string; // path to .mcpack (resource)
addonPackPath?: string; // path to .mcaddon
totalSize: number; // total bytes across all archives
packSizes: {
behaviorPack?: number;
resourcePack?: number;
addonPack?: number;
};
success: boolean;
error?: string;
}
Configuration
Jepia supports configuration files for storing add-on metadata, build options, and persisted UUIDs. Configuration is validated using Zod schemas.
jepia.config.json
{
"name": "MyAddon",
"author": "YourName",
"version": "1.0.0",
"description": "A custom Minecraft add-on",
"namespace": "myaddon",
"baseGameVersion": "1.20.0",
"build": {
"output": "./dist",
"createPackage": true,
"packageFormat": "mcaddon",
"validate": true,
"minify": false
},
"uuids": {
"behaviorPackUUID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"behaviorModuleUUID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"resourcePackUUID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"resourceModuleUUID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
}
UUID Persistence
When addon.generate() is called, Jepia automatically writes the generated UUIDs to jepia.config.json. On subsequent builds, the same UUIDs are reused so that Minecraft treats the pack as the same installation. This prevents duplicate pack entries and preserves world-level settings.
Configuration API
| Function | Description | Returns |
|---|---|---|
validateConfig(config) | Validates a config object against the Zod schema. | JepiaConfig |
loadConfigFile(path) | Loads config from a .json or .ts file. | Promise<JepiaConfig> |
loadConfigFromDirectory(dir) | Searches for jepia.config.ts or jepia.config.json in a directory. | Promise<JepiaConfig | null> |
upsertUuidsInConfigFile(path, uuids) | Merges UUID values into an existing config file without overwriting other fields. | Promise<void> |
parseVersionString(version) | Parses "1.2.3" into [1, 2, 3]. | [number, number, number] |
versionToString(tuple) | Converts [1, 2, 3] to "1.2.3". | string |
Examples
The repository includes several ready‑to‑run examples in the examples/ directory:
- simple‑addon.ts – Basic items, blocks, entities, recipes, and loot tables.
- comprehensive‑addon.ts – A larger add‑on ("FantasyRealm") with 21 components across all categories.
- texture‑layering‑migration.ts – Seven migration scenarios demonstrating the upgrade path from the legacy single-texture API to multi-layer, PBR TextureSet, and Vibrant Visuals systems.
Run any example with:
bun run examples/simple-addon.ts
Full Rendering Pipeline Example
import {
Addon, Entity,
TextureLayer, RenderController,
Material, VanillaMaterials,
Molang
} from 'jepia';
const addon = new Addon('MyMod', 'Custom entity with full rendering pipeline');
// 1. Create texture variants
const wildLayer = new TextureLayer('Wild', 'mymod:ocelot_wild', 'textures/entity/ocelot_wild.png');
const blackLayer = new TextureLayer('Black', 'mymod:ocelot_black', 'textures/entity/ocelot_black.png');
const redLayer = new TextureLayer('Red', 'mymod:ocelot_red', 'textures/entity/ocelot_red.png');
// 2. Create material
const mat = Material.fromVanilla('mymod:ocelot_mat', VanillaMaterials.ENTITY_ALPHATEST);
// 3. Create render controller (auto-configures texture array + q.variant)
const rc = RenderController.fromTextureLayers(
'Ocelot RC', 'mymod:ocelot',
[wildLayer, blackLayer, redLayer]
);
// 4. Add hurt color tint
rc.setIsHurtColor({ r: '0.0', g: '0.0', b: '1.0', a: '0.5' });
// 5. Create entity and wire everything together
const ocelot = new Entity('Ocelot', 'mymod:ocelot');
ocelot.addComponent('minecraft:health', { value: 10 });
ocelot.setMaterial(mat);
ocelot.setRenderController(rc);
addon.addEntity(ocelot);
await addon.generate('./output');
Advanced Usage
Texture Layering & PBR
Jepia supports the full Minecraft PBR pipeline via TextureLayer and TextureSet. This allows building complex textures with normal maps, MER (metalness/emissive/roughness), and heightmaps for Vibrant Visuals rendering.
import { TextureLayer, TextureSet } from 'jepia';
const color = new TextureLayer('Base', 'myaddon:base_color', 'textures/entity/base.png');
const normalMap = new TextureLayer('Normal', 'myaddon:base_normal',
'textures/entity/base_normal.png', 'normal');
const mer = new TextureLayer('MER', 'myaddon:base_mer',
'textures/entity/base_mer.png', 'mer');
const pbr = new TextureSet('Base PBR', 'myaddon:base_pbr');
pbr.setColor(color)
.setNormal(normalMap)
.setMer(mer);
// Generates a .texture_set.json file for Vibrant Visuals
Dynamic Geometry Selection
import { RenderController, Molang } from 'jepia';
// Sheep: woolly vs sheared geometry based on q.is_sheared
const rc = new RenderController('Sheep RC', 'myaddon:sheep')
.addGeometryArray('geos', ['Geometry.default', 'Geometry.sheared'])
.setGeometry(Molang.arrayIndex('Array.geos', Molang.query('is_sheared')))
.setMaterials([{ '*': 'Material.default' }])
.setTextures(['Texture.default']);
Shapeshifter Pattern (Property-Based Geometry)
import { RenderController, Molang } from 'jepia';
const shapes = ['shapeshifter', 'chicken', 'cat', 'sheep'];
const rc = new RenderController('Shapeshifter RC', 'myaddon:shapeshifter')
.addGeometryArray('geos', shapes.map(s => `Geometry.${s}`))
.setGeometry(Molang.arrayIndex(
'Array.geos',
Molang.propertyToIndex('minecraft:shape', shapes)
))
.setMaterials([{ '*': 'Material.default' }])
.setTextures(['Texture.default']);
Configuration File
You can store add‑on metadata (including generated UUIDs) in a jepia.config.json file. This ensures consistent UUIDs across multiple builds.
{
"name": "MyAddon",
"author": "Your Name",
"uuids": {
"behaviorPackUUID": "...",
"resourcePackUUID": "..."
}
}
Scripting
Jepia integrates Minecraft's Script API directly into the add‑on build pipeline. You write runtime game logic in the same TypeScript file as your JSON components. During addon.generate(), Jepia extracts the script callbacks, bundles them with Bun.build, and writes behavior_pack/scripts/main.js. The behavior pack manifest automatically gains a "type": "script" module and the correct @minecraft/* dependencies.
addon.script() callbacks runs inside Minecraft Bedrock at runtime. The @minecraft/* modules do not exist outside the game engine — they are provided by Minecraft when the pack is loaded. Jepia extracts the source text at build time via Function.prototype.toString() and rewrites ctx.server references into proper ES module imports.
addon.script()
Registers an inline runtime script block. The callback body is extracted and bundled into scripts/main.js.
Simple form — defaults to @minecraft/server at the latest stable version:
addon.script((ctx) => {
const { world, system } = ctx.server;
world.afterEvents.entityHitEntity.subscribe((event) => {
const attacker = event.damagingEntity;
if (attacker.typeId === 'minecraft:player') {
system.run(() => attacker.sendMessage('You struck something!'));
}
});
});
Full form — specify module versions explicitly:
addon.script({
modules: {
server: '2.5.0', // @minecraft/server (Scripting V2 stable)
serverUi: '2.0.0', // @minecraft/server-ui (Scripting V2 stable)
},
}, (ctx) => {
const { world } = ctx.server;
const { ActionFormData } = ctx.serverUi;
world.afterEvents.itemUse.subscribe((event) => {
const form = new ActionFormData()
.title('Menu')
.button('Hello');
form.show(event.source);
});
});
Multiple addon.script() calls are merged into a single scripts/main.js with deduplicated imports.
ScriptContext properties
| Property | Module | Default version | Notes |
|---|---|---|---|
ctx.server | @minecraft/server | 2.5.0 | Scripting V2 stable |
ctx.serverUi | @minecraft/server-ui | 2.0.0 | Scripting V2 stable |
ctx.common | @minecraft/common | 1.2.0 | Error types, stable |
ctx.serverGametest | @minecraft/server-gametest | 1.0.0-beta | Requires Beta APIs experiment |
ctx.serverAdmin | @minecraft/server-admin | 1.0.0-beta | Dedicated server only, beta |
ctx.serverNet | @minecraft/server-net | 1.0.0-beta | Dedicated server only, beta |
ctx.serverEditor | @minecraft/server-editor | 0.1.0-beta | Editor extension only, beta |
ctx.serverGraphics | @minecraft/server-graphics | 1.0.0-beta | Graphics/rendering, beta |
ctx.diagnostics | @minecraft/diagnostics | 1.0.0-beta | Content diagnostics, beta |
ctx.debugUtilities | @minecraft/debug-utilities | 1.0.0-beta | Debug helpers, beta |
@minecraft/server version → Minecraft version
| @minecraft/server | Minecraft |
|---|---|
2.5.0 | ~1.26.0 |
2.4.0 | ~1.25.30 |
2.3.0 | ~1.25.0 |
2.2.0 | ~1.24.0 |
2.1.0 | ~1.23.0 |
2.0.0 | ~1.22.0 |
Only Scripting V2 (2.x.x) is supported. V1 (1.x.x) has been removed.
addon.setScriptModuleVersion()
Override the version for a specific module globally:
addon.setScriptModuleVersion('@minecraft/server', '2.5.0'); // Scripting V2 stable
addon.scriptFile()
Points to an existing TypeScript or JavaScript file. Jepia bundles it alongside any inline blocks. Useful for complex scripts or existing codebases.
// Single file
addon.scriptFile('./scripts/events.ts');
// Multiple files (bundled into one output)
addon.scriptFile('./scripts/events.ts');
addon.scriptFile('./scripts/commands.ts');
File-based scripts can use normal top-level import ... from '@minecraft/server' statements — Jepia treats all @minecraft/* modules as externals automatically.
Generated output structure
behavior_pack/
├── manifest.json ← script module + @minecraft/server dependency added
└── scripts/
└── main.js ← bundled runtime code (runs inside Minecraft only)
Generated manifest (excerpt)
{
"modules": [
{ "type": "data", "uuid": "...", "version": [1, 0, 0] },
{ "type": "script", "language": "javascript",
"uuid": "...", "version": [1, 0, 0], "entry": "scripts/main.js" }
],
"dependencies": [
{ "module_name": "@minecraft/server", "version": "2.5.0" }
]
}
Custom Components V2
Custom Components bridge JSON block/item definitions with runtime Script API handlers. Calling addCustomComponent() on a Block or Item does two things automatically during addon.generate():
- Adds the component name to the block/item JSON
componentssection. - Generates
system.beforeEvents.startup.subscribe()registration code inscripts/main.js.
Block.addCustomComponent()
import { Block } from 'jepia';
const cloudBlock = new Block('Cloud Block', 'myaddon:cloud_block');
cloudBlock.addCustomComponent(
'myaddon:crumble_on_step', // namespaced component name
{
// Handler runs inside Minecraft — system is imported by generated startup code
onStepOn: (event) => {
const { block, dimension } = event;
system.run(() => {
dimension.runCommand(`setblock ${block.x} ${block.y} ${block.z} air`);
});
},
onEntityFallOn: (event) => {
event.entity.sendMessage('The cloud crumbles!');
},
},
{ fragile: true } // optional JSON params embedded in block component entry
);
Block event handlers (BlockCustomComponentHandlers):
| Handler | Trigger |
|---|---|
beforeOnPlayerPlace | Before a player places the block |
onBreak | Block is broken (any cause) |
onEntityFallOn | An entity lands on the block |
onPlace | Block is placed |
onPlayerBreak | A player breaks the block |
onPlayerInteract | A player right-clicks / interacts |
onRandomTick | Random tick fires |
onRedstoneUpdate | Redstone signal changes |
onStepOff | An entity steps off the block |
onStepOn | An entity steps onto the block |
onTick | Every game tick while active |
Item.addCustomComponent()
import { Item } from 'jepia';
const coin = new Item('Lucky Coin', 'myaddon:lucky_coin');
coin.addCustomComponent('myaddon:flip_coin', {
onUse: (event) => {
const { source: player } = event;
system.run(() => {
if (Math.random() > 0.5) {
player.sendMessage('Heads! Good fortune.');
player.addEffect('minecraft:luck', 200, { amplifier: 0 });
} else {
player.sendMessage('Tails! Better luck next time.');
}
});
},
onHitEntity: (event) => {
event.attackingEntity?.sendMessage('Strike with the Lucky Coin!');
},
});
Item event handlers (ItemCustomComponentHandlers):
| Handler | Trigger |
|---|---|
onBeforeDurabilityDamage | Before durability is reduced |
onCompleteUse | After a use-duration completes (e.g., eating) |
onConsume | Item is consumed |
onHitEntity | Item used to hit an entity |
onMineBlock | Block mined with item |
onUse | Player uses (right-clicks) the item |
onUseOn | Player uses item on a block |
Accessors
block.hasCustomComponents(); // boolean
block.getCustomComponents(); // CustomComponentRegistration[]
block.getCustomComponent('ns:name'); // single registration or undefined
block.getSummary().customComponentCount; // number
Custom Commands
Register a custom slash command. Jepia generates the startup registration code in scripts/main.js automatically, including enum registration and command handler wiring.
import { CustomCommandPermissionLevel, CustomCommandParamType } from 'jepia';
addon.addCustomCommand({
name: 'myaddon:heal',
description: 'Heal targeted players to full health',
permissionLevel: CustomCommandPermissionLevel.GameDirectors,
mandatoryParameters: [
{ type: CustomCommandParamType.PlayerSelector, name: 'targets' },
],
// system is imported in the generated startup code — use it directly.
// This callback runs inside Minecraft only.
callback: (origin, targets) => {
system.run(() => {
for (const player of targets) {
const health = player.getComponent('minecraft:health');
if (health) health.setCurrentValue(health.effectiveMax);
player.sendMessage('You have been healed!');
}
});
},
});
CustomCommandConfig
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Namespaced command name (e.g. "myaddon:heal") |
description | string | Yes | Human-readable command description |
permissionLevel | CustomCommandPermissionLevel | No | Who can run the command (default: Any) |
mandatoryParameters | CustomCommandParameter[] | No | Required parameters |
optionalParameters | CustomCommandParameter[] | No | Optional parameters |
callback | Function | Yes | Handler — runs in Minecraft's restricted context; use system.run() for writes |
CustomCommandPermissionLevel
| Value | Who can run |
|---|---|
Any | All players |
GameDirectors | Operators / cheats enabled |
Admin | Server admins |
Host | World host only |
Owner | Pack owner only |
CustomCommandParamType
| Value | Minecraft equivalent |
|---|---|
BlockType | Block identifier string |
Boolean | true / false |
EntitySelector | @e, @a, etc. |
Float | Decimal number |
Integer | Whole number |
ItemType | Item identifier string |
Position | XYZ coordinates |
PlayerSelector | @p, @a, specific name |
String | Quoted string |
Enum | Custom enum value (requires CustomCommandEnum) |
Custom enums
addon.addCustomCommand(
{
name: 'myaddon:give_ore',
description: 'Give player an ore type',
mandatoryParameters: [
{ type: CustomCommandParamType.PlayerSelector, name: 'target' },
{ type: CustomCommandParamType.Enum, name: 'oreType' },
],
callback: (origin, target, oreType) => {
system.run(() => {
target[0]?.runCommand(`give @s myaddon:${oreType}_ore`);
});
},
},
[
{ name: 'OreType', values: ['iron', 'copper', 'gold'] },
]
);
Cross-Reference Helpers
These methods bridge component identifiers between JSON definitions and script code. They're especially useful when component IDs are dynamic or derived from user input.
const fireSword = new Item('Fire Sword', 'myaddon:fire_sword');
addon.addItem(fireSword);
// Get the identifier for use in scripts
const id = addon.getItemId(fireSword); // "myaddon:fire_sword"
// Or look up all registered IDs
const itemIds = addon.getRegisteredItemIds(); // ["myaddon:fire_sword", ...]
const blockIds = addon.getRegisteredBlockIds(); // ["myaddon:magic_ore", ...]
const entityIds = addon.getRegisteredEntityIds(); // [...]
| Method | Returns | Description |
|---|---|---|
getItemId(item) | string | Identifier string of the item component |
getBlockId(block) | string | Identifier string of the block component |
getEntityId(entity) | string | Identifier string of the entity component |
getRegisteredItemIds() | string[] | All registered item IDs |
getRegisteredBlockIds() | string[] | All registered block IDs |
getRegisteredEntityIds() | string[] | All registered entity IDs |
hasScripts() | boolean | Whether any scripts are registered |
Constants injection
When scripts are enabled, Jepia automatically injects a constants object into scripts/main.js containing all registered component identifiers in SCREAMING_SNAKE_CASE:
// Auto-generated in scripts/main.js
export const ITEMS = {
FIRE_SWORD: "myaddon:fire_sword",
COOL_POTION: "myaddon:cool_potion",
};
export const BLOCKS = {
MAGIC_ORE: "myaddon:magic_ore",
};
export const ENTITIES = {
DRAGON: "myaddon:dragon",
};
Use these constants inside your script callbacks to avoid string duplication between JSON and runtime code.
Script Types Reference
ScriptModuleId
type ScriptModuleId =
| '@minecraft/server' // stable 2.5.0 (V2)
| '@minecraft/server-ui' // stable 2.0.0 (V2)
| '@minecraft/common' // stable 1.2.0
| '@minecraft/server-gametest' // 1.0.0-beta only
| '@minecraft/server-admin' // 1.0.0-beta only
| '@minecraft/server-net' // 1.0.0-beta only
| '@minecraft/server-editor' // 0.1.0-beta only
| '@minecraft/server-graphics' // 1.0.0-beta only
| '@minecraft/diagnostics' // 1.0.0-beta only
| '@minecraft/debug-utilities'; // 1.0.0-beta only
ScriptOptions
interface ScriptOptions {
modules?: {
server?: string; // @minecraft/server version
serverUi?: string; // @minecraft/server-ui version
common?: string; // @minecraft/common version
serverGametest?: string;
serverAdmin?: string;
serverNet?: string;
serverEditor?: string;
serverGraphics?: string;
diagnostics?: string;
debugUtilities?: string;
};
}
UUID persistence
The script module UUID (required for stable pack identity) is stored in jepia.config.json alongside the behavior/resource pack UUIDs:
{
"addonName": "MyAddon",
"uuids": {
"behaviorPackUUID": "...",
"behaviorModuleUUID": "...",
"resourcePackUUID": "...",
"resourceModuleUUID": "...",
"scriptModuleUUID": "..."
}
}
Dialogue
NPC dialogue definitions written to behavior_pack/dialogue/<id>.json. Format version 1.20.80.
import { Dialogue } from 'jepia';
const dlg = new Dialogue('Trader Dialogue', 'myaddon:trader');
dlg.addScene('intro', {
npc_name: 'Mysterious Trader',
text: 'What do you seek?',
on_open_commands: ['/effect @initiator speed 10 1'],
buttons: [
{ name: 'Shop', commands: ['/dialogue open @e[type=myaddon:trader,r=3] @initiator shop'] },
{ name: 'Leave', commands: [] },
],
});
addon.addDialogue(dlg);
| Method | Description |
|---|---|
addScene(tag, opts?) | Add a scene. opts: npc_name, text, on_open_commands, on_close_commands, buttons |
getScenes() | Returns all scenes as a copy |
validate() | Checks: ≥1 scene, no duplicate tags, ≤6 buttons per scene |
toJSON() | Serialises to minecraft:npc_dialogue JSON |
Camera
Custom camera presets written to resource_pack/cameras/presets/<id>.json. Format version 1.21.0.
import { Camera } from 'jepia';
const cam = new Camera('Over-Shoulder', 'myaddon:over_shoulder');
cam.setInheritFrom('minecraft:follow_orbit')
.setRadius(4)
.setViewOffset(2.0, 0.5)
.setEntityOffset(0, 1.5, 0)
.setStartingRotation(-10, 0);
addon.addCamera(cam);
| Method | Description |
|---|---|
setInheritFrom(preset) | Vanilla preset to inherit (e.g. "minecraft:follow_orbit") |
setRadius(r) | Distance from player in blocks (0.1–100) |
setViewOffset(x, y) | Screen-space anchor offset |
setEntityOffset(x, y, z) | Player-space anchor offset |
setStartingRotation(pitch, yaw) | Initial camera rotation (degrees) |
setAimAssist(opts) | Aim-assist preset, target_mode, angle, distance |
setField(key, value) | Escape hatch for any other camera preset field |
validate() | Checks: radius 0.1–100, pitch ±90°, yaw ±180° |
Fog
Fog definitions written to resource_pack/fogs/<id>.json. Format version 1.16.100.
import { Fog } from 'jepia';
const fog = new Fog('Mystic Fog', 'myaddon:mystic');
fog.setDistanceFog('air', { fog_start: 0.8, fog_end: 1.0, fog_color: '#c0c0ff', render_distance_type: 'render' })
.setDistanceFog('water', { fog_start: 0, fog_end: 30, fog_color: '#1a3080', render_distance_type: 'fixed' })
.setVolumetricDensity('air', { max_density: 0.3, uniform: false })
.setVolumetricMediaCoefficients('air', { scattering: [0.02, 0.02, 0.02] });
addon.addFog(fog);
| Method | Description |
|---|---|
setDistanceFog(medium, opts) | Distance fog for air / water / lava / lava_resistance / powder_snow / weather |
setVolumetricDensity(medium, opts) | Volumetric density for air / water / lava / weather |
setVolumetricMediaCoefficients(medium, opts) | Scattering + absorption coefficients for air / water / cloud |
validate() | Checks: fog_start ≤ fog_end, max_density 0–1, render_distance_type valid |
SoundDefinition
Sound event definitions aggregated into a single resource_pack/sounds/sound_definitions.json. Format version 1.20.20. Event names use Bedrock's dot-notation (e.g. "random.chestopen").
import { SoundDefinition } from 'jepia';
const snd = new SoundDefinition('Drill Use', 'myaddon.drill.use');
snd.setCategory('block')
.addSoundFile('sounds/myaddon/drill_use1')
.addSoundEntry({ name: 'sounds/myaddon/drill_use2', volume: 0.9, pitch: [0.9, 1.1] })
.setDistance(4, 16);
addon.addSoundDefinition(snd); // merged into sound_definitions.json
| Method | Description |
|---|---|
setCategory(cat) | ambient, block, bottle, bucket, hostile, music, neutral, player, record, ui, weather |
addSoundFile(path) | Add a simple file reference (path relative to sounds/, no extension) |
addSoundEntry(entry) | Add a detailed entry with name, volume, pitch, weight, stream, is3D |
setDistance(min, max) | 3D attenuation distance range (blocks) |
toDefinitionJSON() | Returns per-event definition object (without the top-level wrapper) |
BlockCullingRule
Block face culling rules written to resource_pack/block_culling/<id>.json. Format version 1.21.80.
import { BlockCullingRule } from 'jepia';
const cull = new BlockCullingRule('Glass Pane Culling', 'myaddon:glass_pane');
cull.addFaceRule('block', 0, 'north', 'same_block')
.addFaceRule('block', 0, 'south', 'same_block')
.addFaceRule('block', 0, 'east', 'same_block')
.addFaceRule('block', 0, 'west', 'same_block');
addon.addBlockCullingRule(cull);
| Method | Description |
|---|---|
addRule(rule) | Add a full rule object: direction, condition, geometry_part, cull_against_full_and_opaque? |
addFaceRule(bone, cube, dir, cond?) | Convenience shorthand — bone + cube index + direction + condition (default: same_block) |
getRules() | Returns all rules as a copy |
Conditions: "default", "same_block", "same_block_permutation", "same_culling_layer".
Plugin System
The plugin system lets you hook into the add-on generation lifecycle. Implement JepiaPlugin and register it with addon.use(plugin).
import type { JepiaPlugin } from 'jepia';
const myPlugin: JepiaPlugin = {
name: 'my-plugin',
onBeforeGenerate({ addonName, outputPath, metadata }) {
console.log(`Building "${addonName}" → ${outputPath}`);
metadata.startTime = Date.now();
},
onAfterGenerate({ metadata }) {
const ms = Date.now() - (metadata.startTime as number);
console.log(`Done in ${ms}ms`);
},
};
addon.use(myPlugin);
await addon.generate('./output');
| Interface / Class | Description |
|---|---|
JepiaPlugin | Interface: name (required), onBeforeGenerate?, onAfterGenerate? |
PluginContext | Passed to hooks: addonName, outputPath, metadata |
PluginRegistry | Internal registry; use addon.use(plugin) to register |
addon.use(plugin) | Register a plugin; returns this for chaining |
Both hooks are called in registration order and support async functions.
Particle
Particle effect definitions written to resource_pack/particles/<id>.json. Format version 1.10.0.
import { Particle } from 'jepia';
const sparks = new Particle('Sparks', 'myaddon:sparks');
sparks.setRenderParameters('particles_alpha', 'textures/particle/particles');
sparks.setEmitterRate('steady', { spawn_rate: 10, max_particles: 50 });
sparks.setEmitterLifetime('looping', { active_time: 2 });
sparks.setEmitterShape('sphere', { radius: 0.5 });
sparks.setParticleLifetime(1.5);
sparks.setAppearanceBillboard({ size: [0.1, 0.1], facing_camera_mode: 'lookat_xyz' });
addon.addParticle(sparks);
| Method | Description |
|---|---|
setRenderParameters(material, texture) | Set material and texture path for rendering |
setEmitterRate(type, params) | Set spawn rate: "steady", "instant", or "manual" |
setEmitterLifetime(type, params) | Set emitter lifetime: "looping", "once", or "expression" |
setEmitterShape(type, params?) | Set emitter shape: "point", "sphere", "box", "disc", "entity_aabb", "custom" |
setParticleLifetime(max) | Set particle max lifetime (number or Molang expression) |
setAppearanceBillboard(params) | Set billboard appearance: size, facing_camera_mode, uv |
setAppearanceTinting(color) | Set particle color tinting (RGBA Molang array or gradient) |
addCurve(name, curve) | Add a Molang curve for animation |
addEvent(name, event) | Add a lifecycle event |
setComponent(name, value) | Set arbitrary particle component (escape hatch) |
validate() | Checks material, texture, emitter lifetime, emitter rate |
Animation
Bone animations written to resource_pack/animations/<id>.json. Format version 1.8.0.
import { Animation } from 'jepia';
const walk = new Animation('Walk Anim', 'myaddon:walk');
walk.setLoop(true);
walk.setAnimationLength(1.0);
walk.addBoneKeyframes('left_leg', 'rotation', {
'0.0': ['-30', '0', '0'],
'0.5': ['30', '0', '0'],
'1.0': ['-30', '0', '0'],
});
walk.addTimelineEvent('0.5', '/playsound step.grass @s');
addon.addAnimation(walk);
| Method | Description |
|---|---|
setAnimationId(id) | Override animation identifier (default: animation.<ns>.<name>) |
getAnimationId() | Get the animation identifier string |
setLoop(loop) | Set loop mode: true, false, or "hold_on_last_frame" |
setAnimationLength(seconds) | Set total animation duration in seconds |
addBoneKeyframes(bone, property, keyframes) | Add keyframes: property is "position", "rotation", or "scale" |
addTimelineEvent(time, event) | Add a timeline event (command string or array) |
setData(data) | Merge raw animation definition data (escape hatch) |
AnimationController
Animation state machines written to resource_pack/animation_controllers/<id>.json. Format version 1.10.0.
import { AnimationController } from 'jepia';
const ctrl = new AnimationController('Move Controller', 'myaddon:move');
ctrl.setInitialState('idle');
ctrl.addState('idle', {
animations: ['idle'],
transitions: [{ walk: 'q.is_moving' }],
});
ctrl.addState('walk', {
animations: ['walk'],
transitions: [{ idle: '!q.is_moving' }],
blend_transition: 0.2,
});
addon.addAnimationController(ctrl);
| Method | Description |
|---|---|
setControllerId(id) | Override controller identifier (default: controller.animation.<ns>.<name>) |
getControllerId() | Get the controller identifier string |
setInitialState(state) | Set initial state name (defaults to "default") |
addState(name, config) | Add/replace a state: animations, transitions, blend_transition, particle_effects, sound_effects |
getStateNames() | Get all configured state names |
setData(data) | Merge raw controller data (escape hatch) |
validate() | Checks: at least 1 state, initial state exists |
Attachable
Item appearance when worn or held, written to resource_pack/attachables/<id>.json. Format version 1.8.0.
import { Attachable } from 'jepia';
const helmet = new Attachable('Iron Helmet', 'myaddon:iron_helmet');
helmet.setMaterials({ default: 'armor' });
helmet.setTextures({ default: 'textures/models/armor/iron_1' });
helmet.setGeometry({ default: 'geometry.humanoid.armor.helmet' });
helmet.setRenderControllers(['controller.render.armor']);
addon.addAttachable(helmet);
| Method | Description |
|---|---|
setMaterials(materials) | Set material definitions (name → material id) |
setTextures(textures) | Set texture definitions (name → texture path) |
setGeometry(geometry) | Set geometry definitions (name → geometry id) |
setAnimations(animations) | Set animation references (short name → full id) |
setRenderControllers(controllers) | Set render controllers list |
setScripts(scripts) | Set scripts block (parent_setup, pre_animation, animate) |
setData(data) | Merge raw description data (escape hatch) |
validate() | Checks: at least 1 texture and 1 geometry |
ClientBiome
Client-side biome rendering written to resource_pack/biomes_client/<id>.json. Format version 1.0.0. Pairs with the server-side Biome component.
import { ClientBiome } from 'jepia';
const crystal = new ClientBiome('Crystal Caves Client', 'myaddon:crystal_caves');
crystal.setWaterColor('#1a3c6b');
crystal.setSkyColor('#0a0a2e');
crystal.setFogAppearance('#0a0a2e', 0.0, 0.5);
crystal.setGrassColor('#2d5a27');
crystal.setFoliageColor('#1e3a1e');
addon.addClientBiome(crystal);
| Method | Description |
|---|---|
setWaterColor(hex) | Set water surface color |
setSkyColor(hex) | Set sky color |
setFogAppearance(color, start, end) | Set fog color, start distance (0–1), end distance |
setGrassColor(hex) | Set grass/vegetation tint |
setFoliageColor(hex) | Set foliage (leaves) tint |
setComponent(name, value) | Set arbitrary client biome component (escape hatch) |
JigsawStructure
Procedural jigsaw structure definitions written to behavior_pack/worldgen/structures/<id>.json. Format version 1.21.20.
import { JigsawStructure } from 'jepia';
const village = new JigsawStructure('Village', 'myaddon:village');
village.setStep('surface_structures');
village.setStartPool('myaddon:village/town_centers');
village.setMaxDepth(6);
village.setStartHeight({ type: 'constant', value: { absolute: 0 } });
village.setTerrainAdaptation('beard_thin');
village.setHeightmapProjection('world_surface');
village.addBiomeFilter({ test: 'has_biome_tag', value: 'village' });
addon.addJigsawStructure(village);
| Method | Description |
|---|---|
setStep(step) | Set generation step (e.g. "surface_structures", "underground_structures") |
setStartPool(pool) | Set starting template pool identifier |
setMaxDepth(depth) | Set max recursion depth (0–20) |
setStartHeight(height) | Set start height provider (constant or uniform) |
setTerrainAdaptation(mode) | Set terrain mode: none, bury, beard_thin, beard_box, encapsulate |
setHeightmapProjection(proj) | Set projection: world_surface, ocean_floor, none |
addBiomeFilter(filter) | Add a biome filter condition |
addPoolAlias(alias) | Add pool alias (direct, random, or random_group) |
setStartJigsawName(name) | Set jigsaw block name from start pool |
setDimensionPadding(padding) | Set dimension padding |
setMaxDistanceFromCenter(dist) | Set max distance from center |
validate() | Checks step, startPool, maxDepth range, startHeight |
TemplatePool
Weighted structure template pools written to behavior_pack/worldgen/template_pools/<id>.json. Format version 1.21.20.
import { TemplatePool } from 'jepia';
const pool = new TemplatePool('Town Centers', 'myaddon:village/town_centers');
pool.addElement({ location: 'myaddon:village/plains_center' }, 1);
pool.addElement({ location: 'myaddon:village/desert_center' }, 1);
pool.addEmptyElement(3); // 60% chance of nothing
pool.setFallback('minecraft:empty');
addon.addTemplatePool(pool);
| Method | Description |
|---|---|
addElement(config, weight?) | Add a structure element. Config: location, optional processors/projection. Weight 1–200 (default 1) |
addEmptyElement(weight?) | Add empty element (generates nothing). Weight 1–200 |
setFallback(pool) | Set fallback pool (default: "minecraft:empty") |
getElements() | Get all elements as a copy |
validate() | Checks: at least 1 element |
StructureSet
Structure placement rules written to behavior_pack/worldgen/structure_sets/<id>.json. Format version 1.21.20.
import { StructureSet } from 'jepia';
const set = new StructureSet('Village Set', 'myaddon:villages');
set.setPlacement({
type: 'minecraft:random_spread',
salt: 10387312,
spacing: 34,
separation: 8,
spread_type: 'linear',
});
set.addStructure('myaddon:village', 1);
addon.addStructureSet(set);
| Method | Description |
|---|---|
setPlacement(config) | Set placement: type, salt, spacing, separation (< spacing), spread_type |
addStructure(id, weight?) | Add a jigsaw structure with weight (default 1) |
getPlacement() | Get placement config or null |
getStructures() | Get all structures as a copy |
validate() | Checks: placement set, separation < spacing, at least 1 structure |
ProcessorList
Block transformation rules for structure placement, written to behavior_pack/worldgen/processors/<id>.json. Format version 1.21.20.
import { ProcessorList } from 'jepia';
const proc = new ProcessorList('Mossy Processor', 'myaddon:mossy');
proc.addBlockIgnore(['minecraft:structure_void']);
proc.addRule({
input_predicate: { predicate_type: 'minecraft:block_match', block: 'minecraft:cobblestone' },
location_predicate: { predicate_type: 'minecraft:always_true' },
output_state: { name: 'minecraft:mossy_cobblestone' },
});
addon.addProcessorList(proc);
| Method | Description |
|---|---|
addBlockIgnore(blocks) | Add block_ignore processor (array of block names to filter out) |
addProtectedBlocks(tag) | Add protected_blocks processor (tag of blocks that can't be replaced) |
addRule(rule) | Add rule processor: input/location/position predicates → output_state |
addCapped(limit, delegate) | Add capped processor limiting delegate applications |
addRawProcessor(processor) | Add raw processor object directly (must have processor_type) |
validate() | Checks: at least 1 processor |
Validation
Every component is validated against the official Minecraft JSON schemas using AJV. Validation happens automatically when you add components and before file generation.
If a component fails validation, Jepia throws a detailed error with schema‑path and error messages. You can also trigger validation manually:
import { validator } from 'jepia';
const isValid = validator.validateItem(item.toJSON());
if (!isValid) {
console.error(validator.errors);
}
All render-pipeline components expose a validate() method that returns { valid: boolean, errors: string[] }:
const rc = new RenderController('My RC', 'myaddon:entity')
.setGeometry('Geometry.default')
.setMaterials([{ '*': 'Material.default' }])
.setTextures(['Array.skins[q.variant]']); // references array not yet defined
const result = rc.validate();
// result.valid → false
// result.errors → ['Texture reference "Array.skins[q.variant]" uses array "Array.skins"
// which is not defined in textureArrays...']
The following schemas are bundled for validation:
item.schema.json– Items (format_version 1.21.40+)block.schema.json– Blocks (format_version 1.21.40+)entity.schema.json– Entities (format_version 1.21.40+)material.schema.json– Material filesrender_controller.schema.json– Render controllers (format_version 1.8.0)texture_set.schema.json– Texture sets (format_version 1.16.100 / 1.21.30)