How to Bulk Import Outfits from Roblox Studio to the Bloxiana Dashboard

How to Bulk Import Outfits from Roblox Studio to the Bloxiana Dashboard

Utilities
6 min read
Last updated: May 28, 2026

This guide will show you how to bulk import outfits from your Roblox games into the Bloxiana Dashboard when creating Pinterest posts. Bulk importing allows you to quickly send outfit data from Roblox Studio into Bloxiana, where you can review the imports, configure them, and queue them to be posted through our system.

Step 1: Create a Webhook in Bloxiana

Create a new webhook that will be used for bulk importing outfits from Roblox Studio.

Once the webhook is created, copy the webhook URL. You will need to paste this URL into the Roblox Studio script.

enter image description here

Step 2: Prepare the Roblox Studio Script

Before running the script, you must update a few values.

Things you may need to change:

  • Replace PASTE_YOUR_BLOXIANA_WEBHOOK_URL_HERE with the webhook URL you created on the Bloxiana dashboard.
  • Replace ENTER_TITLE with the Pinterest pin title you want all imported outfits to use.
  • Replace ENTER_DESCRIPTION with the Pinterest pin description you want attached to the imports.
  • Replace ENTER_LINK with the external link users should be sent to when they click the Pinterest pin, such as your Roblox game, group, or profile.
  • Leave MAX_IMPORT_ITEMS_PER_REQUEST = 100 as-is unless you are specifically told to change it.
  • Make sure all replacement text stays inside the quotation marks.

For example:

local DEFAULT_TAGS = { "roblox", "outfits", "robloxavatar" }

Step 4: Paste This Script into Roblox Studio

The script below includes the values that you need to update, after updating paste the script into your ROBLOX Studio Command Bar.

enter image description here
enter image description here

local ServerStorage = game:GetService("ServerStorage")
local HttpService = game:GetService("HttpService")
local Workspace = game:GetService("Workspace")

-- Paste the full Bloxiana webhook URL here.
-- Example:
-- https://bloxiana.lol/api/guilds/YOUR_GUILD_ID/pinterest/import-webhook/pwh_xxxxx
local ENDPOINT_URL = "PASTE_YOUR_BLOXIANA_WEBHOOK_URL_HERE"

local MAX_IMPORT_ITEMS_PER_REQUEST = 100

local DEFAULT_TITLE = "ENTER_TITLE"
local DEFAULT_DESCRIPTION = "ENTER_DESCRIPTION"
local DEFAULT_LINK = "ENTER_LINK"
local DEFAULT_TAGS = { "roblox" } -- to add more, do like: { "roblox", "tag2", "tag3" }

--------------------------------------------------------------------------------
-- OUTFITS FOLDER SETUP
-- This looks for the "Outfits" folder in Workspace first, then ServerStorage.
-- ADVICE: If your outfits folder uses a different name (e.g., "MyOutfits" or "ShopFits"),
-- change the string inside the FindFirstChild quotes below to match it exactly!
--------------------------------------------------------------------------------
local outfitsFolder = Workspace:FindFirstChild("Outfits") or ServerStorage:FindFirstChild("Outfits")
if not outfitsFolder then
	warn("No 'Outfits' folder found in Workspace or ServerStorage. Please create one.")
	return
end

if ENDPOINT_URL == "" or ENDPOINT_URL == "PASTE_YOUR_BLOXIANA_WEBHOOK_URL_HERE" then
	warn("Missing Bloxiana webhook URL. Set ENDPOINT_URL first.")
	return
end

local allOutfitsData = {}

local function clampNumber(value: number, minValue: number, maxValue: number): number
	return math.max(minValue, math.min(maxValue, value))
end

local function formatNumber(value: number): number
	return tonumber(string.format("%.2f", value)) or value
end

local function colorToHex(color: Color3): string
	local r = math.floor(clampNumber(color.R, 0, 1) * 255 + 0.5)
	local r_string = string.format("%02X", r)
	local g = math.floor(clampNumber(color.G, 0, 1) * 255 + 0.5)
	local g_string = string.format("%02X", g)
	local b = math.floor(clampNumber(color.B, 0, 1) * 255 + 0.5)
	local b_string = string.format("%02X", b)

	return "#" .. r_string .. g_string .. b_string
end

local function addAssetId(assetIds: { number }, assetId: number)
	if assetId ~= 0 then
		table.insert(assetIds, assetId)
	end
end

local function processHumanoidDescription(humanoidDescription: HumanoidDescription, rigType: Enum.HumanoidRigType)
	local assetIds: { number } = {}

	addAssetId(assetIds, humanoidDescription.Shirt)
	addAssetId(assetIds, humanoidDescription.Pants)
	addAssetId(assetIds, humanoidDescription.GraphicTShirt)

	addAssetId(assetIds, humanoidDescription.Head)
	addAssetId(assetIds, humanoidDescription.Torso)
	addAssetId(assetIds, humanoidDescription.LeftArm)
	addAssetId(assetIds, humanoidDescription.RightArm)
	addAssetId(assetIds, humanoidDescription.LeftLeg)
	addAssetId(assetIds, humanoidDescription.RightLeg)
	addAssetId(assetIds, humanoidDescription.Face)

	local accessories = humanoidDescription:GetAccessories(true)
	for _, accessoryInfo in ipairs(accessories) do
		if accessoryInfo.AssetId ~= 0 then
			table.insert(assetIds, accessoryInfo.AssetId)
		end
	end

	local assetIdStrings: { string } = {}
	for _, assetId in ipairs(assetIds) do
		table.insert(assetIdStrings, tostring(assetId))
	end

	if #assetIdStrings == 0 then
		return nil
	end

	local rigTypeString = (rigType == Enum.HumanoidRigType.R6) and "R6" or "R15"
	
	-- Scales only matter/exist safely for R15 rigs
	local scalesData = nil
	if rigTypeString == "R15" then
		scalesData = {
			height = formatNumber(clampNumber(humanoidDescription.HeightScale, 0.9, 1.05)),
			width = formatNumber(clampNumber(humanoidDescription.WidthScale, 0.7, 1)),
			head = formatNumber(clampNumber(humanoidDescription.HeadScale, 0.95, 1)),
			proportion = formatNumber(clampNumber(humanoidDescription.ProportionScale, 0, 1)),
		}
	end

	return {
		assetIds = table.concat(assetIdStrings, ","),
		rigType = rigTypeString,
		bodyColorHex = colorToHex(humanoidDescription.TorsoColor),
		scales = scalesData,
	}
