Sharely.ai Web Control

The Sharely.ai Web Control allows you to embed intelligent AI agents directly into your web applications, providing a seamless, branded experience for your users.

Web Control is open source (Apache 2.0): github.com/sharelyai/webcontrol (opens in a new tab). Embed the hosted bundle with a script tag, compose the React packages into your own UI, or fork and self-host the whole thing — see Open Source & Self-Hosting.


What is the Web Control?

The Sharely.ai Web Control is an embeddable JavaScript component that brings the full power of Sharely.ai agents into your application:

  • Fully Customizable UI - Match your brand and design system
  • Your Domain - Host on your own domain with your branding
  • Integrated Experience - Seamlessly blend AI into your user experience
  • Programmatic Control - Full API control over agent behavior and appearance
  • Flexible Authentication - Anonymous visitors or identities asserted by your app
  • Role-Based Access Control - Built-in RBAC support for enterprise applications
  • Custom Agent Rendering - Streams thinking steps, tool calls, and citations from your own agent

When to Use Web Control

Use Web Control when you want to:

  • ✅ Embed AI capabilities directly into your existing application
  • ✅ Maintain complete control over the user experience and branding
  • ✅ Integrate with your existing authentication system
  • ✅ Implement role-based content access control
  • ✅ Provide a native, seamless AI assistant experience

Compare to Sharely.ai Hosted Agents:

FeatureWeb ControlHosted Agents
HostingYour domainSharely.ai domain
BrandingFully customizableSharely.ai branding
AuthenticationYour auth systemSharely.ai auth
IntegrationEmbedded in appStandalone links
DevelopmentRequires codingNo code needed
Best forCustom applicationsQuick launches, sharing

Open Source & Self-Hosting

Web Control is developed in the open at github.com/sharelyai/webcontrol (opens in a new tab) under the Apache 2.0 license (© 2026 Sharely.ai, Inc.).

The repository is a monorepo with three ways to consume it:

  1. Hosted embed bundle (easiest) — a single script tag served from https://webcontrol.sharely.ai/assets/sharelyai.js. No build step. This is what the rest of this page documents.

  2. React packages — layered packages you can compose into your own React app for granular control:

    • @sharelyai/services — headless data layer (API client, hooks, store) with no UI
    • @sharelyai/ui-shared — theme and base components
    • @sharelyai/ui-chat, @sharelyai/ui-search, @sharelyai/ui-browse, @sharelyai/ui-agent-chat — individual feature panels
    • @sharelyai/webcontrol — the full assembled component

    See the package examples (opens in a new tab) and the runnable demo app in the repository.

  3. Fork and self-host — build the single-file bundle from source (pnpm install && pnpm build) and serve it from your own CDN with your own defaults. The repository's CONTRIBUTING guide (opens in a new tab) covers local development, environment variables, deployment (including Vercel), and rebranding guidance for forks.

You are free to use, modify, and deploy your own fork under the Apache 2.0 terms. Publishing to the @sharelyai npm scope remains restricted to Sharely.ai maintainers. Issues and contributions are welcome on GitHub.


Two Authentication Modes

Web Control supports exactly two integration modes. The mode is determined by what you pass to initialize().

AnonymousHost-Asserted Identity
You passworkspaceId onlyworkspaceId + externalToken + externalUserId (+ spaceId)
Who the user isAn anonymous visitorA user your app has authenticated
Who asserts itNobody — Web Control mints a temporary sessionYour backend, by minting a token
RBACWorkspace defaultsRole embedded in the token
Best forPublic sites, demos, lead capturePortals, SaaS apps, enterprise

Not supported: passing externalUserId without externalToken. Identity must always be asserted with a token your backend minted — an unaccompanied user ID would let any visitor impersonate any user. If you want to identify users, use host-asserted mode.

Mode 1: Anonymous

How it works:

  1. User accesses your application without logging in
  2. Web Control initializes with just your workspaceId
  3. The widget creates a temporary space for the visitor and keeps a temporal token (persisted in a cookie) so the conversation survives page reloads
  4. User can interact with the AI assistant immediately

