# Creating Extensions

### <i class="fa-circle-question">:circle-question:</i>  What are Extensions?

Extensions is a feature that allows you to integrate custom features for Pixel Administration that haven't been implemented. You can use this for a variety of reasons like integrating new commands, running commands, creating pages, creating modals, and so on. There's almost an unlimited potential that comes with extensions, but that can also leave room for bad ones to come in too.

### <i class="fa-gear">:gear:</i> Who can use Extensions?

Right now all admins have access to extensions, but in the near future we'll allow certain ranks access to the extensions just in case some of them are more powerful and should be restricted. However the running commands part of extensions will still detect if that rank has access to commands if you set that up properly. So I don't think that should be an issue.

### <i class="fa-file-shield">:file-shield:</i> How is Extension Security handled?

The security for extensions is important. That's why we've created a system for detecting the bad ones to notify any admins of its existence since it's automatically off and disabled by default. But how do we detect the bad ones? Well it goes through a series of checks. You can find those checks in the ExtensionService (if you're able to get access to the MainModule). I'm just not providing it here because I'd have to update this documentation every time I update those checks, which might cause me to lose track of them. Anyways, the checks are bypassed if an extension is verified. But that's determined by the hash of an extension which can only be modified from a clone of Pixel Administration's MainModule Loader, and shouldn't be able to be bypassed on Pixel Administration's official loader.

### <i class="fa-file-circle-plus">:file-circle-plus:</i> How can I make an Extension?

