Documentation

Did you find this helpful?

Writing Custom Cloud Script

Cloud Script is one of PlayFab's most versatile features. It allows client code to request execution of any kind of custom server-side functionality you can implement, and it can be used in conjunction with virtually anything.

This guide covers writing your Cloud Script functions. Please see this guide for help in uploading your Cloud Script files to your title.

Prerequisites for this guide:

  • Unity environment set up with PlayFab Unity SDK
    • Note: this guide demonstrates Unity code samples, but Cloud Script works similarly for all SDKs.
    • The title ID is set in the PlayFabSharedSettings object
    • The project can successfully log in a user.

Getting Started: Hello World

Our hello world example works on a brand new title, with no modifications in Game Manager.  The default Cloud Script file for a new tilte includes a handler called helloWorld. It utilizes a few basic features, input parameters, logging, currentPlayerId, and return parameters.  See the default helloWorld function code (minus comments) below.

// Cloud Script (JavaScript)
handlers.helloWorld = function (args, context) {
    var message = "Hello " + currentPlayerId + "!";
    log.info(message);
    var inputValue = null;
    if (args && args.hasOwnProperty("inputValue"))
        inputValue = args.inputValue;
    log.debug("helloWorld:", { input: inputValue });
    return { messageValue: message };
}

Deconstruct the code

The handlers object is pre-defined in the PlayFab Cloud Script environment.  You should add any of your Cloud Script functions to this object.

helloWorld is a function made available to your title and SDKs because it is defined in the handlers object.

args is an arbitrary object which comes from the caller.  It is parsed from json, and can contain any data formatted in any way.  See FunctionParameter in the next section.  You should treat this object as highly suspicious.  A hacked client or malicious user can provide ANY information here in any format.

context is an advanced parameter. In this example, it is null. See this guide(link pending) for more information.This parameter is server-controlled and safe.

currentPlayerId is a global variable which is set to the PlayFabId of the player requesting this call. This parameter is server-controlled and safe.

log.info: log is a global object. It is primarily used for debugging your Cloud Script. log contains the following functions: info, debug, error.  There are more details later in this guide.

return: any object you return is serialized as json, and returned to the caller.  You may return any json serializable object with any data you wish.  BEWARE: It is your responsibility if your Cloud Script returns secret data to your clients. A hacked client or malicious user can examine the returned data, even if you don't display it to the user in regular gameplay.

Executing Cloud Script Functions from a Unity Game Client

Calling a Cloud Script function from within a client is straightforward. You first have to create a ExecuteCloudScriptRequest, set the ActionId property to the name of the Cloud Script function you wish to execute (in this case it would be helloWorld); and lastly, send the object to PlayFab via our API. You can only call Cloud Script methods attached to the handlers javascript object.
So, to execute Cloud Script methods you will need the following lines of code in your client:

// Build the request object and access the API
private static void StartCloudHelloWorld()
{
    PlayFabClientAPI.ExecuteCloudScript(new ExecuteCloudScriptRequest()
    {
        FunctionName = "helloWorld", // Arbitrary function name (must exist in your uploaded cloud.js file)
        FunctionParameter = new { inputValue = "YOUR NAME" }, // The parameter provided to your function
        GeneratePlayStreamEvent = true, // Optional - Shows this event in PlayStream
    }, OnCloudHelloWorld, OnErrorShared);
}
// OnCloudHelloWorld defined in the next code block

Deconstruct the Code

ExecuteCloudScriptRequest is the request type for any call to PlayFabClientAPI.ExecuteCloudScript.

ExecuteCloudScriptRequest.FunctionName is a string.  The value should match the name of the function defined in Cloud Script.  In this case, "helloWorld"

ExecuteCloudScriptRequest.FunctionParameter can be any object, able to be serialized to json.  It becomes the first args parameter in the helloWorld function.  See args in the previous section.

ExecuteCloudScriptRequest.GeneratePlayStreamEvent is optional. If true, an event will be posted to PlayStream, which you can view in Game Manager, or utilize for other PlayStream triggers.

Depending on the language, the final part of ExecuteCloudScript line involves making the request to the PlayFab Cloud Script server, as well as result and error handling part, specific for the language. For example, in Unity, JavaScript or AS3 error and result handling is provided using callback functions. In pure C#, however, you SDK allows more succinct code by means of async/await construct. An example provided here:

private static void OnCloudHelloWorld(ExecuteCloudScriptResult result) {
    // Cloud Script returns arbitrary results, so you have to evaluate them one step and one parameter at a time
    Debug.Log(JsonWrapper.SerializeObject(result.FunctionResult));
    JsonObject jsonResult = (JsonObject)result.FunctionResult;
    object messageValue;
    jsonResult.TryGetValue("messageValue", out messageValue); // note how "messageValue" directly corresponds to the JSON values set in Cloud Script
    Debug.Log((string)messageValue);
}

private static void OnErrorShared(PlayFabError error)
{
    Debug.Log(error.GenerateErrorReport());
}

Intermediate Overview: Globals and advanced arguments

Cloud Script is a set of Javascript functions compiled with V8 and hosted on PlayFab's servers. It has access to any server API, along with a logger, the PlayFab ID of the player making the Cloud Script request, and any information included with the request, all in the form of preset objects. Cloud Script functions themselves are properties of a global handlers object. Here is a complete list of these predefined variables:

NameUse
serverHas access to all server-side API calls. They can be called (synchronously) like so: var result = server.AuthenticateUserTicket(request);
httpPerforms synchronous HTTP requests, like so: http.request(url, method, content, contentType, headers, logRequestAndResponse) headers is an object with properties corresponding to various headers and their values. logRequestAndResponse is a boolean that determines whether the title should log any errors in the request as part of the response.
logCreates log statements and adds them to the response. Logs have three levels: log.info(), log.debug(), and log.error(). All three levels take a message string, along with an optional object containing extra data to include with the log. e.g. log.info('hello!', { time: new Date() });
currentPlayerIdPlayFab Id of the player that triggered the Cloud Script call.
handlersGlobal object which contains all Cloud Script functions for your title. Functions can be added or called through this object, e.g. handlers.pop = function() {};, handlers.pop();.
scriptGlobal object which contains "revision" and "titleId". Revision represents the revision number for the currently executing Cloud Script, and titleId represents the titleId for the current title.

In addition, all handler functions are passed two parameters, detailed below:

NameUse
argsFirst parameter to a handler function. An object representation of the FunctionParameter field of an ExecuteCloudscript request.
contextSecond parameter to a handler function. Additional information about the request when it is triggered by a PlayStream event action, including the data from the event that triggered the action (context.playStreamEvent) and the profile data for the player associated with it. (context.playerProfile)

Cloud Script functions can be called through the ExecuteCloudScript API call or by a preset PlayStream event action. Full details about the response to ExecuteCloudScript can be found here.

Intermediate: FunctionParameter and args

We described above how to populate request.FunctionParameter, and view that info in the args parameter. This guide demonstrates how you upload new Cloud Script. Putting both together, we can provide another example of how to pass arguments from client to Cloud Script.  Take the example from above, and modify the Cloud Script code, and your client code as follows:

handlers.helloWorld = function (args) {
    // ALWAYS validate args parameter passed in from clients (Better than we do here)
    var message = "Hello " + args.name + "!"; // Utilize the name parameter sent from client
    log.info(message);
    return { messageValue: message };
}
// Build the request object and access the API
private static void StartCloudHelloWorld()
{
    PlayFabClientAPI.ExecuteCloudScript(new ExecuteCloudScriptRequest()
    {
        FunctionName = "helloWorld", // Arbitrary function name (must exist in your uploaded cloud.js file)
        FunctionParameter = new { name = "YOUR NAME" }, // The parameter provided to your function
        GeneratePlayStreamEvent = true, // Optional - Shows this event in PlayStream
    }, OnCloudHelloWorld, OnErrorShared);
}

