Roblox Animation

Import a Blender Animation into Studio With No Addon or Plugin

You import your animated FBX and the character shows up frozen, so you conclude Studio can't read animation from Blender without a plugin. It can — through the Animation Editor's own FBX importer, as long as the rig carries the canonical R15 names and the action is baked before it leaves Blender.

by Gustavo Cantino11 min readIntermediate

AI-assisted draft, reviewed, tested and edited by a human before publishing. See our Editorial Policy. · Reviewed by Gustavo Cantino, 9/20/26 Editorial Policy

Typographic plate with the Roblox Animation category in large type, the intermediate level below it, and a field of vertical lime green bars on a dark background.

Yes, you can. You can import a Blender animation into Roblox Studio with no addon in Blender and no plugin in Studio — the importer already ships inside the Animation Editor: select the rig, open the editor, click the three-dot menu and use the option to import from an FBX file. What "no addon, no plugin" costs you is manual work: the rig has to carry the names Studio expects, and the animation has to be baked into keyframes before it leaves Blender. Blender does neither of those things on its own in a default export, and that is where 90% of the attempts die.

Yes, you can — and what "no addon, no plugin" costs you

The mistake that makes most people conclude "it's impossible without a plugin" is walking in through the wrong door. Studio's 3D model importer (Avatar tab, Import 3D button) brings in mesh, texture, skinning and bones — and throws the animation away. You import your animated character, it shows up standing perfectly still, and the natural conclusion is that Studio can't read animation out of an FBX. It can. Just not there.

The right path is a different piece of Studio: the Animation Editor. It has its own FBX importer, and that importer creates no mesh at all — it reads the bone rotation curves out of the file and writes keyframes onto the timeline of the rig you already have selected. That is why the rig has to exist first, and with the right names: the importer matches an FBX bone to a rig part by name, string by string. If it finds no match at all, you get a short error and zero keyframes. There is no middle ground, no partial import you can fix later in the editor.

The second expensive mistake is thinking animation lives in a file. It doesn't:

Script in ServerScriptService
-- WRONG: there is no local file path for an animation
local anim = Instance.new("Animation")
anim.AnimationId = "rbxasset://animations/myAnim.fbx"

AnimationId only accepts rbxassetid:// pointing at an asset published under your account or your group. The FBX is raw material, not the finished product. The finished product is a published KeyframeSequence, and the only way to produce one without a plugin is by going through the Animation Editor.

Counting the damage in time: a 40-frame walk cycle takes 20 to 40 minutes to animate in Blender. If you only discover the bone-name problem at import time, you don't lose the 30 minutes of animation — you lose the whole afternoon guessing which of three things is broken (rig, bake or export), because the importer's error message doesn't tell you which one it is. The steps below take all three off the table at once.

The install-nothing path, in 7 steps

1. Take the rig from Studio, don't build your own. In Studio: Avatar tab → Rig Builder → R15 Rig (or Rthro Rig, if that's the body your game uses). It spawns into the Workspace with the canonical names already in place: HumanoidRootPart, LowerTorso, UpperTorso, Head, LeftUpperArm, LeftLowerArm, LeftHand and the equivalents on the right side and on the legs. Those 16 names are the contract. If you rig from scratch in Blender and name the bone "arm_left", the importer has no way of knowing that thing is supposed to be LeftUpperArm.

2. Export that rig to Blender. Right-click the rig model in the Explorer → Export Selection → save it as .fbx. You just generated a file with exactly the hierarchy and exactly the names the importer is going to want back. This is the trick that makes the addon-free workflow viable: instead of you adapting Blender to Roblox, you let Roblox dictate the skeleton.

3. Import it into Blender and don't touch the scale. File → Import → FBX, default options. Object scale at 1.0, no creative "Apply Scale". Studio's animation importer reads bone rotation; bone scale is ignored and translation is only respected on the root. If you scale the armature up so it looks the size you like in the viewport, the animation still works, but you'll start believing in things that don't exist in Studio.

4. Animate — armature only, in Pose Mode. Frame 1 always holds the rest pose or an explicitly keyed starting pose. Set the scene FPS before you start: 30 is the safe default, because the Animation Editor works in seconds and a 24 fps scene gives you ugly fractional times (frame 13 = 0.5416s) that are painful to line up on the timeline later.