{% hint style="warning" %}
Extensions are made using scripts. If you're not a scripter, please don't confuse yourself here. It's recommended that you just use the built in functions that come with the panel instead. If you need a feature that we're missing, recommend it to us on the [DevForum post](https://devforum.roblox.com/t/pixel-administration-open-alpha/3909608/).
{% endhint %}

You can create an extension by using the Extensions folder that comes with Pixel Administration. If it doesn't exist, just create it. Extensions are always module scripts, so lets create a module called "Extension" (this module can be named anything).\
\
![](https://1445404073-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuYzhZD76M7Bw9euOnkJi%2Fuploads%2FeHlw8zysOH5XrTO9SDQD%2Fimage.png?alt=media\&token=c24a3686-3800-44f7-9bec-f5f46e7d6256)

Open up your newly created extension and replace the contents with this.

{% code lineNumbers="true" %}

```lua
local Extension = {}

Extension.Name = script.Name -- The extension's ModuleScript name.
Extension.Version = "1.0.0" -- The extension's version.
Extension.Author = "Your Name" -- Who made this extension.
Extension.Description = "What this extension does." -- The extension's description.

local api = nil -- Variable for the ExtensionService API (will be set in :Init).

return Extension
```

{% endcode %}

Pretty simple, right? Now lets make this actually do something. We're going to add an Init function to the Extension to the ExtensionService knows what to do with it once it prepares the extensions.

{% code lineNumbers="true" %}

```lua
function Extension:Init(extensionAPI)
	api = extensionAPI -- Setup that 'api' variable earlier.

	api:Log("'" .. Extension.Name .. "' has been initialized!")
end
```

{% endcode %}

But why are we using api:Log() rather than print()? Well we're doing that because of user preference. Not everyone wants to see the logs of an extension, so it needs to be detected and used by the API to be disabled. Otherwise there's prints that you cannot remove.

<figure><img src="https://1445404073-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuYzhZD76M7Bw9euOnkJi%2Fuploads%2FWMJSdNsyliIVHYuI29gb%2Fimage.png?alt=media&#x26;token=599951b4-3940-4bcd-9ace-ad4e313bb86d" alt=""><figcaption></figcaption></figure>

You should see the initialized print in the console! Also there's a ton of APIs I'd like to show you that we can use for extensions. Lets take a look at them below before we continue so you understand how they work! Not every argument or function will be covered in this tutorial.

***

<details>

<summary><mark style="color:$warning;">Log(message:</mark> <mark style="color:$primary;">string</mark>, <mark style="color:$info;">isWarning:</mark> <mark style="color:$primary;">boolean)</mark></summary>

Allows you to log a message to the console. ExtensionService's equivalent of print() and allows for permission toggling.

<mark style="color:$info;">**message:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The message to print in a string.</mark>

<mark style="color:$info;">**isWarning:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The equivalent of warn(). Defaults to false if unset.</mark>

```lua
function api:Log(message, isWarning)
	if not checkPermission("Logging") then
		return
	end
		
	trackAPIUsage("Core", "Log", "Logging messages to console")
		
	safeLog(message, isWarning)
end
```

</details>

***

<details>

<summary><mark style="color:$warning;">GetDataStore(name:</mark> <mark style="color:$primary;">string)</mark></summary>

Allows you to get a datastore for the extension.

<mark style="color:$info;">**name:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The datastore's name of that extension to get.</mark>

```lua
function api:GetDataStore(name)
	if not checkPermission("DataStorage") then 
		return nil 
	end
	
	trackAPIUsage("DataStorage", "GetDataStore", "Accessing DataStore for persistence")
	
	local DataStoreService = game:GetService("DataStoreService")
	return DataStoreService:GetDataStore("PA_Extension_" .. extensionName .. "_" .. name)
end
```

</details>

<details>

<summary><mark style="color:$warning;">SetDataStore(name:</mark> <mark style="color:$primary;">string,</mark> <mark style="color:$info;">key:</mark> <mark style="color:$primary;">string, value)</mark></summary>

Allows you to set a datastore's key and value for the extension.

<mark style="color:$info;">**name:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The datastore's name of that extension to get.</mark>

<mark style="color:$info;">**key:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The datastore's key that should be adjusted.</mark>

<mark style="color:$info;">**value:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The new value for that key.</mark>

```lua
function api:SetDataStore(name, key, value)
    if not checkPermission("DataStorage") then 
        return false, "Permission denied: DataStorage"
    end

    trackAPIUsage("DataStorage", "SetDataStore", "Setting data in DataStore")

    local DataStoreService = game:GetService("DataStoreService")
    local dataStore = api:GetDataStore(name)
    if not dataStore then
        return false, "Failed to access DataStore"
    end

    local success, result = pcall(function()
        return dataStore:SetAsync(key, value)
    end)

    if success then
        return true, result
    else
        warn("[ExtensionService] SetDataStore failed for " .. name .. ": " .. tostring(result))
        return false, result
    end
end
```

</details>

<details>

<summary><mark style="color:$warning;">GetSetting(key:</mark> <mark style="color:$primary;">string,</mark> <mark style="color:$info;">default:</mark> <mark style="color:$primary;">string)</mark></summary>

Allows you to get a setting for the extension.

<mark style="color:$info;">**key:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The Extension's setting key to get.</mark>

<mark style="color:$info;">**default:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The fallback if something fails.</mark>

```lua
function api:GetSetting(key, default)
	if not checkPermission("Settings") then 
		return default 
	end
		
	trackAPIUsage("Settings", "GetSetting", "Reading extension settings")
		
	local dataStore = api:GetDataStore("Settings")
	if not dataStore then
		return default
	end
		
	local success, wrappedValue = pcall(function()
		return dataStore:GetAsync(key)
	end)

	if success and wrappedValue and type(wrappedValue) == "table" and wrappedValue.v ~= nil then
		return wrappedValue.v
	elseif success and wrappedValue then
		return wrappedValue
	end

	return default
end
```

</details>

<details>

<summary><mark style="color:$warning;">SetSetting(key:</mark> <mark style="color:$primary;">string,</mark> <mark style="color:$info;">value)</mark></summary>

Allows you to set a setting for the extension.

<mark style="color:$info;">**key:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The Extension's setting key to get.</mark>

<mark style="color:$info;">**value:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The new value for that key.</mark>

```lua
function api:SetSetting(key, value)
	if not checkPermission("Settings") then 
		return false 
	end
		
	trackAPIUsage("Settings", "SetSetting", "Saving extension settings")
		
	local dataStore = api:GetDataStore("Settings")
	if not dataStore then
		return false
	end
		
	local success
	if value == nil then
		success = pcall(dataStore.RemoveAsync, dataStore, key)
	else
		local wrappedValue = {v = value}
		success = pcall(dataStore.SetAsync, dataStore, key, wrappedValue)
	end
		
	return success
end
```

</details>

***

<details>

<summary><mark style="color:$warning;">RegisterCommand(commandData)</mark></summary>

Allows you to create a command for that extension. See [custom-commands](https://systech-corp-1.gitbook.io/pixel-administration/commands/custom-commands "mention") to learn how to make a custom command.

<mark style="color:$info;">**commandData:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The command's data and information.</mark>

```lua
function api:RegisterCommand(commandData)
	if not checkPermission("Commands") then 
		return false 
	end
		
	trackAPIUsage("Commands", "RegisterCommand", "Registering custom admin commands")
	commandData.ExtensionName = extensionName
	commandData.IsExtension = true

	local originalExecute = commandData.OnActivated
	if originalExecute then
		commandData.OnActivated = function(args, executor)
			local ext = getExt()
			if not ext or not ext.Enabled then
				return {Success = false, Message = "Extension is disabled"}
			end
			if not checkPermission("Commands") then
				return {Success = false, Message = "Extension does not have command permission"}
			end
			
			return originalExecute(args, executor)
		end
	end

	local ext = getExt()
	if ext then
		table.insert(ext.Commands, commandData)
	end
		

	table.insert(extensionCommands, commandData)
	if checkPermission("Logging") then
		safeLog("Registered command: " .. (commandData.CmdName or commandData.Name))
	end

	local cmdService = GetCommandService()
	if cmdService and type(cmdService.InvalidateCache) == "function" then
		cmdService:InvalidateCache()
	end

	if tick() - lastRefreshTime > DEBOUNCE_TIME then
		lastRefreshTime = tick()

		task.defer(function()
			local ReplicatedStorage = game:GetService("ReplicatedStorage")
			local Storage = ReplicatedStorage:FindFirstChild("PA - Storage")
			if Storage then
				local Events = Storage:FindFirstChild("Events")
				if Events then
					local refreshCommands = Events:FindFirstChild("RefreshCommands")
					if refreshCommands then
						if cmdService then
							cmdService:GetCommands()
						end

						refreshCommands:FireAllClients()
					end
				end
			end
		end)
	end

	return true
end
```

</details>

<details>

<summary><mark style="color:$warning;">RunCommand(commandName:</mark> <mark style="color:$primary;">string, arguments, executor:</mark> <mark style="color:$primary;">Player)</mark></summary>

Allows you to run a command by name and by passing through arguments and the executor.

<mark style="color:$info;">**commandData:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The command's data and information.</mark>

<mark style="color:$info;">**arguments:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">A table of arguments.</mark>

<mark style="color:$info;">**executor:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">Who ran the command.</mark>

```lua
function api:RunCommand(commandName, arguments, executor)
	if not checkPermission("Commands") then 
		return {Success = false, Message = "Extension does not have command permission"}
	end
	trackAPIUsage("Commands", "RunCommand", "Executing admin commands")

	local CommandService = Services.CommandService
	if not CommandService then
		return {Success = false, Message = "CommandService not available"}
	end

	return CommandService:RunCommand(commandName, arguments, executor)
end
```

</details>

***

<details>

<summary><mark style="color:$warning;">Notify(targetPlayer:</mark> <mark style="color:$primary;">Player,</mark> <mark style="color:$info;">title:</mark> <mark style="color:$primary;">string, message:</mark> <mark style="color:$primary;">string, duration:</mark> <mark style="color:$primary;">number)</mark></summary>

Allows you to display a notification to a certain player.

<mark style="color:$info;">**targetPlayer:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The player to notify.</mark>

<mark style="color:$info;">**title:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The title of the notification.</mark>

<mark style="color:$info;">**message:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The main message of the notification.</mark>

<mark style="color:$info;">**duration:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">How long the notification will stay visible.</mark>

```lua
function api:Notify(targetPlayer, title, message, duration)
	if not checkPermission("Notifications") then 
		return 
	end
		
	trackAPIUsage("UI", "Notify", "Sending notifications to players")
		
	if Services and Services.NotificationService then
		Services.NotificationService.NotifyPlayer(targetPlayer, title, message, duration)
	end
end
```

</details>

<details>

<summary><mark style="color:$warning;">NotifyAll(title:</mark> <mark style="color:$primary;">string, message:</mark> <mark style="color:$primary;">string, duration:</mark> <mark style="color:$primary;">number)</mark></summary>

Allows you to display a notification to all players.

<mark style="color:$info;">**title:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The title of the notification.</mark>

<mark style="color:$info;">**message:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The main message of the notification.</mark>

<mark style="color:$info;">**duration:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">How long the notification will stay visible.</mark>

```lua
function api:NotifyAll(title, message, duration)
	if not checkPermission("Notifications") then 
		return 
	end
		
	trackAPIUsage("UI", "NotifyAll", "Broadcasting notifications to all players")
		
	for _, player in ipairs(game:GetService("Players"):GetPlayers()) do
		api:Notify(player, title, message, duration)
	end
end
```

</details>

***

<details>

<summary><mark style="color:$warning;">IsWhitelisted(player:</mark> <mark style="color:$primary;">Player)</mark></summary>

Checks if a player can use Pixel Administration or not.

<mark style="color:$info;">**player:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The player to check the whitelist status for.</mark>

```lua
function api:IsWhitelisted(player)
	if not checkPermission("PlayerManagement") then 
		return false 
	end
		
	trackAPIUsage("Players", "IsWhitelisted", "Checking whitelist status")
		
	if Services and Services.RankService and type(Services.RankService.HasWhitelistedRank) == "function" then
		return Services.RankService:HasWhitelistedRank(player)
	end
		
	return false
end
```

</details>

***

<details>

<summary><mark style="color:$warning;">CreateRemoteEvent(name:</mark> <mark style="color:$primary;">string)</mark></summary>

Creates a remote event for the extension.

<mark style="color:$info;">**name:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The event's name.</mark>

```lua
function api:CreateRemoteEvent(name)
	if not checkPermission("Networking") then 
		return nil 
	end
		
	trackAPIUsage("Networking", "CreateRemoteEvent", "Creating RemoteEvents for communication")

	local ReplicatedStorage = game:GetService("ReplicatedStorage")
	local Storage = ReplicatedStorage:FindFirstChild("PA - Storage")
	if not Storage then return nil end

	local Events = Storage:FindFirstChild("Events")
	if not Events then return nil end

	local remote = Instance.new("RemoteEvent")
	remote.Name = "Ext_" .. extensionName .. "_" .. name
	remote.Parent = Events

	return remote
end
```

</details>

<details>

<summary><mark style="color:$warning;">CreateRemoteFunction(name:</mark> <mark style="color:$primary;">string)</mark></summary>

Creates a remote function for the extension.

<mark style="color:$info;">**name:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The function's name.</mark>

```lua
function api:CreateRemoteFunction(name)
	if not checkPermission("Networking") then 
		return nil 
	end
		
	trackAPIUsage("Networking", "CreateRemoteFunction", "Creating RemoteFunctions for communication")

	local ReplicatedStorage = game:GetService("ReplicatedStorage")
	local Storage = ReplicatedStorage:FindFirstChild("PA - Storage")
	if not Storage then return nil end

	local Events = Storage:FindFirstChild("Events")
	if not Events then return nil end

	local remote = Instance.new("RemoteFunction")
	remote.Name = "Ext_" .. extensionName .. "_" .. name
	remote.Parent = Events

	return remote
end
```

</details>

***

<details>

<summary><mark style="color:$warning;">RemovePage(pageName:</mark> <mark style="color:$primary;">string)</mark></summary>

Creates a page in Pixel Administration for the function.

<mark style="color:$info;">**pageName:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The page's name to remove.</mark>

```lua
function api:RemovePage(pageName)
	if not checkPermission("Pages") then 
		return false 
	end
	
	trackAPIUsage("UI", "RemovePage", "Removing custom pages from the admin panel")

	local ext = getExt()
	if not ext then return false end

	local pageIndex = nil
	for i, page in ipairs(ext.Pages) do
		if page.Name == pageName then
			pageIndex = i
			break
		end
	end

	if not pageIndex then
		if checkPermission("Logging") then
				safeLog("Page '" .. pageName .. "' not found", true)
	end
		return false
	end

	table.remove(ext.Pages, pageIndex)

	if checkPermission("Logging") then
		safeLog("Removed page: " .. pageName)
	end

	local ReplicatedStorage = game:GetService("ReplicatedStorage")
	local Storage = ReplicatedStorage:FindFirstChild("PA - Storage")
	if Storage then
		local Events = Storage:FindFirstChild("Events")
		if Events then
			local removePageRemote = Events:FindFirstChild("RemoveExtensionPage")
			if not removePageRemote then
				removePageRemote = Instance.new("RemoteEvent")
				removePageRemote.Name = "RemoveExtensionPage"
				removePageRemote.Parent = Events
			end
			removePageRemote:FireAllClients(pageName)
		end
	end

	return true
end
```

</details>

<details>

<summary><mark style="color:$warning;">CreatePage(config)</mark></summary>

Creates a page in Pixel Administration for the function.

<mark style="color:$info;">**config:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The page's data.</mark>

#### Example:

Provided due to complexity of the <mark style="color:$info;">**config**</mark> argument.

```lua
api:CreatePage({
	Name = "ComponentShowcase",
	Title = "Component Showcase",
	Icon = "rbxassetid://11293977875",
	Order = 10,
	Content = {
		{
			Type = "Header",
			Text = "Welcome to the Component Showcase!"
		},
		{
			Type = "Text",
			Content = "This extension demonstrates all available UI components and ExtensionService APIs. Explore each section below to see what's possible!"
		},
		{
			Type = "Section",
			Title = "Basic Components",
			Content = {
				{
					Type = "Header",
					Text = "Headers & Text"
				},
				{
					Type = "Text",
					Content = "This is regular text. It supports wrapping and can be used for descriptions, explanations, or any other content you need to display."
				},
				{
					Type = "Text",
					Content = "You can also use <b>rich text</b> with <font color='rgb(51, 112, 254)'>colors</font>, <i>italics</i>, and more!",
					RichText = true
				},
				{
					Type = "Divider"
				},
				{
					Type = "Text",
					Content = "Dividers help separate content into logical sections."
				}
			}
		},
		{
			Type = "Section",
			Title = "Interactive Elements",
			Content = {
				{
					Type = "Text",
					Content = "Click the buttons below to test different button variants and see notifications:"
				},
				{
					Type = "Button",
					Text = "Primary Button",
					Variant = "primary",
					OnClick = function(player)
						api:Notify(player, "Button Clicked", "You clicked the primary button!", 3)
						api:Log("Primary button clicked by " .. player.Name)
					end
				},
				{
					Type = "Button",
					Text = "Secondary Button",
					Variant = "secondary",
						OnClick = function(player)
					api:Notify(player, "Button Clicked", "You clicked the secondary button!", 3)
					end
				},
				{
					Type = "Divider"
				},
				{
					Type = "Button",
					Text = "Show Modal Demo",
					Variant = "primary",
					OnClick = function(player)
						Extension:ShowDemoModal(player)
					end
				}
			}
		},
		{
			Type = "Section",
			Title = "Badges",
			Content = {
				{
					Type = "Text",
					Content = "Badges are great for showing status, categories, or highlighting information:"
				},
				{
					Type = "Badge",
					Text = "Success",
					Color = Color3.fromRGB(51, 254, 112)
				},
				{
					Type = "Badge",
					Text = "Warning",
					Color = Color3.fromRGB(255, 200, 60)
				},
				{
					Type = "Badge",
					Text = "Error",
					Color = Color3.fromRGB(255, 60, 60)
				},
				{
					Type = "Badge",
					Text = "Info",
					Color = Color3.fromRGB(72, 144, 255)
				}
			}
		}
	}
})
```

```lua
function api:CreatePage(config)
	trackAPIUsage("UI", "CreatePage", "Creating custom pages in the admin panel")

	local prefix = extensionName:gsub("%s+", "_")
	assignActionIds(config.Content or {}, prefix .. "_" .. config.Name)

	local pageData = {
		Name = config.Name or "ExtensionPage",
		Title = config.Title or "Extension Page",
		Icon = config.Icon,
		Order = config.Order or 999,
		Content = config.Content or {},
		OnLoad = config.OnLoad,
		ExtensionName = extensionName
	}

	local ext = getExt()
	if ext then
		table.insert(ext.Pages, pageData)
	end

	if checkPermission("Logging") then
		safeLog("Created page: " .. pageData.Title)
	end

	if ext and ext.Enabled and checkPermission("Pages") then
		local ReplicatedStorage = game:GetService("ReplicatedStorage")
		local Storage = ReplicatedStorage:FindFirstChild("PA - Storage")
		if Storage then
			local Events = Storage:FindFirstChild("Events")
			if Events then
				local addPageRemote = Events:FindFirstChild("AddExtensionPage")
				if addPageRemote then
					task.defer(function()
						addPageRemote:FireAllClients(pageData)
					end)
				else
					safeLog("AddExtensionPage remote not found!", true)
				end
			end
		end
	end

	return pageData
end
```

</details>

<details>

<summary><mark style="color:$warning;">CreateModal(config)</mark></summary>

Creates a page in Pixel Administration for the function.

<mark style="color:$info;">**config:**</mark> <mark style="color:$info;"></mark><mark style="color:$info;">The modal's data.</mark>

#### Example:

Provided due to complexity of the <mark style="color:$info;">**config**</mark> argument.

```lua
api:CreateModal({
    Name = "DemoModal",
    Title = "Modal Demo",
    Content = "This is a demonstration modal created by the Component Showcase extension. Modals can contain custom content and multiple buttons with callbacks!",
    Buttons = {
        {
            Text = "Awesome!",
            Value = "awesome",
            Primary = true,
            OnActivated = function(player, value)
                api:Log("Player " .. player.Name .. " clicked 'Awesome!' with value: " .. tostring(value))
                api:Notify(player, "Modal Action", "You clicked Awesome! Great choice!", 5)
            end
        },
        {
            Text = "Close",
            Value = "close",
            Primary = false,
            OnActivated = function(player, value)
                api:Log("Player " .. player.Name .. " clicked 'Close' with value: " .. tostring(value))
                api:Notify(player, "Modal Action", "Modal closed.", 5)
            end
        }
    },
    Size = UDim2.new(0, 450, 0, 250)
})
```

```lua
function api:CreateModal(config)
	trackAPIUsage("UI", "CreateModal", "Creating modal dialogs for user interaction")

	local ReplicatedStorage = game:GetService("ReplicatedStorage")
	local Storage = ReplicatedStorage:FindFirstChild("PA - Storage")
	if not Storage then return nil end

	local Events = Storage:FindFirstChild("Events")
	if not Events then return nil end

	local modalEvent = Instance.new("RemoteEvent")
	modalEvent.Name = "Ext_" .. extensionName .. "_Modal_" .. (config.Name or "Unnamed")
	modalEvent.Parent = Events

	local buttons = {}
	if config.Buttons then
		for i, button in ipairs(config.Buttons) do
			if type(button) == "table" then
				local text = button.Text or button[1] or "Button"
				local value = button.Value ~= nil and button.Value or (button[2] ~= nil and button[2] or i)
				local onActivated = button.OnActivated or nil
				table.insert(buttons, {
					Text = text,
					Value = value,
					Primary = button.Primary or (i == 1),
					OnActivated = onActivated
				})
			end
		end
	end

	local modalData = {
		Name = config.Name or "Modal",
		Title = config.Title or "Extension Modal",
		Content = config.Content or "Modal content",
		Buttons = buttons,
		Size = config.Size or UDim2.new(0, 400, 0, 300),
		RemoteName = modalEvent.Name,
		RemoteEvent = modalEvent,
		ExtensionName = extensionName
	}

	local ext = getExt()
	if ext then
		table.insert(ext.Modals, modalData)
	end

	modalEvent.OnServerEvent:Connect(function(player, buttonValue)
		for _, btn in ipairs(modalData.Buttons) do
			if btn.Value == buttonValue and btn.OnActivated then
			local success, err = pcall(btn.OnActivated, player, buttonValue)
				if not success then
					warn("[ExtensionService] Error in OnActivated callback for modal " .. modalData.Name .. ": " .. tostring(err))
				end
			end
		end
	end)

	local addModalRemote = Events:FindFirstChild("AddExtensionModal")
	if addModalRemote and checkPermission("UICreation") then
		addModalRemote:FireAllClients(modalData)
	end

	return modalData
end
```

</details>

Phew! That took a while. Now you should understand how everything works, and how to build onto your extension. Feel free to come back here if you need to learn more! Now lets finish this off. All extensions support OnEnable/OnDisable events to detect when the user has disabled the extension. This is where you'd set everything up, or disable everything here. But most of the time the ExtensionService API should handle the shutdown of most things in the disable function that you used the API for like making pages. Those pages should hide when the extension or permission for Pages is disabled. So you wont have to worry about that. Everything else you should add some disable logic

{% code lineNumbers="true" %}

```lua
function Extension:OnEnable()
	api:Log("Extension enabled!")
end

function Extension:OnDisable()
	api:Log("Extension disabled!")
end
```

{% endcode %}

Also you may be wondering if you should use Init or OnEnable. Both of those work fine, so you can use either one and you shouldn't see any errors. Anyways that's the end of this tutorial! I'm really exited to see what you'll build with this.
