> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bloxiana.lol/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Bulk Import

> Send outfits from Roblox Studio into the Bloxiana dashboard in bulk

Bulk importing sends outfit data from Roblox Studio straight into the Bloxiana dashboard, where you can review it, configure it, and queue it (for example, as [Pinterest posts](/docs/features/pinterest)). It's the fastest way to bring in dozens or hundreds of outfits at once instead of adding them one by one.

## Step 1: Create a webhook

In the Bloxiana dashboard, create a new webhook for bulk importing outfits, then copy its webhook URL. You'll paste this into the Roblox Studio script in the next step.

<Frame>
  <img src="https://mintcdn.com/bloxiana/XJRgaYYQSVrLL_Dz/images/pinterest/bulk-webhook.png?fit=max&auto=format&n=XJRgaYYQSVrLL_Dz&q=85&s=416bf25b1491332a6710ced9ef8fe7d4" alt="Creating a bulk import webhook in the Bloxiana dashboard" width="1280" height="905" data-path="images/pinterest/bulk-webhook.png" />
</Frame>

## Step 2: Fill in the script values

Before running the script, update these values near the top. Keep all replacement text inside the quotation marks.

| Value                                  | What to set it to                                                                 |
| -------------------------------------- | --------------------------------------------------------------------------------- |
| `PASTE_YOUR_BLOXIANA_WEBHOOK_URL_HERE` | The webhook URL you just copied                                                   |
| `ENTER_TITLE`                          | The Pinterest pin title to use for every imported outfit                          |
| `ENTER_DESCRIPTION`                    | The Pinterest pin description to attach                                           |
| `ENTER_LINK`                           | The link users are sent to when they click the pin (your game, group, or profile) |
| `DEFAULT_TAGS`                         | Your tags, for example `{ "roblox", "outfits", "robloxavatar" }`                  |

Leave `MAX_IMPORT_ITEMS_PER_REQUEST = 100` as-is unless you're told otherwise.

## Step 3: Run the script in Roblox Studio

Paste the updated script into the Roblox Studio **Command Bar** and press Enter. The script looks for an `Outfits` folder in `Workspace` or `ServerStorage` and sends every outfit it finds.

<Frame>
  <img src="https://mintcdn.com/bloxiana/XJRgaYYQSVrLL_Dz/images/pinterest/bulk-studio-outfits.png?fit=max&auto=format&n=XJRgaYYQSVrLL_Dz&q=85&s=19a9c8f872c1deff13c012cf043b1e60" alt="The Outfits folder in Roblox Studio" width="580" height="521" data-path="images/pinterest/bulk-studio-outfits.png" />
</Frame>

<Frame>
  <img src="https://mintcdn.com/bloxiana/XJRgaYYQSVrLL_Dz/images/pinterest/bulk-studio-cmdbar.png?fit=max&auto=format&n=XJRgaYYQSVrLL_Dz&q=85&s=6b1856bae2f10d06398d9629f0b3567e" alt="Pasting the script into the Studio Command Bar" width="288" height="69" data-path="images/pinterest/bulk-studio-cmdbar.png" />
</Frame>

<Accordion title="Roblox Studio import script">
  ```lua theme={null}
  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
  )
  ```
</Accordion>

## Step 4: Load your imports

Go back to the Bloxiana webhook import page and press **Refresh**. Your successful imports appear under **Pending Imports**.

<Frame>
  <img src="https://mintcdn.com/bloxiana/XJRgaYYQSVrLL_Dz/images/pinterest/bulk-pending-imports.png?fit=max&auto=format&n=XJRgaYYQSVrLL_Dz&q=85&s=0deb166a89dc08c137fd5017079237b1" alt="Pending imports on the Bloxiana webhook page" width="1280" height="879" data-path="images/pinterest/bulk-pending-imports.png" />
</Frame>

Press **Load** on the imports you want to bring into Bloxiana.

<Frame>
  <img src="https://mintcdn.com/bloxiana/XJRgaYYQSVrLL_Dz/images/pinterest/bulk-load-imports.png?fit=max&auto=format&n=XJRgaYYQSVrLL_Dz&q=85&s=ed3e334472f26adbd066badebc6a4dc0" alt="Loading imports into Bloxiana" width="1280" height="872" data-path="images/pinterest/bulk-load-imports.png" />
</Frame>

## Troubleshooting

If the import doesn't work, check that:

* The webhook URL was pasted correctly.
* Your outfit folder is named `Outfits`, or the script is updated to match your folder name.
* Your outfits are inside `Workspace` or `ServerStorage`.
* Each outfit has valid `HumanoidDescription` assets.
* HTTP requests are enabled in Roblox Studio.
* You pressed Enter after pasting the script into the Command Bar.

Still stuck? Join the Bloxiana support server for help.