5. Bake the action. This is the step that replaces the addon. With the armature selected in Pose Mode, select all bones (A) and go to Object → Animation → Bake Action. Check Visual Keying, check Clear Constraints, leave Clear Parents unchecked, check Only Selected Bones, Frame Step = 1, and set the range from the first to the last frame of your animation. What this does: it turns IK, constraints, copy rotation and whatever crazy rig you assembled into pure per-frame rotation keyframes. FBX carries no constraints. Without the bake, your animation leaves Blender with the IK bones animated and the deform bones standing still — in Studio you see the character in a trembling T-pose.

6. Export the FBX with the right preset. File → Export → FBX. Limit to: Selected Objects. Object Types: Armature only. Transform: Apply Scalings = All Local, Forward = -Z Forward, Up = Y Up. Armature: Add Leaf Bones unchecked (leaf bones create ghost bones with an _end suffix that pollute the import). Bake Animation: checked, Key All Bones checked, NLA Strips unchecked, All Actions unchecked, Force Start/End Keying checked, Sampling Rate = 1, Simplify = 0. Simplify at 0 is annoying because it writes a keyframe on every single frame (a 60-frame animation becomes 60 keyframes per bone), but Simplify above 0 is the single biggest cause of "the animation arrived, it's just wrong".

7. Import it into Studio. Select the same R15 rig from step 1 in the Workspace, open the Animation Editor (Avatar tab → Animation Editor), and give the animation a name. In the editor panel, three-dot menu → Import → the option to import from an FBX file. Pick the .fbx you just exported. The keyframes show up on the timeline. Tweak whatever you want right there, then three dots → Publish to Roblox. That gives you the ID to paste into AnimationId.

Total cost in money: $0. Blender is free, publishing an animation consumes no Robux, and the importer is native. Cost in time, with the rig already validated: 3 to 5 minutes between the bake and the animation playing in Studio.

The name validator you run before you export

The artifact below is a ModuleScript that compares the parts of your rig against the canonical R15 list and prints, by name, what's missing and what's extra. Run it before step 2 if you built the rig by hand, and again after any import back into Studio.

Put the ModuleScript in ServerScriptService with the name RigNameCheck, select your rig in the Explorer and run this in the Studio Command Bar:

Studio Command Bar
require(game.ServerScriptService.RigNameCheck).check(game.Selection:Get()[1])

The output tells you in two lines whether the import is going to work. If missing comes back empty, the importer will find every bone. If it comes back with five names, those are exactly the five bones you have to rename in Blender before exporting.

The second artifact is the export preset itself, written out as JSON next to your .blend: every checkbox from steps 5 and 6, in one file, so the next animation doesn't depend on you remembering that Simplify has to be 0.

The mistake that burns your afternoon: ".001" at the end of the bone name

Blender won't allow two objects with the same name. If you duplicated the armature, imported the FBX twice, or pasted an arm in from another file, it renames things on its own: LeftUpperArm.001. Studio's importer compares exact strings — LeftUpperArm.001 is not LeftUpperArm, and that bone simply doesn't come in. Classic result: the animation imports, the body moves and one arm stays frozen. You'll swear it's a bake problem.

Before exporting, open Blender's Outliner in Blender File mode, expand Armatures and read the names. If there's a suffix, clean it:

Blender: Scripting tab > Python console (not an addon, it's the native console)
-- python: strips .001, .002 suffixes from the selected armature's bones
import bpy, re
for b in bpy.context.object.data.bones:
    b.name = re.sub(r"\.\d+$", "", b.name)

The second expensive mistake at this stage isn't technical, it's asset ownership. If the game belongs to a group and you publish the animation under your personal account, the AnimationId loads fine in Studio (you're the owner, you have permission) and fails on the published server, with the character in a T-pose for everyone except you. At Publish to Roblox time, pick the same creator that owns the experience. If you already published it wrong, republish under the correct owner and swap the ID: there is no transferring an animation between accounts.

What to measure next

Measure three things on this cycle, because they tell you whether the addon-free workflow is sustainable for your project or whether it's worth going looking for a tool:

  1. Cycle time from bake → import → seen in Studio. If it goes over 10 minutes per attempt, the problem isn't the importer, it's your rig still not being canonical. Run the validator and fix it at the source.
  2. Keyframe count per animation. With Sampling Rate 1 and Simplify 0, a 2-second animation at 30 fps becomes 60 keyframes per bone — 16 bones, nearly a thousand keyframes. That publishes fine, but it's unbearable to hand-edit in the Animation Editor. If you plan to tweak it in Studio afterwards, export with fewer frames and refine there.
  3. The animation playing on a published server, not in Play Solo. Asset ownership errors only show up outside Studio.

The next step is getting out of the editor and making the Humanoid play this in-game: load the Animation into the Animator, set Priority and replace the character's default run animation.

Ready-to-use artifact

ModuleScript named RigNameCheck in ServerScriptService
lua
--!strict
-- RigNameCheck — validates whether a rig has the names that the Animation
-- Editor's FBX importer expects. Run it BEFORE exporting the FBX to Blender
-- and AFTER any import, from the Command Bar:
--
--   require(game.ServerScriptService.RigNameCheck).check(game.Selection:Get()[1])
--
-- Returns true if the rig is ready for import.

local RigNameCheck = {}

-- Canonical R15 names (Rig Builder → R15 Rig).
-- The importer matches an FBX bone to a rig part by EXACT NAME.
local R15_PARTS: { string } = {
	"HumanoidRootPart",
	"LowerTorso", "UpperTorso", "Head",
	"LeftUpperArm", "LeftLowerArm", "LeftHand",
	"RightUpperArm", "RightLowerArm", "RightHand",
	"LeftUpperLeg", "LeftLowerLeg", "LeftFoot",
	"RightUpperLeg", "RightLowerLeg", "RightFoot",
}

local function join(list: { string }): string
	return (#list == 0) and "(none)" or table.concat(list, ", ")
end

-- Checks the rig's parts and Motor6D. Prints a report and returns the verdict.
function RigNameCheck.check(rig: Instance?): boolean
	if rig == nil or not rig:IsA("Model") then
		warn("[RigNameCheck] Select the rig Model in the Explorer before running.")
		return false
	end

	local found: { [string]: boolean } = {}
	local motors = 0

	for _, d in ipairs(rig:GetDescendants()) do
		if d:IsA("BasePart") then
			found[d.Name] = true
		elseif d:IsA("Motor6D") then
			motors += 1
		end
	end

	local missing: { string } = {}
	for _, name in ipairs(R15_PARTS) do
		if not found[name] then
			table.insert(missing, name)
		end
	end

	local extra: { string } = {}
	for name in pairs(found) do
		if table.find(R15_PARTS, name) == nil then
			table.insert(extra, name)
		end
	end
	table.sort(extra)

	print(string.format("[RigNameCheck] rig=%s | parts=%d/16 | Motor6D=%d",
		rig.Name, 16 - #missing, motors))
	print("[RigNameCheck] missing: " .. join(missing))
	print("[RigNameCheck] off-standard (rename or ignore): " .. join(extra))

	if rig:FindFirstChildOfClass("Humanoid") == nil then
		warn("[RigNameCheck] no Humanoid: the Animation Editor will not open on this Model.")
	end
	if motors < 15 then
		warn("[RigNameCheck] fewer than 15 Motor6D: a joint is missing, the animation will not deform everything.")
	end

	local ok = (#missing == 0) and (motors >= 15)
	print(ok and "[RigNameCheck] OK: you can export/import."
		or "[RigNameCheck] FIX the names above before exporting the FBX.")
	return ok
end

return RigNameCheck
Save as export-fbx-roblox.json next to your .blend file
json
{
  "name": "Blender FBX export preset for Roblox's Animation Editor",
  "scene": {
    "fps": 30,
    "frame_start": 1,
    "unit_scale": 1.0
  },
  "before_exporting": {
    "bake_action": true,
    "bake_visual_keying": true,
    "bake_clear_constraints": true,
    "bake_only_selected_bones": true,
    "bake_frame_step": 1,
    "bone_names_without_suffix": true
  },
  "export_fbx": {
    "limit_to_selected_objects": true,
    "object_types": ["ARMATURE"],
    "apply_scalings": "FBX_ALL_LOCAL",
    "forward": "-Z",
    "up": "Y",
    "add_leaf_bones": false,
    "primary_bone_axis": "Y",
    "secondary_bone_axis": "X",
    "bake_animation": true,
    "key_all_bones": true,
    "nla_strips": false,
    "all_actions": false,
    "force_start_end_keying": true,
    "sampling_rate": 1.0,
    "simplify": 0.0
  },
  "import_in_studio": {
    "where": "Avatar > Animation Editor > three-dot menu > Import > FBX file",
    "rig_selected_beforehand": true,
    "publish_as": "the same creator (account or group) that owns the experience"
  }
}
In this article

Keep reading