When to use:

  • Public websites where anyone can access the AI
  • Trial/freemium experiences
  • Lead capture workflows
  • Demo environments

Example use cases: website chatbot for visitor engagement, product demo assistant, lead qualification bot, public knowledge base assistant.

Mode 2: Host-Asserted Identity

How it works:

  1. User logs in to your application using your authentication system
  2. Your backend mints a Sharely.ai token for the authenticated user (the API key never leaves your server)
  3. Frontend initializes Web Control with externalToken, spaceId, and externalUserId — all three together
  4. The AI assistant has full user context and role-based permissions
  5. User maintains their identity and conversation history across sessions

When to use:

  • Enterprise applications with existing auth
  • Customer portals with personalized content
  • Applications requiring role-based access control (RBAC)
  • SaaS platforms with multi-tenant requirements

Example use cases: customer support portal, internal knowledge assistant, partner/reseller portals, enterprise SaaS applications.


Installation

1. Include the Sharely.ai Script

Add the Sharely.ai Web Control script to your HTML:

<script type="module" crossorigin src="https://webcontrol.sharely.ai/assets/sharelyai.js"></script>

Example in Next.js:

import Script from 'next/script';
 
<Script
  type="module"
  crossOrigin
  src="https://webcontrol.sharely.ai/assets/sharelyai.js"
  strategy="afterInteractive"
/>

Example in React:

useEffect(() => {
  const script = document.createElement('script');
  script.src = 'https://webcontrol.sharely.ai/assets/sharelyai.js';
  script.type = 'module';
  script.crossOrigin = 'true';
  document.body.appendChild(script);
 
  return () => {
    document.body.removeChild(script);
  };
}, []);

If you self-host a fork, point the src at your own deployment instead.

2. Add the Container Element

Add a container div where you want the Web Control to appear:

<div id="sharelyai-webcontroller-id"></div>

Note: The container ID must be sharelyai-webcontroller-id.

3. Initialize the Web Control

Initialize the Web Control with your configuration (see modes below).


Anonymous Implementation

For anonymous visitors, initialize with just your workspace ID:

<!DOCTYPE html>
<html>
<head>
  <title>My App with Sharely AI</title>
  <script type="module" crossorigin src="https://webcontrol.sharely.ai/assets/sharelyai.js"></script>
</head>
<body>
  <h1>Welcome to My App</h1>
 
  <!-- Web Control Container -->
  <div id="sharelyai-webcontroller-id"></div>
 
  <script>
    // Initialize when page loads
    window.addEventListener('load', function() {
      if (window.sharelyai) {
        window.sharelyai.initialize({
          workspaceId: 'your-workspace-id',
          mode: 'placed-inline',
          displayMode: {
            OPEN_BY_DEFAULT: true,
            WIDTH: '100%',
            MODE: 'PRIVATE',
            VIEWS: {
              CHAT: { SHOW: true },
              SEARCH: { SHOW: true },
              BROWSE: { SHOW: true }
            }
          }
        });
 
        window.sharelyai.render();
      }
    });
  </script>
</body>
</html>

Do not pass externalUserId in anonymous mode — user identity is only supported together with a host-minted externalToken (see above).


Host-Asserted Implementation

Backend Token Generation

Your backend must generate a Sharely.ai token when users log in. This follows the two-step authentication process:

Step 1: Generate API Access Token

// Node.js/Express example
const API_KEY = process.env.SHARELY_API_KEY;
const WORKSPACE_ID = process.env.SHARELY_WORKSPACE_ID;
const ORGANIZATION_ID = process.env.SHARELY_ORGANIZATION_ID;
 
async function generateAPIAccessToken(customerRoleId) {
  const response = await fetch(
    `https://api.sharely.ai/workspaces/${WORKSPACE_ID}/generate-access-key-token`,
    {
      method: 'POST',
      headers: {
        'x-api-key': API_KEY,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        customerRoleId: customerRoleId  // Optional: for RBAC
      })
    }
  );
 
  if (!response.ok) {
    throw new Error('Failed to generate API token');
  }
 
  return await response.json();
}

Step 2: Create User Space Token

async function createUserSpaceToken(apiToken, userId) {
  const response = await fetch(
    `https://api.sharely.ai/workspaces/${WORKSPACE_ID}/activate-or-retrieve-user-space`,
    {
      method: 'PUT',
      headers: {
        'Authorization': `Bearer ${apiToken}`,
        'organizationId': ORGANIZATION_ID,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        workspaceId: WORKSPACE_ID,
        customerIdString: userId  // User email or custom ID
      })
    }
  );
 
  if (!response.ok) {
    throw new Error('Failed to create user space');
  }
 
  return await response.json();
}

Complete Login Endpoint

// Express endpoint that returns Sharely token on login
app.post('/api/login', async (req, res) => {
  try {
    const { email, password } = req.body;
 
    // Your authentication logic
    const user = await authenticateUser(email, password);
    if (!user) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }
 
    // Determine user's role (optional, for RBAC)
    const customerRoleId = getUserRole(user); // e.g., "premium-user", "admin"
 
    // Generate API access token with role
    const apiToken = await generateAPIAccessToken(customerRoleId);
 
    // Create user space token (role inherited automatically)
    const userSpace = await createUserSpaceToken(apiToken.token, user.email);
 
    // Return tokens to frontend
    res.json({
      user: {
        id: user.id,
        email: user.email
      },
      sharely: {
        token: userSpace.token,
        spaceId: userSpace.spaceId
      }
    });
 
  } catch (error) {
    console.error('Login error:', error);
    res.status(500).json({ error: error.message });
  }
});

Frontend Initialization

After user logs in, initialize the Web Control with the token from your backend. Pass externalToken, spaceId, and externalUserId together:

// Fetch Sharely token from your backend
async function initializeSharelyForUser() {
  try {
    const response = await fetch('/api/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        email: userEmail,
        password: userPassword
      })
    });
 
    const data = await response.json();
 
    if (window.sharelyai) {
      window.sharelyai.initialize({
        workspaceId: 'your-workspace-id',
        externalToken: data.sharely.token,  // Token minted by your backend
        spaceId: data.sharely.spaceId,      // Space ID from your backend
        externalUserId: data.user.email,    // The identity that token asserts
        mode: 'placed-inline',
        displayMode: {
          OPEN_BY_DEFAULT: true,
          WIDTH: '100%',
          MODE: 'PRIVATE',
          VIEWS: {
            CHAT: { SHOW: true },
            SEARCH: { SHOW: true },
            BROWSE: { SHOW: true }
          }
        }
      });
 
      window.sharelyai.render();
    }
  } catch (error) {
    console.error('Failed to initialize Sharely:', error);
  }
}
 
// Call when user is authenticated
initializeSharelyForUser();

Complete React Example

import { useEffect, useState } from 'react';
 
function SharelyWidget({ user }) {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
 
  useEffect(() => {
    if (!user) return;
 
    const initializeSharely = async () => {
      try {
        // Fetch Sharely token for authenticated user
        const response = await fetch('/api/sharely-token', {
          method: 'GET',
          headers: {
            'Authorization': `Bearer ${user.sessionToken}`
          }
        });
 
        if (!response.ok) {
          throw new Error('Failed to get Sharely token');
        }
 
        const { token, spaceId } = await response.json();
 
        // Wait for Sharely script to load
        if (!window.sharelyai) {
          throw new Error('Sharely script not loaded');
        }
 
        // Initialize with external token
        window.sharelyai.initialize({
          workspaceId: process.env.NEXT_PUBLIC_SHARELY_WORKSPACE_ID,
          externalToken: token,
          spaceId: spaceId,
          externalUserId: user.email,
          mode: 'placed-inline',
          displayMode: {
            OPEN_BY_DEFAULT: true,
            WIDTH: '100%',
            MODE: 'PRIVATE',
            VIEWS: {
              CHAT: { SHOW: true },
              SEARCH: { SHOW: true },
              BROWSE: { SHOW: true }
            }
          }
        });
 
        window.sharelyai.render();
        setLoading(false);
 
      } catch (err) {
        console.error('Sharely initialization error:', err);
        setError(err.message);
        setLoading(false);
      }
    };
 
    initializeSharely();
  }, [user]);
 
  if (loading) return <div>Loading AI Assistant...</div>;
  if (error) return <div>Error loading AI Assistant: {error}</div>;
 
  return (
    <div style={{ width: '100%', height: '600px' }}>
      <div id="sharelyai-webcontroller-id" />
    </div>
  );
}
 
export default SharelyWidget;

Initialization API Reference

window.sharelyai.initialize(config)

Initializes the Sharely.ai Web Control with the specified configuration. Calling initialize() again destroys any previous instance first.

Parameters

The config object accepts the following parameters:

ParameterTypeRequiredDescription
workspaceIdstringYesYour Sharely.ai workspace UUID
baseUrlstringNoBackend API base URL. Defaults to https://api.sharely.ai. Useful for self-hosted forks and staging environments
externalTokenstringNoHost-minted user space token (host-asserted mode)
spaceIdstringNoSpace ID from your backend (required when using externalToken)
externalUserIdstringNoUser identifier (email or custom ID). Only valid together with externalToken
langstringNoUI language code (default: "en"). See supported languages
langKnowledgestringNoKnowledge content language filter (default: "en"). See supported languages
modestringNoLauncher position — one of "placed-inline", "placed-floating", "bottom-right-floating", "bottom-center-floating", "top-center-floating". Invalid values throw an error
displayModeobjectNoUI display configuration (see below)
closedTextstringNoText shown on the collapsed launcher
justChatbooleanNoInitialize in a chat-only flow
avatarmodeDesktopstringNoAvatar mode on desktop: "expanded" or "circle"
avatarmodeMobilestringNoAvatar mode on mobile: "expanded" or "circle"
saveSpaceNumMgsnumberNoNumber of messages to save locally (default: 1)
envstringNoEnvironment identifier sent in API headers
onErrorfunctionNo(error: Error) => void callback for runtime errors

The legacy api parameter is still accepted as an alias for baseUrl, but is deprecated.

Supported Languages

lang and langKnowledge accept: en, es, pt, pt-br, de, pl, zh, zh-hans, zh-hant, zh-yue.

displayMode Object

Controls the appearance and behavior of the Web Control UI:

PropertyTypeDescription
OPEN_BY_DEFAULTbooleanWhether the widget is open when initialized
MODEstringPrivacy mode: "PRIVATE" or "PUBLIC"
WIDTHstringWidget width (CSS value: "100%", "400px", etc.)
HEIGHTstringWidget height (CSS value)
Z_INDEXstringStacking order for floating modes
VIEWSobjectConfigure which views are available (see below)

VIEWS Object

Enable or disable specific views within the Web Control:

VIEWS: {
  CHAT:   { SHOW: true },                  // Chat interface
  SEARCH: { SHOW: true, SHOW_TAGS: true }, // Search interface (optionally with tag filters)
  BROWSE: { SHOW: true },                  // Browse/knowledge navigation
  AGENT:  { SHOW: true }                   // Custom agent chat (Bring Your Own Agent)
}

The AGENT view renders conversations with your own agent server — streamed thinking steps, tool calls, and citations included. See Bring Your Own Agent.

Complete Example

