diff --git a/.gitignore b/.gitignore
index 103d90e7..6148f1e4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ __pycache__
# dependencies
node_modules
+package-lock.json
# IDEs and editors
/.idea
diff --git a/README.md b/README.md
index 4849358f..cc26a12d 100644
--- a/README.md
+++ b/README.md
@@ -107,6 +107,30 @@ The ElevenLabs Agents Widgets provide an easy way to embed AI agents into any we
Learn how to embed the widget into your website [here](https://elevenlabs.io/docs/agents-platform/customization/widget).
+#### Dynamic Variables in Widgets
+
+You can pass dynamic variables to your agent through HTML attributes:
+
+```html
+
+```
+
+To handle missing required variables, use the `expected-dynamic-variables` and `missing-dynamic-variable-default` attributes:
+
+```html
+
+```
+
+This automatically fills missing variables (like `previous_orders` and `support_history`) with `null`, preventing conversation failures when your agent's tools require variables you don't have.
+
### Agents CLI
The ElevenLabs Agents CLI allows you to manage your agents as code, with features like version control, templates, and multi-environment deployments.
diff --git a/packages/client/README.md b/packages/client/README.md
index 4bfc8fc5..12b8a24b 100644
--- a/packages/client/README.md
+++ b/packages/client/README.md
@@ -235,6 +235,60 @@ const conversation = await Conversation.startSession({
});
```
+#### Dynamic Variables
+
+Dynamic variables allow you to pass runtime data to your agent's tools. If your agent has tools configured with dynamic variable references, you can provide these variables during conversation initiation.
+
+**Basic usage:**
+
+```ts
+const conversation = await Conversation.startSession({
+ agentId: "",
+ dynamicVariables: {
+ user_name: "John Doe",
+ account_type: "premium",
+ session_id: "abc123",
+ },
+});
+```
+
+**Handling missing required variables:**
+
+If your agent's tools require dynamic variables that you don't always have values for, you can use `expectedDynamicVariables` and `missingDynamicVariableDefault` to automatically fill missing variables with default values:
+
+```ts
+const conversation = await Conversation.startSession({
+ agentId: "",
+
+ // Variables you have available
+ dynamicVariables: {
+ user_name: "John Doe",
+ current_plan: "premium",
+ },
+
+ // All variables your agent's tools expect
+ expectedDynamicVariables: [
+ "user_name",
+ "current_plan",
+ "previous_orders", // Will be auto-filled
+ "support_history", // Will be auto-filled
+ "preferences", // Will be auto-filled
+ ],
+
+ // Default value for missing variables (defaults to null)
+ missingDynamicVariableDefault: null,
+});
+```
+
+This prevents conversation failures when tools require variables you don't have. Missing variables will be automatically filled with the specified default value.
+
+**Supported default values:**
+- `null` (default)
+- Empty string: `""`
+- Custom string: `"N/A"`
+- Number: `0`
+- Boolean: `false`
+
#### User identification
You can optionally pass a user ID to identify the user in the conversation. This can be your own customer identifier. This will be included in the conversation initiation data sent to the server:
diff --git a/packages/client/src/utils/BaseConnection.ts b/packages/client/src/utils/BaseConnection.ts
index 170bc7f8..d9441683 100644
--- a/packages/client/src/utils/BaseConnection.ts
+++ b/packages/client/src/utils/BaseConnection.ts
@@ -48,6 +48,43 @@ export type BaseSessionConfig = {
};
customLlmExtraBody?: unknown;
dynamicVariables?: Record;
+ /**
+ * List of dynamic variable names that your agent's tools expect.
+ * Any variables in this list that aren't provided in dynamicVariables will be
+ * automatically filled with the value specified in missingDynamicVariableDefault.
+ *
+ * This is useful when your agent has tools that require dynamic variables,
+ * but you don't always have values for all of them.
+ *
+ * @example
+ * {
+ * expectedDynamicVariables: ['clients', 'matters', 'conversation_history'],
+ * missingDynamicVariableDefault: null,
+ * dynamicVariables: { clients: 'client data' }
+ * // Will automatically add: matters: null, conversation_history: null
+ * }
+ */
+ expectedDynamicVariables?: string[];
+ /**
+ * Default value to use for missing dynamic variables when tools require them.
+ * This helps avoid errors when tools expect dynamic variables that aren't provided.
+ * Only used when expectedDynamicVariables is specified.
+ *
+ * @default null
+ *
+ * @example
+ * // Fill missing dynamic variables with null
+ * { missingDynamicVariableDefault: null }
+ *
+ * @example
+ * // Fill missing dynamic variables with empty string
+ * { missingDynamicVariableDefault: "" }
+ *
+ * @example
+ * // Fill missing dynamic variables with a custom value
+ * { missingDynamicVariableDefault: "N/A" }
+ */
+ missingDynamicVariableDefault?: string | number | boolean | null;
useWakeLock?: boolean;
connectionDelay?: DelayConfig;
textOnly?: boolean;
diff --git a/packages/client/src/utils/overrides.ts b/packages/client/src/utils/overrides.ts
index bf1bb188..dabe67df 100644
--- a/packages/client/src/utils/overrides.ts
+++ b/packages/client/src/utils/overrides.ts
@@ -31,8 +31,33 @@ export function constructOverrides(
overridesEvent.custom_llm_extra_body = config.customLlmExtraBody;
}
- if (config.dynamicVariables) {
- overridesEvent.dynamic_variables = config.dynamicVariables;
+ // Handle dynamic variables with auto-filling of missing required variables
+ if (config.dynamicVariables || config.expectedDynamicVariables) {
+ const providedVariables = config.dynamicVariables || {};
+
+ // If expectedDynamicVariables is specified, fill in missing ones with default value
+ if (
+ config.expectedDynamicVariables &&
+ config.expectedDynamicVariables.length > 0
+ ) {
+ const defaultValue = config.missingDynamicVariableDefault ?? null;
+ const filledVariables: Record =
+ { ...providedVariables };
+
+ // Add missing variables with default value
+ for (const varName of config.expectedDynamicVariables) {
+ if (!(varName in filledVariables)) {
+ filledVariables[varName] = defaultValue;
+ }
+ }
+
+ overridesEvent.dynamic_variables = filledVariables as Record<
+ string,
+ string | number | boolean
+ >;
+ } else {
+ overridesEvent.dynamic_variables = providedVariables;
+ }
}
if (config.userId) {
diff --git a/packages/convai-widget-core/src/contexts/session-config.tsx b/packages/convai-widget-core/src/contexts/session-config.tsx
index 9dbf911b..ecb0422b 100644
--- a/packages/convai-widget-core/src/contexts/session-config.tsx
+++ b/packages/convai-widget-core/src/contexts/session-config.tsx
@@ -59,6 +59,36 @@ export function SessionConfigProvider({
return undefined;
});
+ const expectedDynamicVariablesJSON = useAttribute("expected-dynamic-variables");
+ const expectedDynamicVariables = useComputed(() => {
+ if (expectedDynamicVariablesJSON.value) {
+ try {
+ return JSON.parse(expectedDynamicVariablesJSON.value) as string[];
+ } catch (e: any) {
+ console.error(
+ `[ConversationalAI] Cannot parse expected-dynamic-variables: ${e?.message}`
+ );
+ }
+ }
+
+ return undefined;
+ });
+
+ const missingDynamicVariableDefaultAttr = useAttribute("missing-dynamic-variable-default");
+ const missingDynamicVariableDefault = useComputed(() => {
+ if (missingDynamicVariableDefaultAttr.value) {
+ try {
+ // Try to parse as JSON first (for null, numbers, booleans)
+ return JSON.parse(missingDynamicVariableDefaultAttr.value);
+ } catch {
+ // If JSON parse fails, treat as string
+ return missingDynamicVariableDefaultAttr.value;
+ }
+ }
+
+ return null;
+ });
+
const { webSocketUrl } = useServerLocation();
const agentId = useAttribute("agent-id");
const signedUrl = useAttribute("signed-url");
@@ -68,6 +98,8 @@ export function SessionConfigProvider({
const isWebRTC = useWebRTCEnabled.value;
const baseConfig = {
dynamicVariables: dynamicVariables.value,
+ expectedDynamicVariables: expectedDynamicVariables.value,
+ missingDynamicVariableDefault: missingDynamicVariableDefault.value,
overrides: overrides.value,
connectionDelay: { default: 300 },
textOnly: textOnly.value,
diff --git a/packages/convai-widget-core/src/types/attributes.ts b/packages/convai-widget-core/src/types/attributes.ts
index cc5bf04a..df2e2c3e 100644
--- a/packages/convai-widget-core/src/types/attributes.ts
+++ b/packages/convai-widget-core/src/types/attributes.ts
@@ -25,6 +25,8 @@ export const CustomAttributeList = [
"server-location",
"language",
"dynamic-variables",
+ "expected-dynamic-variables",
+ "missing-dynamic-variable-default",
"show-avatar-when-collapsed",
"override-prompt",
"override-first-message",
diff --git a/packages/react-native/README.md b/packages/react-native/README.md
index 9504b177..0205e055 100644
--- a/packages/react-native/README.md
+++ b/packages/react-native/README.md
@@ -102,6 +102,11 @@ Starts a new conversation session.
- `config.agentId`: ElevenLabs agent ID, not needed if you provide a conversationToken.
- `config.conversationToken`: Optional pre-generated token, used for private agents that require authentication via your ElevenLabs API key.
- `config.userId`: You can optionally pass a user ID to identify the user in the conversation. This can be your own customer identifier. This will be included in the conversation initiation data sent to the server.
+- `config.dynamicVariables`: Optional key-value pairs to pass runtime data to your agent's tools.
+- `config.expectedDynamicVariables`: Optional array of variable names your agent expects. Any missing variables will be auto-filled.
+- `config.missingDynamicVariableDefault`: Default value for missing variables (defaults to `null`). Can be `null`, string, number, or boolean.
+
+**Basic example:**
```typescript
await conversation.startSession({
@@ -109,6 +114,45 @@ await conversation.startSession({
});
```
+**With dynamic variables:**
+
+```typescript
+await conversation.startSession({
+ agentId: "your-agent-id",
+ dynamicVariables: {
+ user_name: "John Doe",
+ account_type: "premium",
+ },
+});
+```
+
+**Auto-filling missing required variables:**
+
+If your agent's tools require dynamic variables you don't always have, use `expectedDynamicVariables` to prevent errors:
+
+```typescript
+await conversation.startSession({
+ agentId: "your-agent-id",
+
+ // Variables you have
+ dynamicVariables: {
+ user_name: "John Doe",
+ current_plan: "premium",
+ },
+
+ // All variables your agent expects
+ expectedDynamicVariables: [
+ "user_name",
+ "current_plan",
+ "previous_orders", // Will be auto-filled with null
+ "support_history", // Will be auto-filled with null
+ ],
+
+ // Optional: customize the default value
+ missingDynamicVariableDefault: null, // Can also be "", "N/A", 0, false, etc.
+});
+```
+
#### `endSession(): Promise`
Ends the current conversation session.
diff --git a/packages/react-native/src/ElevenLabsProvider.tsx b/packages/react-native/src/ElevenLabsProvider.tsx
index 9545ac01..b4b3fe8d 100644
--- a/packages/react-native/src/ElevenLabsProvider.tsx
+++ b/packages/react-native/src/ElevenLabsProvider.tsx
@@ -121,6 +121,8 @@ export const ElevenLabsProvider: React.FC = ({ children
overrides,
customLlmExtraBody,
dynamicVariables,
+ expectedDynamicVariables,
+ missingDynamicVariableDefault,
userId,
} = useConversationSession(callbacksRef, setStatus, setConnect, setToken, setConversationId, tokenFetchUrl);
@@ -210,6 +212,8 @@ export const ElevenLabsProvider: React.FC = ({ children
overrides,
customLlmExtraBody,
dynamicVariables,
+ expectedDynamicVariables,
+ missingDynamicVariableDefault,
userId,
});
@@ -223,7 +227,7 @@ export const ElevenLabsProvider: React.FC = ({ children
callbacksRef.current.onError?.(error as string);
}
}
- }, [handleParticipantReady, overrides, customLlmExtraBody, dynamicVariables, userId, callbacksRef]);
+ }, [handleParticipantReady, overrides, customLlmExtraBody, dynamicVariables, expectedDynamicVariables, missingDynamicVariableDefault, userId, callbacksRef]);
const conversation: Conversation = {
startSession,
diff --git a/packages/react-native/src/hooks/useConversationSession.ts b/packages/react-native/src/hooks/useConversationSession.ts
index 9cc82a5d..31134736 100644
--- a/packages/react-native/src/hooks/useConversationSession.ts
+++ b/packages/react-native/src/hooks/useConversationSession.ts
@@ -25,6 +25,10 @@ export const useConversationSession = (
const [dynamicVariables, setDynamicVariables] = useState<
ConversationConfig["dynamicVariables"]
>({});
+ const [expectedDynamicVariables, setExpectedDynamicVariables] =
+ useState(undefined);
+ const [missingDynamicVariableDefault, setMissingDynamicVariableDefault] =
+ useState(null);
const [userId, setUserId] = useState(undefined);
const startSession = useCallback(
@@ -36,6 +40,10 @@ export const useConversationSession = (
setOverrides(config.overrides || {});
setCustomLlmExtraBody(config.customLlmExtraBody || null);
setDynamicVariables(config.dynamicVariables || {});
+ setExpectedDynamicVariables(config.expectedDynamicVariables);
+ setMissingDynamicVariableDefault(
+ config.missingDynamicVariableDefault ?? null
+ );
setUserId(config.userId);
let conversationToken: string;
@@ -91,6 +99,8 @@ export const useConversationSession = (
setOverrides({});
setCustomLlmExtraBody(null);
setDynamicVariables({});
+ setExpectedDynamicVariables(undefined);
+ setMissingDynamicVariableDefault(null);
setUserId(undefined);
setConversationId("");
@@ -110,6 +120,8 @@ export const useConversationSession = (
overrides,
customLlmExtraBody,
dynamicVariables,
+ expectedDynamicVariables,
+ missingDynamicVariableDefault,
userId,
};
};
diff --git a/packages/react-native/src/types.ts b/packages/react-native/src/types.ts
index 410d3b0d..eca0ae4c 100644
--- a/packages/react-native/src/types.ts
+++ b/packages/react-native/src/types.ts
@@ -81,6 +81,43 @@ export type ConversationConfig = {
};
customLlmExtraBody?: unknown;
dynamicVariables?: Record;
+ /**
+ * List of dynamic variable names that your agent's tools expect.
+ * Any variables in this list that aren't provided in dynamicVariables will be
+ * automatically filled with the value specified in missingDynamicVariableDefault.
+ *
+ * This is useful when your agent has tools that require dynamic variables,
+ * but you don't always have values for all of them.
+ *
+ * @example
+ * {
+ * expectedDynamicVariables: ['clients', 'matters', 'conversation_history'],
+ * missingDynamicVariableDefault: null,
+ * dynamicVariables: { clients: 'client data' }
+ * // Will automatically add: matters: null, conversation_history: null
+ * }
+ */
+ expectedDynamicVariables?: string[];
+ /**
+ * Default value to use for missing dynamic variables when tools require them.
+ * This helps avoid errors when tools expect dynamic variables that aren't provided.
+ * Only used when expectedDynamicVariables is specified.
+ *
+ * @default null
+ *
+ * @example
+ * // Fill missing dynamic variables with null
+ * { missingDynamicVariableDefault: null }
+ *
+ * @example
+ * // Fill missing dynamic variables with empty string
+ * { missingDynamicVariableDefault: "" }
+ *
+ * @example
+ * // Fill missing dynamic variables with a custom value
+ * { missingDynamicVariableDefault: "N/A" }
+ */
+ missingDynamicVariableDefault?: string | number | boolean | null;
userId?: string;
};
diff --git a/packages/react-native/src/utils/overrides.ts b/packages/react-native/src/utils/overrides.ts
index c1adcce1..d23f0736 100644
--- a/packages/react-native/src/utils/overrides.ts
+++ b/packages/react-native/src/utils/overrides.ts
@@ -33,8 +33,33 @@ export function constructOverrides(
overridesEvent.custom_llm_extra_body = config.customLlmExtraBody;
}
- if (config.dynamicVariables) {
- overridesEvent.dynamic_variables = config.dynamicVariables;
+ // Handle dynamic variables with auto-filling of missing required variables
+ if (config.dynamicVariables || config.expectedDynamicVariables) {
+ const providedVariables = config.dynamicVariables || {};
+
+ // If expectedDynamicVariables is specified, fill in missing ones with default value
+ if (
+ config.expectedDynamicVariables &&
+ config.expectedDynamicVariables.length > 0
+ ) {
+ const defaultValue = config.missingDynamicVariableDefault ?? null;
+ const filledVariables: Record =
+ { ...providedVariables };
+
+ // Add missing variables with default value
+ for (const varName of config.expectedDynamicVariables) {
+ if (!(varName in filledVariables)) {
+ filledVariables[varName] = defaultValue;
+ }
+ }
+
+ overridesEvent.dynamic_variables = filledVariables as Record<
+ string,
+ string | number | boolean
+ >;
+ } else {
+ overridesEvent.dynamic_variables = providedVariables;
+ }
}
if (config.userId) {
diff --git a/packages/react/README.md b/packages/react/README.md
index 195cf4e2..d0c7829d 100644
--- a/packages/react/README.md
+++ b/packages/react/README.md
@@ -128,6 +128,60 @@ const conversation = useConversation({
});
```
+#### Dynamic Variables
+
+Dynamic variables allow you to pass runtime data to your agent's tools. If your agent has tools configured with dynamic variable references, you can provide these variables during conversation initiation.
+
+**Basic usage:**
+
+```ts
+const conversationId = await conversation.startSession({
+ agentId: "",
+ dynamicVariables: {
+ user_name: "John Doe",
+ account_type: "premium",
+ session_id: "abc123",
+ },
+});
+```
+
+**Handling missing required variables:**
+
+If your agent's tools require dynamic variables that you don't always have values for, you can use `expectedDynamicVariables` and `missingDynamicVariableDefault` to automatically fill missing variables with default values:
+
+```ts
+const conversationId = await conversation.startSession({
+ agentId: "",
+
+ // Variables you have available
+ dynamicVariables: {
+ user_name: "John Doe",
+ current_plan: "premium",
+ },
+
+ // All variables your agent's tools expect
+ expectedDynamicVariables: [
+ "user_name",
+ "current_plan",
+ "previous_orders", // Will be auto-filled
+ "support_history", // Will be auto-filled
+ "preferences", // Will be auto-filled
+ ],
+
+ // Default value for missing variables (defaults to null)
+ missingDynamicVariableDefault: null,
+});
+```
+
+This prevents conversation failures when tools require variables you don't have. Missing variables will be automatically filled with the specified default value.
+
+**Supported default values:**
+- `null` (default)
+- Empty string: `""`
+- Custom string: `"N/A"`
+- Number: `0`
+- Boolean: `false`
+
#### User identification
You can optionally pass a user ID to identify the user in the conversation. This can be your own customer identifier. This will be included in the conversation initiation data sent to the server: