Custom particles have four moving parts: a registered ParticleType in common, a JSON description file that lists the texture sprites, a client-side provider class that translates the type into actual rendered quads, and a client-only registration hook. The server only knows about the type; all visual logic stays on the client.
RegistrationProvider in the common module.Particle Type Registry
Create a ParticleRegistry class in your common registry package. For a particle that needs no extra data (just a position and velocity), use SimpleParticleType:
The boolean argument to SimpleParticleType controls whether the particle always shows regardless of the client's particle setting (true) or respects it (false). Use false for decorative particles so players with reduced particle settings are not affected.
Because the SimpleParticleType constructor is protected, let's add our access transformer and access widener entries:
Add ParticleRegistry.init() to CommonClass.init().
Particle Description File
Create the particle description JSON at:
Each entry maps to a texture sprite in your textures/particle/ folder. The sprite names correspond to files:
Particle textures are 8×8 or 16×16 PNG files. You can list a single texture for a static particle or multiple textures for an animated one. The particle provider controls which sprite is shown at each point in the particle's lifetime.
Client-Side Provider
Create a provider class in the common project's client/particle package. Extending TextureSheetParticle gives you sprite animation, gravity, and fading for free:
setSpriteFromAge automatically cycles through the sprite list based on the particle's age relative to its lifetime, producing a smooth animation. The fading alpha is calculated manually in tick.
Client-Side Registration
Register the provider from client-only code. On NeoForge, use RegisterParticleProvidersEvent on the mod bus:
Spawning Particles
Always spawn particles from the server using level.sendParticles, which broadcasts to nearby clients automatically. Calling the client-only level.addParticle from server-side code will throw a ClassCastException:
If you are writing client-only code (inside a renderer or a client-side event handler), you can call Minecraft.getInstance().level.addParticle directly.
Testing In-Game
Add a temporary block break event handler that fires the particle above any broken block, then launch the client. Break a block and verify that the sparkle particles appear, fade out, and cycle through their sprite frames.
If no particles appear, check:
- The particle description JSON exists in the correct path under
assets/examplemod/particles/. - The texture files exist at the paths listed in the JSON.
- The provider is registered (the client-side registration hook was called).
- The client particle setting is not set to Minimal, which suppresses most non-essential particles.
For purposes of this tutorial, I added to our existing useWithoutItem method override in our ExampleBlockEntityBlock class:
You can find the source for this tutorial here:
View Source on GitHubMenus & Screens (MultiLoader 1.21+)
Extend ExampleBlockEntity with a nine-slot inventory, build an AbstractContainerMenu with correct slot layout and shift-click routing, create an AbstractContainerScreen with a custom GUI texture, and wire up screen registration on NeoForge and Fabric.
Continue →