Jepia API Documentation

Note: Jepia is under active development. The API may evolve; check the GitHub repository for the latest updates.

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:

  1. TextureLayer – A single image with a PBR role (color, normal, MER, overlay, etc.).
  2. TextureSet – Groups layers into a PBR texture set (.texture_set.json).
  3. Material – Defines shader behavior (.material file with blend factors, defines, states).
  4. RenderController – Wires geometry, textures, and materials together at runtime using Molang expressions.
  5. 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.

MethodDescriptionReturns
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.

Component Reference: See the full list of Item Components available for 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);
Component Reference: See the full list of Block Components available for 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.

Component Reference: See the full list of Entity Components available for 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 TypeDescription
"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 ModeDescription
"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]);
MethodDescriptionReturns
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
MethodDescriptionReturns
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

MethodDescriptionReturns
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

MethodDescriptionReturns
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

MethodDescriptionReturns
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 }>
StrategyDescriptionBest 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().

MethodDescription
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.

MethodDescription
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.

MethodDescription
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.

MethodDescription
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.

MethodDescription
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.

MethodDescription
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.

MethodDescription
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.

MethodDescription
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

ConstantQueryDescription
MolangQuery.VARIANTq.variantNumeric variant index (ocelot skin, villager type, …)
MolangQuery.MARK_VARIANTq.mark_variantSecondary variant index
MolangQuery.COLORq.colorColor index (horse coat, etc.)
MolangQuery.IS_SHEAREDq.is_shearedWhether the entity is sheared (0/1)
MolangQuery.IS_INVISIBLEq.is_invisibleWhether the entity is invisible (0/1)
MolangQuery.IS_SLEEPINGq.is_sleepingWhether the entity is sleeping (0/1)
MolangQuery.IS_CHESTEDq.is_chestedWhether the entity has a chest equipped (0/1)
MolangQuery.ANIM_TIMEq.anim_timeCurrent animation time in seconds
MolangQuery.OVERLAY_ALPHAq.overlay_alphaAlpha component for the hurt/overlay effect
MolangQuery.HAS_ARMOR_SLOTq.has_armor_slotWhether 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 / FunctionDescriptionReturns
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

FunctionDescriptionReturns
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.

Important: Code inside 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

PropertyModuleDefault versionNotes
ctx.server@minecraft/server2.5.0Scripting V2 stable
ctx.serverUi@minecraft/server-ui2.0.0Scripting V2 stable
ctx.common@minecraft/common1.2.0Error types, stable
ctx.serverGametest@minecraft/server-gametest1.0.0-betaRequires Beta APIs experiment
ctx.serverAdmin@minecraft/server-admin1.0.0-betaDedicated server only, beta
ctx.serverNet@minecraft/server-net1.0.0-betaDedicated server only, beta
ctx.serverEditor@minecraft/server-editor0.1.0-betaEditor extension only, beta
ctx.serverGraphics@minecraft/server-graphics1.0.0-betaGraphics/rendering, beta
ctx.diagnostics@minecraft/diagnostics1.0.0-betaContent diagnostics, beta
ctx.debugUtilities@minecraft/debug-utilities1.0.0-betaDebug helpers, beta

@minecraft/server version → Minecraft version

@minecraft/serverMinecraft
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():

  1. Adds the component name to the block/item JSON components section.
  2. Generates system.beforeEvents.startup.subscribe() registration code in scripts/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):

HandlerTrigger
beforeOnPlayerPlaceBefore a player places the block
onBreakBlock is broken (any cause)
onEntityFallOnAn entity lands on the block
onPlaceBlock is placed
onPlayerBreakA player breaks the block
onPlayerInteractA player right-clicks / interacts
onRandomTickRandom tick fires
onRedstoneUpdateRedstone signal changes
onStepOffAn entity steps off the block
onStepOnAn entity steps onto the block
onTickEvery 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):

HandlerTrigger
onBeforeDurabilityDamageBefore durability is reduced
onCompleteUseAfter a use-duration completes (e.g., eating)
onConsumeItem is consumed
onHitEntityItem used to hit an entity
onMineBlockBlock mined with item
onUsePlayer uses (right-clicks) the item
onUseOnPlayer 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

FieldTypeRequiredDescription
namestringYesNamespaced command name (e.g. "myaddon:heal")
descriptionstringYesHuman-readable command description
permissionLevelCustomCommandPermissionLevelNoWho can run the command (default: Any)
mandatoryParametersCustomCommandParameter[]NoRequired parameters
optionalParametersCustomCommandParameter[]NoOptional parameters
callbackFunctionYesHandler — runs in Minecraft's restricted context; use system.run() for writes

CustomCommandPermissionLevel

ValueWho can run
AnyAll players
GameDirectorsOperators / cheats enabled
AdminServer admins
HostWorld host only
OwnerPack owner only

CustomCommandParamType

ValueMinecraft equivalent
BlockTypeBlock identifier string
Booleantrue / false
EntitySelector@e, @a, etc.
FloatDecimal number
IntegerWhole number
ItemTypeItem identifier string
PositionXYZ coordinates
PlayerSelector@p, @a, specific name
StringQuoted string
EnumCustom 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(); // [...]
MethodReturnsDescription
getItemId(item)stringIdentifier string of the item component
getBlockId(block)stringIdentifier string of the block component
getEntityId(entity)stringIdentifier string of the entity component
getRegisteredItemIds()string[]All registered item IDs
getRegisteredBlockIds()string[]All registered block IDs
getRegisteredEntityIds()string[]All registered entity IDs
hasScripts()booleanWhether 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);
MethodDescription
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);
MethodDescription
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);
MethodDescription
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
MethodDescription
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);
MethodDescription
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 / ClassDescription
JepiaPluginInterface: name (required), onBeforeGenerate?, onAfterGenerate?
PluginContextPassed to hooks: addonName, outputPath, metadata
PluginRegistryInternal 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);
MethodDescription
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);
MethodDescription
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);
MethodDescription
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);
MethodDescription
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);
MethodDescription
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);
MethodDescription
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);
MethodDescription
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);
MethodDescription
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);
MethodDescription
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 files
  • render_controller.schema.json – Render controllers (format_version 1.8.0)
  • texture_set.schema.json – Texture sets (format_version 1.16.100 / 1.21.30)

Jepia – a TypeScript framework for Minecraft Bedrock Add‑On generation.

See the GitHub repository for source code, issues, and contributions.