private static void OnCloudHelloWorld(ExecuteCloudScriptResult result) {
    // Cloud Script returns arbitrary results, so you have to evaluate them one step and one parameter at a time
    Debug.Log(JsonWrapper.SerializeObject(result.FunctionResult));
    JsonObject jsonResult = (JsonObject)result.FunctionResult;
    object messageValue;
    jsonResult.TryGetValue("messageValue", out messageValue); // note how "messageValue" directly corresponds to the JSON values set in Cloud Script
    Debug.Log((string)messageValue);
}

private static void OnErrorShared(PlayFabError error)
{
    Debug.Log(error.GenerateErrorReport());
}

After making the changes above, you can now easily send and receive data between Cloud Script and your clients. It is important to point out that any data coming from your clients is susceptible to hacking and exploitation. You will always want to validate input parameters prior to updating your backend. The process for validating input parameters will vary from title to title, but the most basic validation will check to ensure inputs are within acceptable ranges and periods.

Intermediate: Calling the Server APIs

As mentioned before, within Cloud Script methods, you have access to the full set of Server API Calls. This enables your cloud code to act as a dedicated server.

Common Server Tasks:

  • Update player statistics & data
  • Grant items and currency
  • Randomly generate game data
  • Securely calculate battle results
  • and more...

See our reference documentation for required parameters and object structures

The following example is from within a potential Cloud Script handler:

// Cloud Script (JavaScript)
//See: JSON.parse, JSON.stringify, parseInt and other built-in javascript helper functions for manipulating data
var currentState; // here we are calculating the current player's game state

// here we are fetching the "SaveState" key from PlayFab,
var playerData = server.GetUserReadOnlyData({"PlayFabId" : currentPlayerId, "Keys" : ["SaveState"]});
var previousState = {}; //if we return a matching key-value pair, then we can proceed otherwise we will need to create a new record.

if(playerData.Data.hasOwnProperty("SaveState"))
{
    previousState = playerData.Data["SaveState"];
}

var writeToServer = {};
writeToServer["SaveState"] = previousState + currentState; // pseudo Code showing that the previous state is updated to the current state

var result = server.UpdateUserReadOnlyData({"PlayFabId" : currentPlayerId, "Data" : writeToServer, "Permission":"Public" });

if(result)
{
    log.info(result);
}
else
{
    log.error(result);
}

Advanced: PlayStream Event Action

The other way to call a Cloud Script function is as a PlayStream event action.

  1. In any browser, Visit PlayFab Game Manager, find your title, go to the PlayStream tab, followed by the Event Actions tab. The page looks like this:

    cloudscript_eventaction.png#asset:746

  2. Create a new action with the button, then give it a name. To make the action trigger a Cloud Script function, add an action with the button in that section and then select the option in the Type drop-down menu. Finally, select the helloWorld function in the Cloud Script function drop-down menu and click the Save Action button.
  3. cloudscript_helloworldaction.png#asset:747

  4. Right now, this action is set to trigger on any PlayStream event. To test it, check the Publish results as PlayStream Event box, save the action, and trigger an event. In the PlayStream debugger, a new event that corresponds to the Cloud Script execution should be present which contains the appropriate information. For more information on checking a PlayStream event in the debugger, see the optional Debugging Cloud Script section.

    An important note is that event actions can only use the live revision when calling Cloud Script functions. If you cannot find the helloWorld function in the drop-down, this is the most likely reason.

Advanced: Debugging Cloud Script

Logging:

One of the most important tools for debugging code is logging, our Cloud Script provides a utility to do so. This takes the form of the log object, which can log any message desired using the info, debug, and error methods. Additionally, the http object will log any errors it comes across while making requests by setting the logRequestAndResponse parameter. While setting these logs up is simple, accessing them takes a bit of finesse.

  1. Here is an example of a Cloud Script function that uses all 4 types of logs:

handlers.logTest = function(args, context) {
    log.info("This is a log statement!");
    log.debug("This is a debug statement.");
    log.error("This is... an error statement?");
    // the last parameter indicates we want logging on errors
    http.request('https://httpbin.org/status/404', 'post', '', 'text/plain', null, true);
};

To run this example, add this function to your live revision before proceeding.

  1. The logTest function can be called using ExecuteCloudScript as seen here:

// Invoke this on start of your application
void Login() {
    PlayFabClientAPI.LoginWithCustomID(new LoginWithCustomIDRequest {
        CreateAccount = true,
        CustomId = "Starter"
    }, result => RunLogTest(), null);
}

void RunLogTest() {
    PlayFabClientAPI.ExecuteCloudScript(new ExecuteCloudScriptRequest {
        FunctionName = "logTest",
        // duplicates the response of the request to PlayStream
        GeneratePlayStreamEvent = true
    }, null, null);
}
// Logs evaluated in next code block

  1. Setting GeneratePlayStreamEvent makes the Cloud Script function call generate a PlayStream event which includes the contents of the response. To find the contents of a PlayStream event, go to either the Game Manager home page for your title or its PlayStream tab. The PlayStream Debugger will display events as they come in. When they arrive, click the small blue info icon in the top right corner of the event, as seen here:

    pseventexample.png#asset:748

    Clicking this will display the raw JSON of the event, which is detailed for each event here. An example of this JSON can be seen below.

  2. If we add the LogScript MonoBehavior to the scene, running the game will yield this in PlayStream:

    cloudscript_logtestresult.png#asset:749

    The result of an ExecuteCloudScript call includes a field called Logs, which is a list of log objects generated by the Cloud Script function. You can see the three log calls, as well as the log from the invalid http request. The http request log also makes use of the Data field, unlike the log calls. This field is a JS object that can be populated by any information relevant to the log statement. log calls can make use of this field as well, using the second parameter:

handlers.logTest = function(args, context) {
    log.info("This is a log statement!", { what: "Here on business." });
    log.debug("This is a debug statement.", { who: "I am a doctor, sir" });
    log.error("This is... an error statement?", { why: "I'm here to fix the plumbing. Probably.", errCode: 123 });
};

These calls will all populate the Data field in the result with their second parameter.

  1. Since the logs are included in the result, client-side code can respond to log statements. The error in the logTestfunction is forced, but the client code can be adapted to respond to it:

void RunLogTest()
{
    PlayFabClientAPI.ExecuteCloudScript(
        new ExecuteCloudScriptRequest
        {
            FunctionName = "logTest",
            // handy for logs because the response will be duplicated on PlayStream
            GeneratePlayStreamEvent = true
        },
        result =>
        {
            var error123Present = false;
            foreach (var log in result.Logs)
            {
                if (log.Level != "Error") continue;
                var errData = (JsonObject) log.Data;
                object errCode;
                var errCodePresent = errData.TryGetValue("errCode", out errCode);
                if (errCodePresent && (ulong) errCode == 123) error123Present = true;
            }

            if (error123Present)
                Debug.Log("There was a bad, bad error!");
            else
                Debug.Log("Nice weather we're having.");
        }, null);
}

If this code is run, the output should indicate the presence of the error. Realistic error responses might be to display the error in the UI, or save a value in a log file.

Advanced: Errors

In development, Cloud Script errors will often not be manually triggered as in the case of log.error. Fortunately, the ExecuteCloudScript response includes an Error field. Adapting the last example from the logging section, we might use it like so:

void RunLogTest() {
    PlayFabClientAPI.ExecuteCloudScript(new ExecuteCloudScriptRequest {
        FunctionName = "logTest",
        // handy for logs because the response will be duplicated on PlayStream
        GeneratePlayStreamEvent = true
    }, result => {
        if(result.Error != null) {
            Debug.Log(string.Format("There was error in the Cloud Script function {0}:\n Error Code: {1}\n Message: {2}"
            , result.FunctionName, result.Error.Error, result.Error.Message));
        }
    },
    null);
}

In the event that some error occurred, this code would display it in the log.



Did you find this helpful?