How to tell the RunContext of a Script at runtime?

Telling the difference between a Script and a LocalScript at runtime is easy, simply do the following;

if myItem:IsA("Script") then
	print("Script")
elseif myItem:IsA("LocalScript") then
	print("LocalScript")
end

However with the introduction of RunContext, a Script could be made to run both on client side like LocalScript or on the server side like legacy Script.

Telling the difference between scripts with different RunContext is not possible in the intuitive way;

if myItem:IsA("Script") then
	if myItem.RunContext == Enum.RunContext.Client then
		print("Client Script")
	elseif myItem.RunContext == Enum.RunContext.Server then
		print("Server Script")
	elseif myItem.RunContext == Enum.RunContext.Legacy then
		print("Legacy Script")
	end
end

This code will fail at runtime with the following permissions error:

The current identity (2) cannot RunContext (lacking permission 1)

So what is the best way to distinguish scripts by their run context at runtime?

EDIT: I want to emphasize that I need to check this from OUTSIDE the script, not from the inside. As @metatablecatmaid correctly points out, we can easily check if we are running on server or client with RunService:IsServer and RunService:IsClient.

RunService:IsServer or RunService:IsClient

Those will only work from INSIDE the script in question. I want to know from the OUTSIDE, as in:

local myItem:Script = someInstance:FindFirstChild("RandomItem")

if myItem:IsA("Script") then
	if myItem.RunContext == Enum.RunContext.Client then
		-- put the script in RepliactedStorage
	elseif myItem.RunContext == Enum.RunContext.Server then
		-- put the script in ServerStorage
	end
end

What exactly is your usecase where you need to know it from outside a script? Would it not just be easier to use the legacy system at that point

The benefits of using RunContext is not apparent before you actually get used to them, and even before that I would find Legacy scripts to be very counter intuitive as they will force you to use arbitrary locations in the datamodel. Creating anything but the most trivial of stand alone modules or packages based on Legacy scripts is very cumbersome. The module needs to bootstrap and put the relevant sub scripts in a bunch of locations. It complicates and slows down both the development as well as the runtime of the resulting solution.

Contrast this with using RunContext; the package has one main Client Script and one main Server Script. They run wherever you put them. If you don’t want them to run you simply set Enabled=false on them and enable them when you need them to run. If your module is larger in size you can split up the code in sub-scripts. There is no need to think about whire they are placed, just avoid the Starter folders.

The use case for knowing which is which from outside the script is of course if you want to make a plugin architecture. You can iterate over the instances in a folder and depending on their type you do different things. This is a common pattern I find to be very useful for many different situations.

You can use the GetDescendants method of the Workspace object to get a list of all descendant objects of the Workspace , and then iterate through the list and check the ClassName and Name properties of each object to identify the script you are interested in.

local workspace = game:GetService("Workspace")
local scripts = workspace:GetDescendants()

for _, obj in ipairs(scripts) do
  if obj.ClassName == "Script" and obj.Name == "MyScript" then
    -- Found the script, now check its run context
    if obj.RunContext == Enum.RunContext.Client then
      print("Client Script")
    elseif obj.RunContext == Enum.RunContext.Server then
      print("Server Script")
    elseif obj.RunContext == Enum.RunContext.Legacy then
      print("Legacy Script")
    end
  end
end

Or use the GetChildren method of the Workspace object to get a list of children of the Workspace , and then recursively search through the children of each object in the list until you find the script you are looking for.

local function findScript(parent, name)
  for _, obj in ipairs(parent:GetChildren()) do
    if obj.ClassName == "Script" and obj.Name == name then
      -- Found the script, now check its run context
      if obj.RunContext == Enum.RunContext.Client then
        print("Client Script")
      elseif obj.RunContext == Enum.RunContext.Server then
        print("Server Script")
      elseif obj.RunContext == Enum.RunContext.Legacy then
        print("Legacy Script")
      end
      return obj
    end
    local script = findScript(obj, name)
    if script then
      return script
    end
  end
end

local workspace = game:GetService("Workspace")
findScript(workspace, "MyScript")

Thanks for your reply, however you are not adressing the question. The question is how can tell two scripts apart based on the RunContext property alone. I know how to iterate the scene for descendants, that is not related to the question. I also know that I can encode the script type in the name of the script which would be a convention that imposes unwanted restrictions on my workflow.

It almost feels like you asked GPT3 for an answer…

1 Like

As @metatablecatmaid correctly points out, we can easily check if we are running on server or client with RunService:IsServer and RunService:IsClient.
But I was looking for a way to tell this from outside, so the best and only working way I found is to do the following:
local function isServerScript(myItem)
local success, message = pcall(function()
return myItem.RunContext == Enum.RunContext.Client
end)
return not success
end

I didn’t read the entire word salad, but it looks like you’re trying to use this for a plugin

RunContext is plugin security, which means you can access it in a plugin, I honestly think it would still be nice to at least read the state of RunContext in a game since it makes sense why its write locked.

1 Like