Overview
ExuluVariables provides secure, centralized storage and retrieval of sensitive values like API keys, authentication tokens, and credentials. Variables are stored encrypted in the database and can be referenced across agents, tools, and embedders without exposing the actual values in code or environment files.
Key features
Encrypted storage Variables are encrypted at rest using AES encryption
Centralized management Store once, use everywhere across your application
UI/API management Create and update variables via UI or API
Reference by name Use variable names instead of hardcoded values
Automatic decryption Variables are decrypted automatically when retrieved
Reusable Share variables across agents, tools, and embedders
Why use ExuluVariables?
Secrets arenโt hardcoded in your codebase or stored in .env files that could be committed to version control. Variables are encrypted at rest and only decrypted when retrieved.
Update an API key once, and all agents, tools, and embedders using that variable automatically use the new value. No need to update multiple configuration files.
Manage secrets through the UI or API without redeploying your application. Change API keys, tokens, or credentials on the fly.
Use different variables for development, staging, and production environments while keeping the same codebase.
Track who created or updated variables (when integrated with your audit system).
Quick start
Storing a variable
Variables can be created via the UI or API:
// Via API or database
INSERT INTO variables ( name , value , encrypted )
VALUES ( 'openai_api_key' , 'encrypted_value_here' , true );
Retrieving a variable
import { ExuluVariables } from "@exulu/backend" ;
// Get a variable value
const apiKey = await ExuluVariables . get ( "openai_api_key" );
console . log ( apiKey ); // "sk-..."
The variable is automatically decrypted if it was stored encrypted.
Common use cases
API keys for LLM providers
Store API keys for OpenAI, Anthropic, Google, and other providers:
import { ExuluVariables } from "@exulu/backend" ;
// Store once in database:
// name: "openai_api_key", value: "sk-...", encrypted: true
// name: "anthropic_api_key", value: "sk-ant-...", encrypted: true
// Use in agents
const openaiKey = await ExuluVariables . get ( "openai_api_key" );
const agent = new ExuluAgent ({
id: "assistant" ,
name: "OpenAI Assistant" ,
type: "agent" ,
description: "AI assistant" ,
provider: "openai" ,
authenticationInformation: openaiKey , // Use retrieved variable
config: {
name: "gpt-4o" ,
model: {
create : ({ apiKey }) => createOpenAI ({ apiKey: apiKey || openaiKey })( "gpt-4o" )
},
instructions: "You are a helpful assistant."
},
capabilities: { text: true , images: [], files: [], audio: [], video: [] }
});
Embedding API keys
Use variables for embedding provider credentials:
import { ExuluVariables , ExuluEmbedder } from "@exulu/backend" ;
// Retrieve the API key
const openaiKey = await ExuluVariables . get ( "openai_api_key" );
// Use in embedder
const embedder = new ExuluEmbedder ({
id: "openai_embedder" ,
name: "OpenAI Embeddings" ,
provider: "openai" ,
model: "text-embedding-3-small" ,
vectorDimensions: 1536 ,
authenticationInformation: openaiKey // Use retrieved variable
});
Store credentials for external services used by tools:
import { ExuluVariables , ExuluTool } from "@exulu/backend" ;
import { z } from "zod" ;
// Create tool with variable reference
const githubTool = new ExuluTool ({
id: "github_search" ,
name: "search_github" ,
description: "Searches GitHub repositories" ,
type: "function" ,
inputSchema: z . object ({
query: z . string ()
}),
config: [
{
name: "github_token" ,
type: "variable" , // Mark as variable reference
value: "github_api_token" // Variable name
}
],
execute : async ({ query }, config ) => {
// Retrieve the variable inside execute
const token = await ExuluVariables . get ( "github_api_token" );
const response = await fetch (
`https://api.github.com/search/repositories?q= ${ query } ` ,
{
headers: {
Authorization: `Bearer ${ token } `
}
}
);
const data = await response . json ();
return { result: JSON . stringify ( data . items . slice ( 0 , 5 )) };
}
});
Database credentials
Store database connection strings and credentials:
import { ExuluVariables } from "@exulu/backend" ;
// Retrieve database URL
const databaseUrl = await ExuluVariables . get ( "database_url" );
// Use in database connection
const db = createDatabaseClient ({
connectionString: databaseUrl
});
Multi-tenant applications
Different tenants can use different API keys:
import { ExuluVariables } from "@exulu/backend" ;
// Store tenant-specific keys:
// name: "tenant_1_openai_key", value: "sk-...", encrypted: true
// name: "tenant_2_openai_key", value: "sk-...", encrypted: true
async function getAgentForTenant ( tenantId : string ) {
const variableName = `tenant_ ${ tenantId } _openai_key` ;
const apiKey = await ExuluVariables . get ( variableName );
return new ExuluAgent ({
id: `tenant_ ${ tenantId } _agent` ,
name: `Agent for Tenant ${ tenantId } ` ,
type: "agent" ,
description: "Tenant-specific agent" ,
provider: "openai" ,
authenticationInformation: apiKey ,
config: { /* ... */ },
capabilities: { text: true , images: [], files: [], audio: [], video: [] }
});
}
How it works
Storage
Variables are stored in the variables table in PostgreSQL:
CREATE TABLE variables (
name VARCHAR PRIMARY KEY ,
value TEXT NOT NULL ,
encrypted BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT NOW (),
updated_at TIMESTAMP DEFAULT NOW ()
);
Encryption
When encrypted: true, variables are encrypted using AES encryption with NEXTAUTH_SECRET as the encryption key:
// Encryption (when storing)
const encrypted = CryptoJS . AES . encrypt ( value , process . env . NEXTAUTH_SECRET ). toString ();
// Decryption (when retrieving)
const bytes = CryptoJS . AES . decrypt ( encrypted , process . env . NEXTAUTH_SECRET );
const decrypted = bytes . toString ( CryptoJS . enc . Utf8 );
The NEXTAUTH_SECRET environment variable must be set and consistent across deployments. If it changes, previously encrypted variables cannot be decrypted.
Retrieval
When you call ExuluVariables.get():
Queries the variables table for the specified name
Throws an error if the variable doesnโt exist
Decrypts the value if encrypted: true
Returns the decrypted value
Variable workflow
Create variable
Store a variable in the database via UI or API // Via UI: Navigate to Settings โ Variables โ Add Variable
// Or via API/SQL:
INSERT INTO variables ( name , value , encrypted )
VALUES ( 'my_api_key' , 'sk-...' , true );
Reference in code
Retrieve the variable using ExuluVariables.get() const apiKey = await ExuluVariables . get ( "my_api_key" );
Use in configuration
Pass the retrieved value to agents, tools, or embedders const agent = new ExuluAgent ({
// ...
authenticationInformation: apiKey
});
Update as needed
Update the variable value through UI or API without code changes UPDATE variables
SET value = 'new_encrypted_value'
WHERE name = 'my_api_key' ;
Variable naming conventions
Use descriptive, namespaced names for variables to avoid conflicts and improve clarity.
Recommended patterns:
// Provider API keys
"openai_api_key"
"anthropic_api_key"
"google_api_key"
// Service-specific
"github_api_token"
"stripe_api_key"
"sendgrid_api_key"
// Environment-specific
"prod_openai_api_key"
"dev_openai_api_key"
// Tenant-specific
"tenant_123_openai_key"
"tenant_456_anthropic_key"
// Feature-specific
"embeddings_openai_key"
"chat_openai_key"
Error handling
import { ExuluVariables } from "@exulu/backend" ;
try {
const apiKey = await ExuluVariables . get ( "my_variable" );
console . log ( "Retrieved variable:" , apiKey );
} catch ( error ) {
console . error ( "Variable not found:" , error . message );
// Handle missing variable (use default, prompt user, etc.)
}
ExuluVariables.get() throws an error if:
The variable name doesnโt exist in the database
Database connection fails
Decryption fails (e.g., wrong NEXTAUTH_SECRET)
Security considerations
Protect your NEXTAUTH_SECRET : This key is used to encrypt/decrypt variables. Store it securely and never commit it to version control.
Database access : Variables are stored in your PostgreSQL database. Ensure proper database access controls and backups.
Encryption at rest : Variables with encrypted: true are encrypted in the database. They are decrypted in memory when retrieved.
Principle of least privilege : Only grant access to variables that are necessary. Consider using separate variables for different environments or tenants.
Comparison with .env files
Feature ExuluVariables .env files Storage Database (encrypted) File system (plain text) Management UI/API Manual file editing Updates No redeployment needed Requires restart/redeploy Sharing Centralized across app Per-instance Version control Not in code Risk of accidental commit Encryption โ
Built-in โ Plain text Audit trail โ
Possible โ No Multi-tenant โ
Easy โ Difficult
Use ExuluVariables when:
You need to update secrets without redeployment
Multiple agents/tools share the same credentials
You want encrypted storage
You need centralized management
Youโre building a multi-tenant application
Use .env files when:
You have simple, static configuration
Secrets are environment-specific and rarely change
You prefer file-based configuration
Integration examples
Complete agent setup
import { ExuluAgent , ExuluVariables } from "@exulu/backend" ;
import { createOpenAI } from "@ai-sdk/openai" ;
// Retrieve API key
const openaiKey = await ExuluVariables . get ( "openai_api_key" );
// Create agent
const agent = new ExuluAgent ({
id: "customer_support" ,
name: "Customer Support Agent" ,
type: "agent" ,
description: "Handles customer inquiries" ,
provider: "openai" ,
authenticationInformation: openaiKey ,
config: {
name: "gpt-4o" ,
model: {
create : ({ apiKey }) => createOpenAI ({ apiKey: apiKey || openaiKey })( "gpt-4o" )
},
instructions: "You are a helpful customer support agent."
},
capabilities: { text: true , images: [], files: [], audio: [], video: [] }
});
// Use agent
const response = await agent . generateSync ({
prompt: "How do I reset my password?" ,
agentInstance: await loadAgent ( "customer_support" ),
statistics: { label: "support" , trigger: "api" }
});
console . log ( response );
Multi-provider setup
import { ExuluAgent , ExuluVariables } from "@exulu/backend" ;
// Retrieve keys for multiple providers
const [ openaiKey , anthropicKey , googleKey ] = await Promise . all ([
ExuluVariables . get ( "openai_api_key" ),
ExuluVariables . get ( "anthropic_api_key" ),
ExuluVariables . get ( "google_api_key" )
]);
// Create agents for each provider
const openaiAgent = new ExuluAgent ({
id: "openai_agent" ,
provider: "openai" ,
authenticationInformation: openaiKey ,
// ... config
});
const anthropicAgent = new ExuluAgent ({
id: "anthropic_agent" ,
provider: "anthropic" ,
authenticationInformation: anthropicKey ,
// ... config
});
const googleAgent = new ExuluAgent ({
id: "google_agent" ,
provider: "google" ,
authenticationInformation: googleKey ,
// ... config
});
Best practices
Always encrypt sensitive values : Set encrypted: true for API keys, tokens, passwords, and other secrets.
Use descriptive names : Name variables clearly to avoid confusion (e.g., openai_api_key instead of key1).
Handle missing variables : Always wrap ExuluVariables.get() in try/catch to handle cases where variables donโt exist.
Document your variables : Maintain a list of required variables for your application, especially when onboarding new team members.
Next steps