window.sharelyai.initialize({
  // Required
  workspaceId: '550e8400-e29b-41d4-a716-446655440000',
 
  // Host-asserted mode (all three together)
  externalToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
  spaceId: 'space-uuid-123',
  externalUserId: 'user@example.com',
 
  // Language configuration
  lang: 'en',          // UI language
  langKnowledge: 'en', // Knowledge content language
 
  // Display configuration
  mode: 'bottom-right-floating',
  closedText: 'Ask our AI',
 
  displayMode: {
    OPEN_BY_DEFAULT: false,
    WIDTH: '420px',
    HEIGHT: '640px',
    MODE: 'PRIVATE',
    VIEWS: {
      CHAT: { SHOW: true },
      SEARCH: { SHOW: true, SHOW_TAGS: true },
      BROWSE: { SHOW: true },
      AGENT: { SHOW: false }
    }
  },
 
  onError: (error) => console.error('Sharely error:', error)
});

window.sharelyai.render()

Renders the Web Control into the #sharelyai-webcontroller-id container element. Must be called after initialize().

window.sharelyai.initialize({ /* config */ });
window.sharelyai.render();

window.sharelyai.destroy()

Unmounts the Web Control and cleans up the instance. initialize() calls this automatically before re-initializing, but you can call it directly — for example, when the user logs out or navigates away in a single-page app.

window.sharelyai.destroy();

window.sharelyai.config()

Returns the current configuration, including the resolved env.

const currentConfig = window.sharelyai.config();
console.log(currentConfig.lang);          // "en"
console.log(currentConfig.workspaceId);   // your workspace ID

window.sharelyai.updateConfig(partialConfig)

Shallow-merges new configuration into the current instance at runtime — useful for switching languages without a full re-initialization:

window.sharelyai.updateConfig({ lang: 'es', langKnowledge: 'es' });

Role-Based Access Control (RBAC)

When using host-asserted mode, you can implement role-based content access control by including a customerRoleId when generating API tokens.

How RBAC Works

  1. Create roles in your Sharely.ai workspace (e.g., "premium-user", "admin", "basic-user")
  2. Assign knowledge to specific roles via the Knowledge Roles API
  3. Generate tokens with roles by including customerRoleId in token generation
  4. Users see filtered content - Web Control automatically shows only role-permitted content across Chat, Search, and Browse

Backend Implementation

// Determine user's role based on your app logic
function getUserRole(user) {
  if (user.isAdmin) return 'admin-role';
  if (user.subscriptionTier === 'premium') return 'premium-user';
  return 'basic-user';
}
 
// Generate token with role
async function generateTokenWithRole(user) {
  const customerRoleId = getUserRole(user);
 
  // Step 1: Generate API token with role
  const apiToken = await fetch(
    `https://api.sharely.ai/workspaces/${WORKSPACE_ID}/generate-access-key-token`,
    {
      method: 'POST',
      headers: {
        'x-api-key': API_KEY,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ customerRoleId })
    }
  ).then(res => res.json());
 
  // Step 2: Create user space (role inherited automatically)
  const userSpace = await fetch(
    `https://api.sharely.ai/workspaces/${WORKSPACE_ID}/activate-or-retrieve-user-space`,
    {
      method: 'PUT',
      headers: {
        'Authorization': `Bearer ${apiToken.token}`,
        'organizationId': ORGANIZATION_ID,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        workspaceId: WORKSPACE_ID,
        customerIdString: user.email
      })
    }
  ).then(res => res.json());
 
  return userSpace;
}

The role is now embedded in the token - when you initialize Web Control with this token, content is automatically filtered by role.


Using Web Control with Your Own Agent

If you've deployed a custom agent with the Server SDK, Web Control is its delivery surface: enable the AGENT view and the widget renders your agent's streamed events — thinking steps, tool calls, sources — with no client UI to build.

window.sharelyai.initialize({
  workspaceId: 'your-workspace-id',
  displayMode: {
    MODE: 'PRIVATE',
    VIEWS: {
      CHAT: { SHOW: false },
      SEARCH: { SHOW: true },
      BROWSE: { SHOW: true },
      AGENT: { SHOW: true }   // conversations go to your registered agent server
    }
  }
});

See Bring Your Own Agent and the Agent API for registering your agent server.


Best Practices

Token Management

