Sub-Projects
Sub-projects let one parent project serve multiple tenants from the same SDK install — same parent project, but the SDK passes a subProjectId to route each session to the right child. The mobile codebase stays the same; the AI configuration (greeting, tools, theme, conversations) varies by tenant.
This page covers the SDK side — the prop, the runtime behavior, and the cross-feature limits. For dashboard-side configuration (creating sub-projects, inheritance, per-sub-project conversations), see Dashboard › Sub-Projects.
When to Use Sub-Projects
Sub-projects are the right answer when one mobile app serves multiple distinct tenants that each need their own AI personality, knowledge base, or branding — but you don’t want the engineering cost of running multiple builds or multiple integrations.
Concrete examples:
- Multi-location app — same app for every branch, but each branch’s AI knows its own offerings and promotions (works for a restaurant chain, a clinic network, a bank, a gym franchise)
- Multi-brand retail — same shopping app for several brand partners, each with its own tone and product catalog
- White-label deployment — one codebase you ship to multiple customer organizations, each with their own assistant
- Regional variants — same app worldwide, but the AI varies by country’s documents and rules
If your app serves a single tenant, don’t use sub-projects — they add a layer of indirection (the parent + the child) that buys nothing in single-tenant cases. Use one project.
The SDK Contract
Sub-project routing is a top-level prop, not part of the runtime context:
<Qafka subProjectId="branch-london" />This is a deliberate API decision — putting subProjectId inside context would make it look like one more piece of optional metadata, when in reality it’s a routing key that changes which project the SDK is talking to. Mistakes here ripple silently: a misnamed key in context would be ignored; a wrong subProjectId prop produces a clear Sub-project not found error.
Identifying a Sub-Project
The subProjectId value can be either:
- The sub-project’s UUID (immutable, generated by the dashboard) —
e7f2a4b8-3c5d-4f1a-9d8b-2e1f5c4a7b6d - A human-readable identifier you set in the dashboard —
branch-london,branch-nyc,tenant-acme
The backend tries UUID first, then falls back to identifier match within the parent. Prefer human-readable identifiers — they’re easier to debug from the conversations view, easier to thread through your app’s existing branch/tenant ID model, and stable across environments.
Switching at Runtime
When subProjectId changes, the SDK fully tears down the current session and reinitializes against the new sub-project. This means:
- The active conversation ends — the user starts fresh in the new sub-project
- Any in-flight message is dropped
- Theme, greeting, tools, and instructions all reload from the new sub-project
- The voice session, if active, is closed (see voice limitation below)
Practically, this is what you want — switching tenants mid-conversation almost never makes semantic sense (“show me Branch A’s menu — actually no, Branch B’s”). But be deliberate about when you change the prop:
// User picks a different branch from a location switcher → re-mount with new ID
<Qafka subProjectId={selectedBranch.identifier} />If you wrap <Qafka /> in your own location-switcher UI, don’t pipe a frequently-changing value into subProjectId. Memoize or settle on the chosen tenant before passing it in.
Voice and Sub-Projects
Voice chat currently does not respect subProjectId — voice sessions always land on the parent project regardless of which child the rest of the SDK is configured for. This is a known gap on the voice roadmap (sub-project-aware voice routing is on the roadmap alongside Tool V2 and userContext personalization for voice).
If sub-project-specific voice configuration is critical for your launch, fall back to text chat for sub-project flows, or hide the voice page on sub-project screens via the voiceEnabled={false} prop until the gap is closed.
Patterns
Location Switcher
User picks a branch from a list, the chat for that branch opens:
function BranchChatScreen({ branch }) {
return <Qafka subProjectId={branch.identifier} />
}When the user navigates between branch screens, the parent component remounts <Qafka /> with the new identifier and the chat re-initializes against that branch’s sub-project.
Auth-Driven Tenant Routing
User signs in; their tenant is on the user record:
function ChatScreen() {
const { user } = useAuth()
if (!user) return <LoginPrompt />
return (
<Qafka
subProjectId={user.tenantId}
isAuthenticated
context={{ userId: user.id, role: user.role }}
/>
)
}Notice userId and role go in context — those are runtime metadata. tenantId is subProjectId because it’s a routing key.
Single-Project Fallback
If subProjectId is undefined (or omitted), the SDK falls through to the parent project — the same behavior as a single-tenant app. This is useful when your app is gradually migrating to multi-tenancy:
<Qafka subProjectId={tenant?.identifier} /> {/* undefined for users not yet assigned to a tenant */}Users on the parent fallback see the parent’s greeting, tools, and documents. Users with a sub-project assigned see their sub-project’s overrides on top.
Errors
The SDK surfaces sub-project resolution errors through onError. The most common ones:
| Error | What it means |
|---|---|
Sub-project not found | The subProjectId value didn’t match any active child of the active parent project. Check the value against the dashboard. |
Project not found | You passed subProjectId for a project that’s already a sub-project (sub-projects can’t have sub-projects of their own). |
If the subProjectId is missing or empty, the SDK silently uses the parent — that is not an error.