GroupeZ Docs
Getting Started

Migrations

Migrations allow you to version control your database schema. Sarah tracks which migrations have been executed and ensures each migration runs only once.

How Migrations Work

  1. You create migration classes that define schema changes
  2. Sarah maintains a migrations table to track executed migrations
  3. When you call MigrationManager.execute(), only new migrations run
  4. Migrations are executed in the order they were registered

Creating a Migration

Extend the Migration class and implement the up() method:

import fr.maxlego08.sarah.database.Migration;

public class CreateUsersTable extends Migration {

    @Override
    public void up() {
        create("users", table -> {
            table.uuid("uuid").primary();
            table.string("name", 64);
            table.text("location").nullable();
            table.bigInt("play_time").defaultValue(0);
            table.timestamps();
        });
    }
}

Migration Operations

Create Table

public class CreatePlayersTable extends Migration {
    @Override
    public void up() {
        create("players", table -> {
            table.uuid("uuid").primary();
            table.string("name", 32);
            table.decimal("balance", 10, 2).defaultValue(0);
            table.bool("is_banned").defaultValue(false);
            table.timestamps();
        });
    }
}

Create Table from Class Template

You can automatically create a table based on a record or class:

// Define your DTO
public record PlayerDTO(
    UUID uuid,
    String name,
    long playTime
) {}

// Migration
public class CreatePlayersFromTemplate extends Migration {
    @Override
    public void up() {
        create("players", PlayerDTO.class);
    }
}

Alter Table

Modify an existing table:

public class AddEmailToUsers extends Migration {
    @Override
    public void up() {
        alter("users", table -> {
            table.string("email", 255).nullable();
        });
    }
}

Create Index

Create an index for better query performance:

public class CreateUsersNameIndex extends Migration {
    @Override
    public void up() {
        createIndex("users", "name");
    }
}

Drop Table

Remove a table:

public class DropOldLogsTable extends Migration {
    @Override
    public void up() {
        drop("old_logs");
    }
}

Rename Table

public class RenamePlayersTable extends Migration {
    @Override
    public void up() {
        rename("players", "users");
    }
}

Column Types

Sarah supports various column types:

MethodSQL TypeExample
uuid(name)VARCHAR(36)table.uuid("player_id")
string(name, length)VARCHAR(length)table.string("name", 64)
text(name)TEXTtable.text("description")
longText(name)LONGTEXTtable.longText("content")
integer(name)INTtable.integer("count")
bigInt(name)BIGINTtable.bigInt("balance")
decimal(name)DECIMALtable.decimal("price")
decimal(name, length, decimals)DECIMAL(length, decimals)table.decimal("price", 10, 2)
bool(name)TINYINT(1)table.bool("active")
json(name)JSONtable.json("metadata")
blob(name)BLOBtable.blob("data")
timestamp(name)TIMESTAMPtable.timestamp("verified_at")
date(name)DATEtable.date("birth_date")

Column Modifiers

Primary Key

table.uuid("uuid").primary();

Auto Increment

table.autoIncrement("id");        // INT
table.autoIncrementBigInt("id");  // BIGINT

Nullable

table.string("nickname", 32).nullable();

Default Value

table.bigInt("balance").defaultValue(0);
table.bool("active").defaultValue(true);
table.string("status", 16).defaultValue("pending");

Default Current Timestamp

table.timestamp("created_at").defaultCurrentTimestamp();

Unique

table.string("email", 255).unique();

Foreign Key

table.uuid("user_id").foreignKey("users");
// or with custom column and cascade
table.uuid("user_id").foreignKey("users", "uuid", true);

Timestamps Helper

Add created_at and updated_at columns:

create("posts", table -> {
    table.autoIncrementBigInt("id");
    table.string("title", 255);
    table.timestamps();  // Adds created_at and updated_at
});

Or individually:

table.createdAt();  // Just created_at
table.updatedAt();  // Just updated_at

Running Migrations

Basic Execution

// Set custom table name (optional, defaults to "migrations")
MigrationManager.setMigrationTableName("my_plugin_migrations");

// Register migrations in order
MigrationManager.registerMigration(new CreateUsersTable());
MigrationManager.registerMigration(new CreatePostsTable());
MigrationManager.registerMigration(new AddEmailToUsers());

// Execute all pending migrations
MigrationManager.execute(databaseConnection, logger);

Complete Example

public class MyPlugin extends JavaPlugin {

