Host Functions API Reference
This is the complete reference for host functions available to WASM plugins. Host functions are APIs injected by the StudioBrain runtime into the WASM sandbox, allowing plugins to interact with entities, assets, the AI service, and the host UI.
Plugins must declare which host functions they use in the capabilities.host_functions array of plugin.json. Calling an undeclared function returns an error at runtime.
Entity Functions
entity_read
Read a single entity by type and ID.
Signature:
entity_read(entity_type: string, entity_id: string) -> stringParameters:
| Parameter | Type | Description |
|---|---|---|
entity_type | string | The entity type (e.g., character, location, item) |
entity_id | string | The entity’s unique identifier (slug or UUID) |
Returns: JSON string containing the entity data, including frontmatter fields and markdown body.
Required capability: entity_read
Example (Rust):
let json_str = host::entity_read("character", "aria-blackwood")?;
let entity: serde_json::Value = serde_json::from_str(&json_str)?;
let name = entity["name"].as_str().unwrap_or("Unknown");
let backstory = entity["_body"].as_str().unwrap_or("");Response format:
{
"entity_type": "character",
"entity_id": "aria-blackwood",
"name": "Aria Blackwood",
"role": "protagonist",
"age": 28,
"_body": "# Aria Blackwood\n\nA skilled navigator...",
"_meta": {
"created_at": "2026-01-15T10:00:00Z",
"updated_at": "2026-03-20T14:30:00Z",
"template": "character-standard"
}
}Errors:
| Error | Description |
|---|---|
entity_not_found | No entity with this type and ID exists |
capability_not_declared | entity_read not in host_functions |
entity_list
List entities matching a query filter.
Signature:
entity_list(query: string) -> stringParameters:
| Parameter | Type | Description |
|---|---|---|
query | string | Query string with filters (e.g., entity_type=character&role=protagonist) |
Returns: JSON string containing an array of entity objects.
Required capability: entity_read
Query parameters:
| Filter | Description | Example |
|---|---|---|
entity_type | Filter by entity type | entity_type=character |
field:key=value | Filter by frontmatter field | field:role=protagonist |
limit | Maximum results (default 100, max 1000) | limit=50 |
offset | Pagination offset | offset=50 |
sort | Sort field | sort=name |
order | Sort direction (asc or desc) | order=asc |
Example (Rust):
let json_str = host::entity_list("entity_type=character&limit=10&sort=name")?;
let entities: Vec<serde_json::Value> = serde_json::from_str(&json_str)?;
for entity in &entities {
let name = entity["name"].as_str().unwrap_or("Unknown");
host::log("info", &format!("Found character: {}", name));
}entity_create
Create a new entity.
Signature:
entity_create(entity_type: string, data: string) -> stringParameters:
| Parameter | Type | Description |
|---|---|---|
entity_type | string | The entity type to create |
data | string | JSON string with entity fields and optional _body for markdown content |
Returns: JSON string of the created entity (including generated entity_id and metadata).
Required capability: entity_write
Example (Rust):
let data = serde_json::json!({
"name": "New Character",
"role": "supporting",
"age": 35,
"_body": "# New Character\n\nBackstory goes here."
});
let result = host::entity_create("character", &data.to_string())?;
let created: serde_json::Value = serde_json::from_str(&result)?;
let new_id = created["entity_id"].as_str().unwrap();Errors:
| Error | Description |
|---|---|
validation_error | Required fields missing or invalid |
capability_not_declared | entity_write not in host_functions |
capability_not_approved | User has not approved entity_write |
entity_update
Update an existing entity.
Signature:
entity_update(entity_type: string, entity_id: string, content: string) -> stringParameters:
| Parameter | Type | Description |
|---|---|---|
entity_type | string | The entity type |
entity_id | string | The entity’s unique identifier |
content | string | JSON string with the fields to update. Only provided fields are changed (patch semantics). |
Returns: JSON string of the updated entity.
Required capability: entity_write
Example (Rust):
let updates = serde_json::json!({
"role": "antagonist",
"age": 30
});
let result = host::entity_update("character", "aria-blackwood", &updates.to_string())?;Updates use patch semantics. Only the fields you include in the JSON are modified. Omitted fields keep their current values. To clear a field, set it to null.
entity_delete
Delete an entity.
Signature:
entity_delete(entity_type: string, entity_id: string) -> boolParameters:
| Parameter | Type | Description |
|---|---|---|
entity_type | string | The entity type |
entity_id | string | The entity’s unique identifier |
Returns: true if the entity was deleted, false if it did not exist.
Required capability: entity_write
Example (Rust):
let deleted = host::entity_delete("character", "old-character")?;
if deleted {
host::log("info", "Entity deleted successfully");
}Asset Functions
asset_read
Read a file attached to an entity.
Signature:
asset_read(entity_type: string, entity_id: string, filename: string) -> bytesParameters:
| Parameter | Type | Description |
|---|---|---|
entity_type | string | The entity type |
entity_id | string | The entity’s unique identifier |
filename | string | Name of the asset file (e.g., portrait.png, reference.pdf) |
Returns: Raw bytes of the file content.
Required capability: asset_read
Example (Rust):
let image_bytes = host::asset_read("character", "aria-blackwood", "portrait.png")?;
// Process image bytes (e.g., analyze, resize, convert)
let size = image_bytes.len();
host::log("info", &format!("Portrait size: {} bytes", size));Errors:
| Error | Description |
|---|---|
asset_not_found | No asset with this filename exists for the entity |
entity_not_found | The parent entity does not exist |
asset_write
Upload or replace an asset attached to an entity.
Signature:
asset_write(entity_type: string, entity_id: string, filename: string, data: bytes) -> voidParameters:
| Parameter | Type | Description |
|---|---|---|
entity_type | string | The entity type |
entity_id | string | The entity’s unique identifier |
filename | string | Name for the asset file |
data | bytes | Raw file content |
Returns: Nothing on success.
Required capability: asset_write
Example (Rust):
let generated_image: Vec<u8> = generate_portrait()?;
host::asset_write(
"character",
"aria-blackwood",
"ai-portrait.png",
&generated_image
)?;
host::ui_notify("Asset Uploaded", "AI portrait saved", "success");Errors:
| Error | Description |
|---|---|
entity_not_found | The parent entity does not exist |
size_limit_exceeded | File exceeds the maximum asset size |
type_not_allowed | File type is not in the allowed list for this entity template |
Network Functions
http_request
Make an outbound HTTP request.
Signature:
http_request(url: string, method: string, headers: string, body: string) -> stringParameters:
| Parameter | Type | Description |
|---|---|---|
url | string | Full URL including protocol (e.g., https://api.example.com/data) |
method | string | HTTP method: GET, POST, PUT, PATCH, DELETE |
headers | string | JSON string of header key-value pairs |
body | string | Request body (empty string for GET/DELETE) |
Returns: JSON string containing the response:
{
"status": 200,
"headers": {
"content-type": "application/json"
},
"body": "{\"result\": \"ok\"}"
}Required capability: http_request
Domain restrictions: On Cloud, only domains declared in capabilities.http_domains are reachable. See Permissions Reference.
Example (Rust):
let headers = serde_json::json!({
"Authorization": format!("Bearer {}", host::get_config("api_key")?),
"Content-Type": "application/json"
});
let body = serde_json::json!({
"query": "search term"
});
let response_str = host::http_request(
"https://api.example.com/search",
"POST",
&headers.to_string(),
&body.to_string()
)?;
let response: serde_json::Value = serde_json::from_str(&response_str)?;
let status = response["status"].as_u64().unwrap_or(0);
if status == 200 {
let results = &response["body"];
// Process results
}Rate limits:
| Limit | Value |
|---|---|
| Requests per minute (per plugin) | 30 |
| Timeout per request | 5 seconds |
| Maximum response body | 1 MB |
Errors:
| Error | Description |
|---|---|
domain_not_allowed | URL domain is not in http_domains (Cloud only) |
rate_limit_exceeded | Plugin has exceeded 30 requests/minute |
request_timeout | Request took longer than 5 seconds |
response_too_large | Response body exceeds 1 MB |
AI Functions
ai_generate
Call the StudioBrain AI generation service.
Signature:
ai_generate(prompt: string, model: string, options: string) -> stringParameters:
| Parameter | Type | Description |
|---|---|---|
prompt | string | The text prompt for generation |
model | string | Model identifier (e.g., default, creative, precise). Use default to let the system choose. |
options | string | JSON string of generation options |
Generation options:
| Option | Type | Default | Description |
|---|---|---|---|
max_tokens | number | 1000 | Maximum tokens in the response |
temperature | number | 0.7 | Sampling temperature (0.0 - 1.0) |
system_prompt | string | null | System prompt prepended to the generation |
context_entities | string[] | [] | Entity IDs to include as context |
Returns: Generated text as a plain string.
Required capability: ai_generate
Billing: Each call consumes BrainBits from the user’s account. The amount depends on the model and token count. If the user’s balance is insufficient, the call returns an insufficient_brainbits error.
Example (Rust):
let options = serde_json::json!({
"max_tokens": 500,
"temperature": 0.8,
"system_prompt": "You are a creative writing assistant for game development.",
"context_entities": ["aria-blackwood", "the-lost-city"]
});
let backstory = host::ai_generate(
"Write a 3-paragraph backstory for this character based on their traits and location.",
"creative",
&options.to_string()
)?;
// Save the generated backstory to the entity
let update = serde_json::json!({
"_body": backstory
});
host::entity_update("character", "aria-blackwood", &update.to_string())?;Errors:
| Error | Description |
|---|---|
insufficient_brainbits | User’s BrainBits balance is too low |
model_not_available | Requested model is not available |
generation_failed | AI service returned an error |
rate_limit_exceeded | Too many generation requests |
UI Functions
ui_notify
Show a toast notification in the StudioBrain UI.
Signature:
ui_notify(title: string, message: string, level: string) -> voidParameters:
| Parameter | Type | Description |
|---|---|---|
title | string | Notification title (short, one line) |
message | string | Notification body text |
level | string | Notification type: info, success, warning, error |
Returns: Nothing.
Required capability: None (always available).
Example (Rust):
host::ui_notify("Sync Complete", "15 entities synced with Jira", "success");
host::ui_notify("Warning", "API rate limit approaching", "warning");
host::ui_notify("Error", "Failed to connect to external service", "error");Behavior:
info— Blue toast, auto-dismisses after 5 seconds.success— Green toast, auto-dismisses after 5 seconds.warning— Yellow toast, stays until dismissed.error— Red toast, stays until dismissed.
Configuration Functions
get_config
Read a plugin setting value. Settings are defined in the capabilities.settings section of plugin.json and configured by the user in Settings > Plugins.
Signature:
get_config(key: string) -> stringParameters:
| Parameter | Type | Description |
|---|---|---|
key | string | Setting key as declared in plugin.json |
Returns: The setting value as a string. Returns the default value from the manifest if the user has not configured the setting.
Required capability: None (always available).
Example (Rust):
let api_key = host::get_config("api_key")?;
let depth: usize = host::get_config("analysis_depth")
.unwrap_or("3".to_string())
.parse()
.unwrap_or(3);
let auto_sync = host::get_config("auto_sync")? == "true";set_config
Write a plugin setting value. Use this to update settings programmatically (e.g., storing an OAuth token after the user completes a flow).
Signature:
set_config(key: string, value: string) -> voidParameters:
| Parameter | Type | Description |
|---|---|---|
key | string | Setting key |
value | string | New value |
Returns: Nothing.
Required capability: None (always available).
Example (Rust):
host::set_config("last_sync_at", &chrono::Utc::now().to_rfc3339())?;
host::set_config("oauth_token", &token_response.access_token)?;Storage Functions
Plugin storage is a key-value store scoped to each plugin. Data persists across sessions and is included in data exports.
storage_get
Read a value from plugin storage.
Signature:
storage_get(key: string) -> stringParameters:
| Parameter | Type | Description |
|---|---|---|
key | string | Storage key. Use / separators for namespacing (e.g., analysis/character/aria). |
Returns: The stored value as a string, or an empty string if the key does not exist.
Required capability: None (always available).
Example (Rust):
let cached = host::storage_get(&format!("analysis/{}/{}", entity_type, entity_id))?;
if !cached.is_empty() {
let data: serde_json::Value = serde_json::from_str(&cached)?;
// Use cached data
}storage_set
Write a value to plugin storage.
Signature:
storage_set(key: string, value: string) -> voidParameters:
| Parameter | Type | Description |
|---|---|---|
key | string | Storage key |
value | string | Value to store. Max 1 MB per value. |
Returns: Nothing.
Required capability: None (always available).
Example (Rust):
let analysis = serde_json::json!({
"entity": entity_id,
"related_count": related.len(),
"analyzed_at": chrono::Utc::now().to_rfc3339()
});
host::storage_set(
&format!("analysis/{}/{}", entity_type, entity_id),
&analysis.to_string()
)?;storage_delete
Delete a key from plugin storage.
Signature:
storage_delete(key: string) -> voidParameters:
| Parameter | Type | Description |
|---|---|---|
key | string | Storage key to delete |
Returns: Nothing. No error if the key does not exist.
Required capability: None (always available).
storage_list
List keys in plugin storage matching a prefix.
Signature:
storage_list(prefix: string) -> stringParameters:
| Parameter | Type | Description |
|---|---|---|
prefix | string | Key prefix to match (e.g., analysis/character/). Use empty string to list all keys. |
Returns: JSON array of matching key strings.
Required capability: None (always available).
Example (Rust):
let keys_json = host::storage_list("analysis/character/")?;
let keys: Vec<String> = serde_json::from_str(&keys_json)?;
host::log("info", &format!("Found {} cached analyses", keys.len()));Logging
log
Write a message to the plugin log. Logs are visible in Settings > Plugins > [Plugin] > Logs and in the application’s structured logging output (Loki/Grafana).
Signature:
log(level: string, message: string) -> voidParameters:
| Parameter | Type | Description |
|---|---|---|
level | string | Log level: debug, info, warn, error |
message | string | Log message |
Returns: Nothing.
Required capability: None (always available).
Example (Rust):
host::log("info", "Plugin initialized");
host::log("debug", &format!("Processing entity: {}", entity_id));
host::log("warn", "External API returned unexpected format");
host::log("error", &format!("Failed to parse response: {}", err));Behavior:
debuglogs are only visible when plugin debug mode is enabled.info,warn,errorlogs are always recorded.- Logs include the plugin ID and timestamp automatically.
Error Handling
All host functions can return errors. In Rust, use the ? operator with the Result type:
#[export]
fn on_entity_created(entity: Entity) -> Result<(), PluginError> {
// Each host call can fail
let data = host::entity_read("character", entity.id())?;
let response = host::http_request(
"https://api.example.com/webhook",
"POST",
"{}",
&data
)?;
// Check response status
let resp: serde_json::Value = serde_json::from_str(&response)?;
if resp["status"].as_u64() != Some(200) {
host::log("error", "Webhook failed");
return Err(PluginError::new("webhook_failed"));
}
Ok(())
}Common error types:
| Error Type | Description |
|---|---|
capability_not_declared | Host function not listed in capabilities.host_functions |
capability_not_approved | User has not approved the required capability |
capability_blocked | Capability is blocked on this platform (e.g., file_read on Cloud) |
entity_not_found | Entity does not exist |
asset_not_found | Asset file does not exist |
domain_not_allowed | HTTP request to undeclared domain (Cloud only) |
rate_limit_exceeded | Too many calls in the time window |
size_limit_exceeded | Data exceeds maximum size |
validation_error | Invalid parameters |
Next Steps
- Creating Plugins — Full plugin development guide
- Plugin Examples — Complete plugin walkthroughs
- Permissions Reference — Capability matrix and security model
- Getting Started — Quick start with scaffolding tools