Ask or search…
K
Links
Comment on page

Adding configuration options to your extension page

Learn how to create a basic configuration page and how to work with databases.
We are moving Blueprint's documentation to a brand new website within the following weeks. This documentation website will phase out in the future.
In this guide you'll learn how to add configuration options to your Blueprint extension and how to interact with the database using $blueprint.
First of all, make sure you are able to use $blueprint in your controller as shown in this guide. Once you've added it to your controller and view, continue with this guide.
All the configuration options of your extension get published as a "form". Forms need to be validated to make sure users don't put a string where a number needs to go or a number where a boolean is supposed to go.
In your admin page controller, add the following line to import form validation.
namespace Pterodactyl\Http\Controllers\Admin\Extensions\__identifier__;
use Illuminate\View\View;
use Illuminate\View\Factory as ViewFactory;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Helpers\BlueprintExtensionLibrary;
use Illuminate\Contracts\Config\Repository as ConfigRepository;
use Illuminate\Http\RedirectResponse;
use Pterodactyl\Contracts\Repository\SettingsRepositoryInterface;
+ use Pterodactyl\Http\Requests\Admin\AdminFormRequest;
Now add a class somewhere in your controller. It can be below the code above, or at the bottom of your file. Be creative, as it doesn't matter anyways.
class __identifier__SettingsFormRequest extends AdminFormRequest {
}
You don't have to change the __identifier__ text, as this makes use of something called "placeholders". These will be automatically replaced with the correct values upon installation.
Now we'll add a "rules" function into the class. This will be the validation part. You can read more about the rules you can add on the Laravel documentation.
class __identifier__SettingsFormRequest extends AdminFormRequest {
public function rules(): array {
return [
'option' => 'rules',
];
}
}
Now when the user does not follow the validation rules for an option, you want to let them know the option name, not the database item. You can do this with an attributes function.
class __identifier__SettingsFormRequest extends AdminFormRequest {
public function rules(): array {
return [
'color' => 'string',
'name' => 'string',
'favnumber' => 'numeric',
'can_be_empty' => 'string|nullable',
];
}
public function attributes(): array {
return [
'color' => 'Color',
'name' => 'Name',
'favnumber' => 'Favorite number',
'can_be_empty' => 'Can be empty',
];
}
}
Don't worry, that's all for the validation rules. Onto the next thing, receiving inputs from the form.
Next up, receiving the form results or processing them, whatever you want to call it. This is all done in your ExtensionController class, so don't accidentally put all of this into your SettingsFormRequest class or bad things will happen. We start by telling PHP what errors should be thrown to the user when something breaks. You can do this by adding the following above a function.
/**
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
Directly below that is where you should add the "update" function that will make use of your validation rules before sending it directly to the database (since you shouldn't do that with user input).
public function update(__identifier__SettingsFormRequest $request): RedirectResponse {
foreach ($request->normalize() as $key => $value) {
$this->settings->set('^#identifier#^::' . $key, $value);
}
return redirect()->route('admin.extensions.^#identifier#^.index');
}
Once again, we are using placeholders for the identifier of your extension. The reason we used different placeholders in variables and class names is to not make your code editor go berserk, thank me later. (both __identifier__ and ^#identifier#^ do the same thing)
We are not done yet though, we still need to add some database migrations and create a form on your admin view.
Onto the database migrations we go, one of the only things if screwed up, is possible to be reversed with commands. Create a migrations folder in your extension files and add the path of it into your database: migrations option in your conf.yml.
database:
migrations: "important_stuff/migrations"
Now just create a migration in that folder. First thing you want to do is to name the migration correctly. Below you'll see an example of a correctly named migration.
2023_04_08_160505_create_funny_table.php
| | | | | |
| | | | | * file extension
| | | | * name
| | | * time (hh:mm:ss)
| | * day (dd)
| * month (mm)
* year (yyyy)
Now remember the records we used for validation? Probably not since you might have scrolled up to find them. This is where these records know their place in the database. For the values in this guide, the migration would look like this.
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Migrations\Migration;
class Create__identifier__Table extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::dropIfExists('^#identifier#^');
Schema::create('^#identifier#^', function (Blueprint $table) {
// Include these in your migration since they might
// be useful later on.
$table->id();
$table->timestamp('timestamp')->useCurrent()->onUpdate(null);
// Here are your database values.
$table->string('color')->nullable();
$table->string('name')->nullable();
$table->integer('favnumber')->nullable();
$table->string('can_be_empty')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('^#identifier#^');
}
}
Your up() function creates the database records and the down() function undo's them. If you end up changing anything in your migrations, make a new one instead of editing the old one.
In-depth documentation about migrations is located at the Laravel documentation.
Congratulations, you've made your own migration. Onto the last step, making the configuration page.
It's time for your admin page to come to life, well not really, but you get what I mean. Let's head over to your admin view and do some stuff there.
Start by creating a <form>. Inside of your form we can add options, text, buttons, etc.
<form action="" method="POST">
</form>
Now inside of your form, add a <div class="box"> to put your option in. You'll want to add a box header, body and footer.
<form action="" method="POST">
<div class="box">
<!-- header -->
<div class="box-header with-border">
</div>
<!-- body -->
<div class="box-body">
</div>
<!-- footer -->
<div class="box-footer">
</div>
</div>
</form>
A box with a header, body and footer will look similar to this. The header can be used for a title, body for the options and footer for a save button.
Example admin page box.
Inside of your header box you can add a title and a matching icon. Titles are not required, nor are icons. They do however make your admin page look better.
<h3 class="box-title"><i class='bx bx-cog' style='margin-right:5px'></i>Configuration</h3>
You might notice the <i class='..' style='..'>. We can add fancy icons from Boxicons since Blueprint automatically imports it into the admin panel after installation. Here are some example icons you can use;
<!-- Cog --> <i class='bx bx-cog' style='margin-right:5px'></i>
<!-- Palette --> <i class='bx bxs-palette' style='margin-right:5px'></i>
<!-- CPU/chip --> <i class='bx bxs-chip' style='margin-right:5px'></i>
Getting the correct HTML code for icons from Boxicons.
When adding other icons, make sure to add style='margin-right:5px' to them or the icon will be spaced incorrectly next to the header title.
The resulting code should look similar to this. You can tweak this however you like, it's your extension.
<!-- header -->
<div class="box-header with-border">
<h3 class="box-title"><i class='bx bxs-party' style='margin-right:5px'></i>My awesome title!</h3>
</div>
Now onto the body. This is the place to put all your options, fields, dropdowns, etc. For the sake of making this tutorial not 15 paragraphs, we'll keep it limited to two options.
In your box-body div, add two other divs.
<div class="row">
<div class="col-xs-12">
</div>
</div>
Inside of the <div class="col-xs-12"> div, add the following code to add a option title and short description.
<label class="control-label">Option Title</label>
<p class="text-muted small">Short Description</p>
Now add a <input> item in between of your title and short description.
<input type="text" required name="[1]" id="[1]" value="{{ $blueprint->dbGet("__identifier__", "[1]") }}" class="form-control"/>
Replace [1] with the id of your option. These need to be the same as the options written in your validation file. The end result should look similar to this:
<div class="row">
<div class="col-xs-12">
<label class="control-label">Color</label>
<input type="text" required name="color" id="color" value="{{ $blueprint->dbGet("__identifier__", "color") }}" class="form-control"/>
<p class="text-muted small">Tell us what your favorite color is!</p>
</div>
</div>
Adding multiple options is simple, you can have them next to each other or in a list.
<!-- next to each other -->
<div class="row">
<div class="col-xs-6">
<label class="control-label">Color</label>
<input type="text" required name="color" id="color" value="{{ $blueprint->dbGet("__identifier__", "color") }}" class="form-control"/>
<p class="text-muted small">Tell us what your favorite color is!</p>
</div>
<div class="col-xs-12">
<label class="control-label">Name</label>
<input type="text" required name="name" id="name" value="{{ $blueprint->dbGet("__identifier__", "name") }}" class="form-control"/>
<p class="text-muted small">What is your name?</p>
</div>
</div>
<!-- list -->
<div class="row">
<div class="col-xs-12">
<label class="control-label">Color</label>
<input type="text" required name="color" id="color" value="{{ $blueprint->dbGet("__identifier__", "color") }}" class="form-control"/>
<p class="text-muted small">Tell us what your favorite color is!</p>
<label class="control-label">Name</label>
<input type="text" required name="name" id="name" value="{{ $blueprint->dbGet("__identifier__", "name") }}" class="form-control"/>
<p class="text-muted small">What is your name?</p>
</div>
</div>
With your header and body done, you'll get something similar to the following code. Use this example to compare against your own code, don't copy paste it.
<form action="" method="POST">
<div class="box">
<!-- header -->
<div class="box-header with-border">
<h3 class="box-title"><i class='bx bxs-party' style='margin-right:5px'></i>My awesome title!</h3>
</div>
<!-- body -->
<div class="box-body">
<div class="row">
<div class="col-xs-6">
<label class="control-label">Color</label>
<input type="text" required name="color" id="color" value="{{ $blueprint->dbGet("__identifier__", "color") }}" class="form-control"/>
<p class="text-muted small">Tell us what your favorite color is!</p>
</div>
<div class="col-xs-12">
<label class="control-label">Name</label>
<input type="text" required name="name" id="name" value="{{ $blueprint->dbGet("__identifier__", "name") }}" class="form-control"/>
<p class="text-muted small">What is your name?</p>
</div>
</div>
</div>
<!-- footer -->
<div class="box-footer">
</div>
</div>
</form>
Now just add the following code to your footer to add a save button.
{{ csrf_field() }}
<button type="submit" name="_method" value="PATCH" class="btn btn-gray-alt btn-sm pull-right">Save</button>
And there you go, your very own (savable) configuration options for your extension admin page. We can't wait to see what you build from here.
Last modified 27d ago