✅ DO:

  • Generate tokens on your backend, never expose API keys to frontend
  • Implement token refresh logic (tokens expire after 24 hours)
  • Store tokens securely (session storage, not local storage)
  • Validate user authentication before generating tokens

❌ DON'T:

  • Expose your Sharely API key in frontend code
  • Share tokens between different users
  • Pass externalUserId without an externalToken — it is not a supported mode and would amount to unverified identity
  • Generate tokens without user authentication

Error Handling

async function initializeSharelyWithErrorHandling() {
  try {
    // Check if script loaded
    if (!window.sharelyai) {
      throw new Error('Sharely script not loaded');
    }
 
    // Fetch token
    const response = await fetch('/api/sharely-token');
    if (!response.ok) {
      throw new Error(`Token fetch failed: ${response.status}`);
    }
 
    const { token, spaceId } = await response.json();
 
    // Initialize
    window.sharelyai.initialize({
      workspaceId: WORKSPACE_ID,
      externalToken: token,
      spaceId: spaceId,
      externalUserId: user.email,
      mode: 'placed-inline',
      onError: (error) => {
        console.error('Sharely runtime error:', error);
      },
      displayMode: {
        OPEN_BY_DEFAULT: true,
        WIDTH: '100%',
        MODE: 'PRIVATE',
        VIEWS: {
          CHAT: { SHOW: true },
          SEARCH: { SHOW: true },
          BROWSE: { SHOW: true }
        }
      }
    });
 
    window.sharelyai.render();
 
  } catch (error) {
    console.error('Sharely initialization failed:', error);
 
    // Show fallback UI
    document.getElementById('sharelyai-webcontroller-id').innerHTML = `
      <div style="padding: 20px; border: 1px solid #ddd; border-radius: 4px;">
        <p>AI Assistant temporarily unavailable. Please try again later.</p>
      </div>
    `;
  }
}

User Experience

Loading States:

// Show loading indicator
const container = document.getElementById('sharelyai-webcontroller-id');
container.innerHTML = '<div class="loading">Loading AI Assistant...</div>';
 
// Initialize
try {
  await initializeSharely();
} catch (error) {
  container.innerHTML = '<div class="error">Failed to load</div>';
}

Lazy Loading:

// Only initialize when user interacts
document.getElementById('open-ai-button').addEventListener('click', function() {
  initializeSharely();
  this.disabled = true;
  this.textContent = 'AI Assistant Loading...';
});

Security Recommendations

  1. Always use HTTPS for production deployments
  2. Validate users on your backend before generating tokens
  3. Implement rate limiting on token generation endpoints
  4. Use Content Security Policy headers to restrict script sources
  5. Monitor token usage for suspicious activity
  6. Rotate API keys periodically

Troubleshooting

Web Control Not Appearing

Problem: Nothing appears in the container div

Solutions:

  1. Check that the script is loaded: console.log(window.sharelyai)
  2. Verify the container ID is exactly sharelyai-webcontroller-id
  3. Ensure render() is called after initialize()
  4. Check browser console for errors — an invalid mode value throws at initialize()

Token Generation Failures

Problem: Backend fails to generate tokens

Solutions:

  1. Verify API key is correct and not expired
  2. Check that workspace ID and organization ID are correct
  3. Ensure role exists (if using RBAC)
  4. Check API endpoint URLs are correct

Content Not Filtered by Role

Problem: Users see content outside their role

Solutions:

  1. Verify RBAC is enabled for your workspace
  2. Check that roles are assigned to knowledge items
  3. Confirm customerRoleId is included in token generation
  4. Verify token contains role information (decode JWT)

Related Documentation

Open Source

APIs

Concepts


Getting Help


Next Steps

  1. Get your API credentials - Sign up at https://app.sharely.ai (opens in a new tab)
  2. Choose your mode - Anonymous or host-asserted integration
  3. Implement backend token generation - Use the Spaces API (host-asserted mode)
  4. Initialize the Web Control - Add to your frontend
  5. Test thoroughly - Verify authentication and content filtering
  6. Deploy to production - Launch your AI-powered application