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) -> string

Parameters:

ParameterTypeDescription
entity_typestringThe entity type (e.g., character, location, item)
entity_idstringThe 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:

ErrorDescription
entity_not_foundNo entity with this type and ID exists
capability_not_declaredentity_read not in host_functions

entity_list

List entities matching a query filter.

Signature:

entity_list(query: string) -> string

Parameters:

ParameterTypeDescription
querystringQuery 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:

FilterDescriptionExample
entity_typeFilter by entity typeentity_type=character
field:key=valueFilter by frontmatter fieldfield:role=protagonist
limitMaximum results (default 100, max 1000)limit=50
offsetPagination offsetoffset=50
sortSort fieldsort=name
orderSort 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) -> string

Parameters:

ParameterTypeDescription
entity_typestringThe entity type to create
datastringJSON 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:

ErrorDescription
validation_errorRequired fields missing or invalid
capability_not_declaredentity_write not in host_functions
capability_not_approvedUser has not approved entity_write

entity_update

Update an existing entity.

Signature:

entity_update(entity_type: string, entity_id: string, content: string) -> string

Parameters:

ParameterTypeDescription
entity_typestringThe entity type
entity_idstringThe entity’s unique identifier
contentstringJSON 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) -> bool

Parameters:

ParameterTypeDescription
entity_typestringThe entity type
entity_idstringThe 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) -> bytes

Parameters:

ParameterTypeDescription
entity_typestringThe entity type
entity_idstringThe entity’s unique identifier
filenamestringName 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:

ErrorDescription
asset_not_foundNo asset with this filename exists for the entity
entity_not_foundThe 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) -> void

Parameters:

ParameterTypeDescription
entity_typestringThe entity type
entity_idstringThe entity’s unique identifier
filenamestringName for the asset file
databytesRaw 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:

ErrorDescription
entity_not_foundThe parent entity does not exist
size_limit_exceededFile exceeds the maximum asset size
type_not_allowedFile 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) -> string

Parameters:

ParameterTypeDescription
urlstringFull URL including protocol (e.g., https://api.example.com/data)
methodstringHTTP method: GET, POST, PUT, PATCH, DELETE
headersstringJSON string of header key-value pairs
bodystringRequest 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:

LimitValue
Requests per minute (per plugin)30
Timeout per request5 seconds
Maximum response body1 MB

Errors:

ErrorDescription
domain_not_allowedURL domain is not in http_domains (Cloud only)
rate_limit_exceededPlugin has exceeded 30 requests/minute
request_timeoutRequest took longer than 5 seconds
response_too_largeResponse body exceeds 1 MB

AI Functions

ai_generate

Call the StudioBrain AI generation service.

Signature:

ai_generate(prompt: string, model: string, options: string) -> string

Parameters:

ParameterTypeDescription
promptstringThe text prompt for generation
modelstringModel identifier (e.g., default, creative, precise). Use default to let the system choose.
optionsstringJSON string of generation options

Generation options:

OptionTypeDefaultDescription
max_tokensnumber1000Maximum tokens in the response
temperaturenumber0.7Sampling temperature (0.0 - 1.0)
system_promptstringnullSystem prompt prepended to the generation
context_entitiesstring[][]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:

ErrorDescription
insufficient_brainbitsUser’s BrainBits balance is too low
model_not_availableRequested model is not available
generation_failedAI service returned an error
rate_limit_exceededToo many generation requests

UI Functions

ui_notify

Show a toast notification in the StudioBrain UI.

Signature:

ui_notify(title: string, message: string, level: string) -> void

Parameters:

ParameterTypeDescription
titlestringNotification title (short, one line)
messagestringNotification body text
levelstringNotification 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) -> string

Parameters:

ParameterTypeDescription
keystringSetting 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) -> void

Parameters:

ParameterTypeDescription
keystringSetting key
valuestringNew 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) -> string

Parameters:

ParameterTypeDescription
keystringStorage 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) -> void

Parameters:

ParameterTypeDescription
keystringStorage key
valuestringValue 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) -> void

Parameters:

ParameterTypeDescription
keystringStorage 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) -> string

Parameters:

ParameterTypeDescription
prefixstringKey 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) -> void

Parameters:

ParameterTypeDescription
levelstringLog level: debug, info, warn, error
messagestringLog 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:

  • debug logs are only visible when plugin debug mode is enabled.
  • info, warn, error logs 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 TypeDescription
capability_not_declaredHost function not listed in capabilities.host_functions
capability_not_approvedUser has not approved the required capability
capability_blockedCapability is blocked on this platform (e.g., file_read on Cloud)
entity_not_foundEntity does not exist
asset_not_foundAsset file does not exist
domain_not_allowedHTTP request to undeclared domain (Cloud only)
rate_limit_exceededToo many calls in the time window
size_limit_exceededData exceeds maximum size
validation_errorInvalid parameters

Next Steps