    @Override
    public void onEnable() {
        // Setup database connection
        DatabaseConnection connection = setupConnection();

        // Configure migration table name
        MigrationManager.setMigrationTableName("myplugin_migrations");

        // Register all migrations
        registerMigrations();

        // Run migrations
        try {
            MigrationManager.execute(connection, getLogger());
            getLogger().info("Migrations completed successfully!");
        } catch (SQLException e) {
            getLogger().severe("Migration failed: " + e.getMessage());
            e.printStackTrace();
        }
    }

    private void registerMigrations() {
        MigrationManager.registerMigration(new CreatePlayersTable());
        MigrationManager.registerMigration(new CreateHomesTable());
        MigrationManager.registerMigration(new CreateWarpsTable());
        MigrationManager.registerMigration(new AddPlayerEmailColumn());
    }
}

Migration Manager Methods

MethodDescription
setMigrationTableName(name)Set custom migrations table name
getMigrationTableName()Get current migrations table name
registerMigration(migration)Register a migration to be executed
getMigrations()Get list of registered migrations
execute(connection, logger)Execute all pending migrations
setDatabaseConfiguration(config)Set the database configuration
getDatabaseConfiguration()Get the database configuration

Best Practices

1. Name Migrations Descriptively

// Good
public class CreateUsersTable extends Migration { }
public class AddEmailColumnToUsers extends Migration { }
public class CreateUserPostsIndex extends Migration { }

// Bad
public class Migration1 extends Migration { }
public class UpdateTable extends Migration { }

2. Keep Migrations Small

Each migration should do one thing:

// Good - separate migrations
public class CreateUsersTable extends Migration { ... }
public class CreatePostsTable extends Migration { ... }

// Bad - too much in one migration
public class CreateAllTables extends Migration { ... }

3. Never Modify Existing Migrations

Once a migration has been executed in production, create a new migration for changes:

// Don't modify CreateUsersTable after it's deployed
// Instead, create a new migration:
public class AddAvatarToUsers extends Migration {
    @Override
    public void up() {
        alter("users", table -> {
            table.string("avatar_url", 255).nullable();
        });
    }
}

4. Use Timestamps

Always include timestamps for auditing:

create("important_data", table -> {
    table.autoIncrementBigInt("id");
    table.string("data", 255);
    table.timestamps();  // Always include this
});

Real-World Example

Here's a complete migration setup for a homes plugin:

// Migration 1: Create players table
public class CreatePlayersTable extends Migration {
    @Override
    public void up() {
        create("homes_players", table -> {
            table.uuid("uuid").primary();
            table.string("name", 16);
            table.integer("max_homes").defaultValue(3);
            table.timestamps();
        });
    }
}

// Migration 2: Create homes table
public class CreateHomesTable extends Migration {
    @Override
    public void up() {
        create("homes", table -> {
            table.autoIncrementBigInt("id");
            table.uuid("player_uuid").foreignKey("homes_players", "uuid", true);
            table.string("name", 32);
            table.string("world", 64);
            table.decimal("x", 10, 2);
            table.decimal("y", 10, 2);
            table.decimal("z", 10, 2);
            table.decimal("yaw", 5, 2);
            table.decimal("pitch", 5, 2);
            table.timestamps();
        });
    }
}

// Migration 3: Add icon column
public class AddIconToHomes extends Migration {
    @Override
    public void up() {
        alter("homes", table -> {
            table.string("icon", 64).defaultValue("GRASS_BLOCK");
        });
    }
}

Next Steps

On this page

GroupeZ MCP

Documentation in your AI

MCP lets your AI search GroupeZ documentation and read current examples to help you configure your plugins.

Read-only · No documentation API key needed

1. Copy the server URL

Transport: HTTP (Streamable HTTP). Authentication: none. Your tool uses its own model and subscription limits.

  1. Open Plugins in ChatGPT on the web, then + → Add custom MCP server.
  2. Name it ‘GroupeZ Documentation’, paste the URL above and choose ‘No authentication’. Create and install the plugin.
  3. In a conversation, type @ and select the plugin before asking. If needed, enable search, get_page and list_pages in its settings.
Official setup guide

3. Ask a question

Use GroupeZ MCP to help me create a zMenu inventory. Search relevant pages, read the examples and cite your sources.
Other clients and troubleshooting

For clients using mcpServers, adapt this example to their format:

{
  "mcpServers": {
    "groupez-docs": {
      "url": ""
    }
  }
}

Available tools are search, get_page and list_pages. Refresh the connector’s tools after updates. If your AI cannot access them, check that the connector is enabled in the conversation and your account allows custom MCP servers. Opening the URL in a browser is not an MCP test.