Repository navigation
Custom Endpoints
Daisuke Tsukada edited this page Aug 26, 2025
·
1 revision
Learn how to extend Unity Remote Server with custom API endpoints for your specific needs.
Create a simple custom endpoint:
using DSGarage.UnityRemoteServer;
using UnityEditor;
using System.Net;
[InitializeOnLoad]
public static class MyCustomEndpoints
{
static MyCustomEndpoints()
{
// Register when Unity Editor starts
RemoteServer.RegisterCustomEndpoint("/custom/hello", HandleHello);
}
static void HandleHello(HttpListenerContext context)
{
var response = new {
message = "Hello from custom endpoint!",
time = System.DateTime.Now
};
RemoteServer.SendJsonResponse(context, response);
}
}Test your endpoint:
curl http://127.0.0.1:8787/custom/hellostatic void HandleCustomEndpoint(HttpListenerContext context)
{
switch (context.Request.HttpMethod)
{
case "GET":
HandleGet(context);
break;
case "POST":
HandlePost(context);
break;
case "DELETE":
HandleDelete(context);
break;
default:
context.Response.StatusCode = 405; // Method Not Allowed
context.Response.Close();
break;
}
}
static void HandleGet(HttpListenerContext context)
{
// Handle GET request
var data = GetSomeData();
RemoteServer.SendJsonResponse(context, data);
}
static void HandlePost(HttpListenerContext context)
{
// Read POST data
var json = RemoteServer.ReadRequestBody(context);
var data = JsonUtility.FromJson<MyData>(json);
// Process data
ProcessData(data);
// Send response
RemoteServer.SendJsonResponse(context, new { ok = true });
}Handle query parameters in your endpoints:
static void HandleSearch(HttpListenerContext context)
{
var query = context.Request.QueryString;
var searchTerm = query["q"] ?? "";
var limit = int.Parse(query["limit"] ?? "10");
var offset = int.Parse(query["offset"] ?? "0");
var results = SearchAssets(searchTerm, limit, offset);
RemoteServer.SendJsonResponse(context, new
{
query = searchTerm,
results = results,
count = results.Length,
limit = limit,
offset = offset
});
}Search for assets in the project:
[InitializeOnLoad]
public static class AssetSearchEndpoint
{
static AssetSearchEndpoint()
{
RemoteServer.RegisterCustomEndpoint("/assets/search", HandleAssetSearch);
}
static void HandleAssetSearch(HttpListenerContext context)
{
var query = context.Request.QueryString["q"] ?? "";
var type = context.Request.QueryString["type"] ?? "all";
var guids = AssetDatabase.FindAssets(query);
var assets = new List<object>();
foreach (var guid in guids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var asset = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path);
if (type == "all" || asset.GetType().Name.ToLower().Contains(type.ToLower()))
{
assets.Add(new
{
guid = guid,
path = path,
name = asset.name,
type = asset.GetType().Name
});
}
}
RemoteServer.SendJsonResponse(context, new
{
query = query,
type = type,
count = assets.Count,
assets = assets
});
}
}Control scene loading and management:
[InitializeOnLoad]
public static class SceneEndpoints
{
static SceneEndpoints()
{
RemoteServer.RegisterCustomEndpoint("/scene/list", ListScenes);
RemoteServer.RegisterCustomEndpoint("/scene/open", OpenScene);
RemoteServer.RegisterCustomEndpoint("/scene/save", SaveScene);
}
static void ListScenes(HttpListenerContext context)
{
var scenes = new List<object>();
// Get all scenes in build settings
foreach (var scene in EditorBuildSettings.scenes)
{
scenes.Add(new
{
path = scene.path,
enabled = scene.enabled,
name = System.IO.Path.GetFileNameWithoutExtension(scene.path)
});
}
// Add current scene info
var currentScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
RemoteServer.SendJsonResponse(context, new
{
current = new
{
name = currentScene.name,
path = currentScene.path,
isDirty = currentScene.isDirty
},
buildScenes = scenes
});
}
static void OpenScene(HttpListenerContext context)
{
var json = RemoteServer.ReadRequestBody(context);
var request = JsonUtility.FromJson<SceneRequest>(json);
EditorApplication.delayCall += () =>
{
UnityEditor.SceneManagement.EditorSceneManager.OpenScene(request.path);
};
RemoteServer.SendJsonResponse(context, new { ok = true });
}
[System.Serializable]
class SceneRequest
{
public string path;
}
}Create and modify prefabs:
[InitializeOnLoad]
public static class PrefabEndpoints
{
static PrefabEndpoints()
{
RemoteServer.RegisterCustomEndpoint("/prefab/create", CreatePrefab);
RemoteServer.RegisterCustomEndpoint("/prefab/instantiate", InstantiatePrefab);
}
static void CreatePrefab(HttpListenerContext context)
{
var json = RemoteServer.ReadRequestBody(context);
var request = JsonUtility.FromJson<CreatePrefabRequest>(json);
EditorApplication.delayCall += () =>
{
var go = new GameObject(request.name);
// Add components
if (request.addCamera) go.AddComponent<Camera>();
if (request.addLight) go.AddComponent<Light>();
if (request.addRigidbody) go.AddComponent<Rigidbody>();
// Save as prefab
var path = $"Assets/Prefabs/{request.name}.prefab";
PrefabUtility.SaveAsPrefabAsset(go, path);
GameObject.DestroyImmediate(go);
AssetDatabase.Refresh();
};
RemoteServer.SendJsonResponse(context, new { ok = true });
}
[System.Serializable]
class CreatePrefabRequest
{
public string name;
public bool addCamera;
public bool addLight;
public bool addRigidbody;
}
}Run Unity tests remotely:
[InitializeOnLoad]
public static class TestEndpoints
{
static TestEndpoints()
{
RemoteServer.RegisterCustomEndpoint("/test/run", RunTests);
RemoteServer.RegisterCustomEndpoint("/test/results", GetTestResults);
}
static void RunTests(HttpListenerContext context)
{
var json = RemoteServer.ReadRequestBody(context);
var request = JsonUtility.FromJson<TestRequest>(json);
EditorApplication.delayCall += () =>
{
var testRunner = new UnityEditor.TestTools.TestRunner.TestRunner();
if (request.runAll)
{
UnityEditor.TestTools.TestRunner.Api.RunTests();
}
else
{
UnityEditor.TestTools.TestRunner.Api.RunTests(request.filter);
}
};
RemoteServer.SendJsonResponse(context, new { ok = true });
}
[System.Serializable]
class TestRequest
{
public bool runAll;
public string filter;
}
}Unity API calls must be made on the main thread:
static void HandleEndpoint(HttpListenerContext context)
{
// This runs on HTTP server thread
var request = RemoteServer.ReadRequestBody(context);
// Queue for main thread execution
EditorApplication.delayCall += () =>
{
// This runs on Unity's main thread
DoUnityApiCall();
};
// Or use RemoteOps for thread-safe execution
RemoteOps.ExecuteOnMainThread(() =>
{
DoUnityApiCall();
});
RemoteServer.SendJsonResponse(context, new { ok = true });
}Properly handle errors in custom endpoints:
static void HandleEndpoint(HttpListenerContext context)
{
try
{
// Your logic here
var result = DoSomething();
RemoteServer.SendJsonResponse(context, new
{
ok = true,
data = result
});
}
catch (System.Exception e)
{
context.Response.StatusCode = 500;
RemoteServer.SendJsonResponse(context, new
{
ok = false,
error = e.Message,
stack = e.StackTrace
});
}
}Add authentication to your endpoints:
static bool ValidateApiKey(HttpListenerContext context)
{
var apiKey = context.Request.Headers["X-API-Key"];
return apiKey == "your-secret-key";
}
static void SecureEndpoint(HttpListenerContext context)
{
if (!ValidateApiKey(context))
{
context.Response.StatusCode = 401; // Unauthorized
RemoteServer.SendJsonResponse(context, new
{
ok = false,
error = "Invalid API key"
});
return;
}
// Handle authenticated request
HandleRequest(context);
}For large data, use streaming:
static void StreamLargeData(HttpListenerContext context)
{
context.Response.ContentType = "application/octet-stream";
using (var output = context.Response.OutputStream)
{
// Stream data in chunks
byte[] buffer = new byte[4096];
int bytesRead;
using (var input = File.OpenRead("large-file.dat"))
{
while ((bytesRead = input.Read(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, bytesRead);
}
}
}
context.Response.Close();
}-
Use Meaningful URLs:
/assets/texture/listinstead of/list_tex -
Version Your API:
/api/v1/buildfor future compatibility -
Return Consistent Responses: Always include
okstatus - Document Your Endpoints: Add comments and examples
- Validate Input: Check parameters before processing
- Handle Errors Gracefully: Return meaningful error messages
- Use Appropriate HTTP Methods: GET for reading, POST for actions
- Keep Endpoints Focused: One endpoint, one responsibility
Test your endpoints with curl:
# GET request
curl http://127.0.0.1:8787/custom/status
# POST with JSON
curl -X POST http://127.0.0.1:8787/custom/action \
-H "Content-Type: application/json" \
-d '{"param": "value"}'
# With custom header
curl http://127.0.0.1:8787/custom/secure \
-H "X-API-Key: your-secret-key"Or with Python:
import requests
# Test endpoint
response = requests.get("http://127.0.0.1:8787/custom/hello")
print(response.json())
# POST data
response = requests.post(
"http://127.0.0.1:8787/custom/create",
json={"name": "TestObject"}
)
print(response.json())