end

local function getOutfitType(outfitSetCount: number): string
	if outfitSetCount == 1 then
		return " (Solo)"
	elseif outfitSetCount == 2 then
		return " (Duo)"
	elseif outfitSetCount >= 3 then
		return " (Trio)"
	end

	return ""
end

local function processOutfitHumanoid(outfitHumanoid: Humanoid, categoryName: string)
	local outfitSets = {}
	local humanoidDescriptions: { HumanoidDescription } = {}

	for _, child in ipairs(outfitHumanoid:GetChildren()) do
		if child:IsA("HumanoidDescription") then
			table.insert(humanoidDescriptions, child)
		end
	end

	table.sort(humanoidDescriptions, function(a, b)
		return a.Name < b.Name
	end)

	for _, humanoidDescription in ipairs(humanoidDescriptions) do
		if #outfitSets >= 4 then
			break
		end

		local outfitSet = processHumanoidDescription(humanoidDescription, outfitHumanoid.RigType)
		if outfitSet then
			table.insert(outfitSets, outfitSet)
		end
	end

	if #outfitSets == 0 then
		warn("Skipped outfit with no valid HumanoidDescription assets: " .. outfitHumanoid:GetFullName())
		return
	end

	local outfitType = getOutfitType(#outfitSets)

	table.insert(allOutfitsData, {
		category = categoryName .. outfitType,
		title = DEFAULT_TITLE,
		description = DEFAULT_DESCRIPTION,
		link = DEFAULT_LINK,
		tags = DEFAULT_TAGS,
		outfitSets = outfitSets,
	})
end

for _, descendant in ipairs(outfitsFolder:GetDescendants()) do
	if descendant:IsA("Humanoid") then
		if string.find(descendant.Name:lower(), "template") then
			continue
		end

		local parent = descendant.Parent
		local categoryName = "Girl"

		if parent and parent:IsA("Folder") and parent ~= outfitsFolder then
			categoryName = parent.Name
		end

		processOutfitHumanoid(descendant, categoryName)
	end
end

if #allOutfitsData == 0 then
	warn("No outfits were found to send.")
	return
end

local function sendBatch(batchData, batchIndex: number, totalBatches: number): boolean
	local jsonString = HttpService:JSONEncode(batchData)

	local success, response = pcall(function()
		return HttpService:RequestAsync({
			Url = ENDPOINT_URL,
			Method = "POST",
			Headers = {
				["Content-Type"] = "application/json",
			},
			Body = jsonString,
		})
	end)

	if not success then
		warn("Failed to send batch " .. batchIndex .. "/" .. totalBatches .. ": " .. tostring(response))
		return false
	end

	if not response.Success then
		warn(
			"Server rejected batch "
				.. batchIndex
				.. "/"
				.. totalBatches
				.. " | Status: "
				.. tostring(response.StatusCode)
				.. " | Body: "
				.. tostring(response.Body)
		)
		return false
	end

	print("Sent batch " .. batchIndex .. "/" .. totalBatches .. " successfully. Response: " .. tostring(response.Body))
	return true
end

local totalBatches = math.ceil(#allOutfitsData / MAX_IMPORT_ITEMS_PER_REQUEST)
local successfulBatches = 0

for batchIndex = 1, totalBatches do
	local batch = {}
	local startIndex = ((batchIndex - 1) * MAX_IMPORT_ITEMS_PER_REQUEST) + 1
	local endIndex = math.min(startIndex + MAX_IMPORT_ITEMS_PER_REQUEST - 1, #allOutfitsData)

	for index = startIndex, endIndex do
		table.insert(batch, allOutfitsData[index])
	end

	if sendBatch(batch, batchIndex, totalBatches) then
		successfulBatches += 1
	end

	task.wait(0.5)
end

print(
	"Finished sending Pinterest webhook imports. "
		.. successfulBatches
		.. "/"
		.. totalBatches
		.. " batches succeeded. Total outfits: "
		.. #allOutfitsData
)

Step 5: Refresh the Bloxiana Webhook Import Page

Go back to the Bloxiana Webhook Page.

Press Refresh.

Your successful imports should appear under Pending Imports.

enter image description here

From there, press Load on the imports you want to bring into Bloxiana.

enter image description here

Troubleshooting

If the import does not work, check the following:

  • Make sure the webhook URL was pasted correctly.
  • Make sure the outfit folder is named Outfits, or update the script to match your folder name.
  • Make sure your outfits are inside Workspace or ServerStorage.
  • Make sure each outfit has valid HumanoidDescription assets.
  • Make sure HTTP requests are enabled in Roblox Studio.
  • Make sure you pressed Enter after pasting the script into the Command Bar.

Happy Importing!

Feel free to join our support server if you need help.

Featured Guides

Handpicked guides to get you started.

View all