The objD CLI is an additional package to handle execution, building and deploying of your project. To activate the global package(will be available anywhere on your system), run this command:

dart pub global activate objd_cli

This will add the commands to your console. To run a command run:

dart pub global run objd_cli [command] [args]


objd [command] [args]

If the objd command is not available, you have to add the pub cache to your system path. Follow this tutorial: https://www.dartlang.org/tools/pub/cmd/pub-global#running-a-script-from-your-path

# Commands

  • help - opens a help menu with all commands
  • new [project_name] - generates a new project from a boilerplate
  • run [project_root] - builds one project
  • serve [project_root] - watches the current directory to change and builds the project on change
  • server inject [jar-file] - injects a server file(use bukkit with plugins to reload automatically) before starting the server(The file is not included in the package due to legal reasons)
  • server start [world_dir] - copies the world into the server directory and starts the server

# Build Options

You can use certain arguments to pass options to the build methods. This argument list can directly be edited in createProject:

	["arg1","arg2", ... ] // arguments as optional List

OR (what I recommend) you can just take the program arguments from main:

void main(List<String> args) {

This allows you to use the arguments in the execution command, like:

  • dart index.dart arg1 --min
  • objd run index.dart arg1 --min
  • objd serve index.dart --min

All Available Arguments:

  • --hotreload: Saves the state of your project and compares just the latest changes.
  • --full: Generates the full project(just for objd serve!).
  • --min: This minifies the file amount by ignoring the mcmeta and tag files
  • --gen: Using this flag runs darts build_runner before objd to apply all necessary code generators
  • --clean: Easily remove all files and folders that would be generated by objD with the same options to easily clean junk.
  • --prod: This creates a production build of your project and saves it into a zipped datapack. In Production Comments and line breaks are removed and every widget can access the prod value in Context to get notified.
  • --debug: This creates a debug json file in your project root, that lists all properties and other generated files
  • --zip: Creates an Zip archive instead of a folder with your datapack contents(default in production more)
  • --no-zip: Forces the usual folder output instead of zip
  • --useIsolates: Experimental option for spawing a seperate Isolate(Multithreading) for each filesystem action(usually slower)

# Hotreload

The hotreload option is an experimental feature, that just looks at the things you changed since the last build. This can improve performance significantly especially for big projects with many generated mcfunctions.

This feature is enabled by default for objd serve, if you include the args. You can disable it with the --full option.

How it works:

objD saves a representation of your project in the objd.json file of your project. For each new build or reload it checks which parts of the project you changed and just generates the new necessary files to implement your change in the datapack.

# objd_gen

objd_gen is an optional package that adds code generators to objD. With these annotations Widgets, Files, Packs and Projects can be written much more consisely.

# Installation

Include the package along with build_runner in your pubspec.yaml as a dev dependency:

  objd_gen: ^0.0.3

The generators put the new dart classes and functions in a new file alongside your annotated file. To make it available include it with the part statement:

import 'package:objd/core.dart';

part '[your_filename].g.dart';

After writing all your Widgets,this package generates the associated classes and functions with:

dart pub run build_runner build

Or if you want it to generate automatically after saving run:

dart pub run build_runner watch

# Widget

Writing a Widget becomes much simpler with the @Wdg annotation. You can just give it a function with needed parameters which returns a new Widget and the generators will figure out a Widget class to go along with it.

One simple case would be:

Widget helloWorld() => Log('Hello World!');

After running build_runner a new Widget HelloWorld is generated(inherited from the function name), so it is advisable to use lowercase functions.

Parameters also work like you exspect and you even get access to the widget context if you need to:

Widget helloName(String name, {String lastname = '', Context context}) => For.of([
  Comment('This was generated by HelloName on version ${context.version}'),
  Log('Hello $name $lastname!'),

This would translate to a Widget in the following way, which you can use everywhere in your projects from now on:

class HelloName extends Widget {
  final String name;
  final String lastname;

    this.name, {
    this.lastname = '',

  Widget generate(Context context) => helloName(
        lastname: lastname,
        context: context,

# File

Writing Minecrafts functions this style also becomes really easy. Just annotate a Widget variable that should be inside of your function with @Func():

final Widget load = HelloWorld();

This again would read the variable name load and generate a new variable called LoadFile, which includes the File Widget:

final File LoadFile = File(
  child: load,

Inside the parentheses of @Func() you can also provide various parameters to customize the file generation:

name Provide a custom filename different from the variable name
path Give a custom path for your function(default = /), change this to null to use the source folder
execute whether to execute your File(when included somewhere in the widget tree)
create whether to actually create the file or not


  name: 'main',
  path: 'folder',
  execute: false,
  create: true,
final Widget main_widget = Comment('main file');

# Pack

The @Pck() annotation works similar to @Func. You annotate a File List variable and it generates a Widget for this Pack.

name namespace of this pack
main path of the main function
load path of the load function

If you decide to not provide a namespace it again chooses your variable name.


(name: 'namespace', main: 'main', load: 'load')
final List<File> myPack = [

This generates this widget:

class NamespacePack extends Widget {
  Widget generate(Context context) => Pack(
        name: 'namespace',
        files: myPack,
        load: File('load', create: false),
        main: File('main', create: false),

# Project

And the last piece of creating a datapack is a @Prj. This can automatically generate a main function with all necessary pieces to actually generate all packs and files.

name Name of the datapack
version targeted minecraft version(as integer)
target directory to generate the datapack in
description a description for the pack.mcdata
genMain set this to false if you dont want a main function generated(will be generate_[varname] instead)

Once again we must annotate a Widget variable, that is the root of our widget tree:

  name: 'My awesome Datapack',
  target: './datapacks/',
  version: 17,
  description: 'A simple dp for demonstrating annotations',
final myProject = NamespacePack();

This generates the following in the g.dart:

void main([List<String> args = const []]) => createProject(
        name: 'My awesome Datapack',
        target: './datapacks/',
        version: 17,
        description: 'A simple dp for demonstrating annotations',
        generate: myProject,

# Full example

And thats it, you can now make an entire datapack in one objD file just with some functions and variables. Of course you can extend this principle to as many dart files as you like and make your datapack as complex and modular as you like.

You can see the full example here.

I hope these code generators help with making datapacks using objD. In case you encounter any problems or have any idea or feedback, feel free to reach out via Discord or Email.

# objD 3rd-Party-Support

Since Version 0.3.2 objD allows you to customize it much more and integrate into other projects. You can manually get all the generated Files as json, download zip binary data or use objD with Dart Web to allow a cross platform experience.

Therefore a few methods are provided apart from createProject, that give you the output in alternate forms.

# getAllFiles

getAllFiles accepts, just like createProject, your Project and optionally arguments. It then generates the widget tree and builds the files, but instead of saving them to your machine, returns them as a Dart Map(filename-content).


  var files = getAllFiles(Project(...));
  print(files); // => {pack.mcmeta: '...', data/minecraft/tags/functions/tick.json: '...', ...}

# getCommands

This command emmulates generating commands to one file, but returns the raw list of commands instead of writing the file. This does not consider Files, Packs or similar in the tree, so attention when using Groups, Execute and If You can also provide a Context for specifying the namespace, current filename, version and more.

  List<String> commands = getCommands(Say('hi'));
  print(commands.first); // => "say hi"

# getJsonMap

This function gets a json representation(Map) of the tree structure before generating the files. This is the same that is used as the objd.json output in debug mode.


  var json = getJsonMap(Project(...));
  print(json); // => {name:'...',path: '...', packs: [...]}

# saveAsZip

Saves or downloads(depending on platform) the project as a zip archive. Accepts a file Map(filename-content), most likely from getAllFiles, and a path to save to(must include filename + .zip extension)



# getArchive

When you want to save it as any other Archive or want to implement own generation, you can get the Archive instance with all files added by calling getArchive with the files Map. Read the documentation of the archive package to see what you are able to do: https://pub.dev/documentation/archive


  var archive = getArchive(files);
Last Updated: 12/30/2021, 10:41:22 PM