Getting Started with WebLLM

Build your first AI-powered web app in 5 minutes—no server, no API keys in code

WebLLM Team
Getting Started with WebLLM

You're about to add AI to a web app with one line of JavaScript. No server setup. No API key management in your code. No backend infrastructure.

const response = await navigator.llm.prompt('Explain this concept');

This tutorial will get you from zero to working AI app in 5 minutes.

Prerequisites

  • Chrome browser
  • Any text editor
  • 5 minutes

That's it. No Node.js, no build tools, no server.

Step 1: Install the WebLLM Extension (1 minute)

  1. Open Chrome
  2. Go to the Chrome Web Store and search for "WebLLM"
  3. Click "Add to Chrome"
  4. Click the extension icon in your toolbar to open settings

The extension adds navigator.llm to all web pages you visit.

Step 2: Configure a Provider (1 minute)

The extension needs at least one AI provider. You have options:

Option A: Use Ollama (Free, Local, Private)

  1. Download Ollama
  2. Run it (it starts a local server)
  3. In WebLLM extension settings, enable "Ollama"
  4. Default settings work (http://localhost:11434)

Ollama is free and runs AI models on your computer. Great for privacy.

Option B: Use OpenAI (Requires API Key)

  1. Get an API key from platform.openai.com
  2. In WebLLM extension settings, enable "OpenAI"
  3. Enter your API key
  4. Choose a model (gpt-3.5-turbo for testing)

Option C: Use Any Supported Provider

WebLLM supports many providers: Anthropic, Google, Mistral, local servers, and more. Pick what you have access to.

Step 3: Create Your HTML File (1 minute)

Create a file called ai-demo.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>My First WebLLM App</title>
    <style>
      body {
        font-family: system-ui, sans-serif;
        max-width: 600px;
        margin: 50px auto;
        padding: 20px;
      }
      textarea {
        width: 100%;
        height: 100px;
        margin: 10px 0;
        padding: 10px;
        font-size: 16px;
      }
      button {
        padding: 10px 20px;
        font-size: 16px;
        cursor: pointer;
      }
      #response {
        margin-top: 20px;
        padding: 20px;
        background: #f5f5f5;
        border-radius: 8px;
        white-space: pre-wrap;
        min-height: 100px;
      }
    </style>
  </head>
  <body>
    <h1>AI Assistant</h1>

    <textarea id="prompt" placeholder="Ask me anything...">
What are three interesting facts about octopuses?</textarea
    >

    <button id="askButton">Ask AI</button>

    <div id="response">Response will appear here...</div>

    <script>
      const promptInput = document.getElementById('prompt');
      const askButton = document.getElementById('askButton');
      const responseDiv = document.getElementById('response');

      askButton.addEventListener('click', async () => {
        // Check if WebLLM is available
        if (!navigator.llm) {
          responseDiv.textContent = '❌ WebLLM not found. Please install the extension.';
          return;
        }

        const prompt = promptInput.value.trim();
        if (!prompt) {
          responseDiv.textContent = 'Please enter a question.';
          return;
        }

        // Show loading state
        askButton.disabled = true;
        responseDiv.textContent = '⏳ Thinking...';

        try {
          // This is the magic line
          const response = await navigator.llm.prompt(prompt);
          responseDiv.textContent = response;
        } catch (error) {
          responseDiv.textContent = `❌ Error: ${error.message}`;
        } finally {
          askButton.disabled = false;
        }
      });
    </script>
  </body>
</html>

Step 4: Open and Use (1 minute)

  1. Open ai-demo.html in Chrome (File → Open File, or drag into browser)
  2. Click "Ask AI"
  3. The first time, you'll see a permission prompt—click "Allow"
  4. Wait a moment for the response
  5. You now have a working AI app!

Step 5: Add Streaming (1 minute)

Streaming shows responses as they're generated, like ChatGPT. Update your script:

<script>
  const promptInput = document.getElementById('prompt');
  const askButton = document.getElementById('askButton');
  const responseDiv = document.getElementById('response');

  askButton.addEventListener('click', async () => {
    if (!navigator.llm) {
      responseDiv.textContent = '❌ WebLLM not found. Please install the extension.';
      return;
    }

    const prompt = promptInput.value.trim();
    if (!prompt) {
      responseDiv.textContent = 'Please enter a question.';
      return;
    }

    askButton.disabled = true;
    responseDiv.textContent = '';

    try {
      // Stream the response for real-time display
      const stream = navigator.llm.streamPrompt(prompt);

      for await (const chunk of stream) {
        responseDiv.textContent += chunk;
      }
    } catch (error) {
      responseDiv.textContent = `❌ Error: ${error.message}`;
    } finally {
      askButton.disabled = false;
    }
  });
</script>

Now responses appear word by word, giving users immediate feedback.

What Just Happened?

Let's break down the flow:

  1. Your code called navigator.llm.prompt()
  2. The extension received the request
  3. Permission check (granted on first use with user consent)
  4. Provider routing - extension chose the best available provider
  5. AI processing - your configured provider processed the request
  6. Response returned - back through the extension to your code

Your app didn't need to know:

  • Which AI provider the user has
  • API authentication details
  • Endpoint URLs
  • Model names

The extension handled all of that.

Beyond the Basics

Adding a System Prompt

Make the AI behave a certain way:

const session = await navigator.llm.createSession({
  system:
    'You are a helpful coding assistant. Keep answers concise and include code examples.',
});

const response = await session.prompt('How do I reverse a string in JavaScript?');

Building a Chat

Maintain conversation context:

const session = await navigator.llm.createSession({
  system: 'You are a friendly assistant.',
});

// First message
const response1 = await session.prompt('My name is Alex.');
console.log(response1); // "Nice to meet you, Alex!"

// Follow-up (remembers context)
const response2 = await session.prompt("What's my name?");
console.log(response2); // "Your name is Alex."

Graceful Degradation

Handle cases where WebLLM isn't available:

async function askAI(prompt) {
  // Try WebLLM first
  if (navigator.llm) {
    try {
      return await navigator.llm.prompt(prompt);
    } catch (error) {
      console.warn('WebLLM error:', error);
    }
  }

  // Fallback to your server
  const response = await fetch('/api/ai', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ prompt }),
  });

  const data = await response.json();
  return data.response;
}

Checking Permissions

async function checkAIAccess() {
  if (!navigator.llm) {
    return { available: false, reason: 'extension-not-installed' };
  }

  try {
    const permission = await navigator.permissions.query({ name: 'llm' });

    return {
      available: permission.state === 'granted',
      state: permission.state,
      reason: permission.state === 'denied' ? 'permission-denied' : null,
    };
  } catch {
    return { available: true, state: 'unknown' };
  }
}

Complete Example: AI Writing Assistant

Here's a more complete example—an AI writing assistant:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>AI Writing Assistant</title>
    <style>
      * {
        box-sizing: border-box;
      }
      body {
        font-family: system-ui, sans-serif;
        max-width: 800px;
        margin: 0 auto;
        padding: 20px;
        line-height: 1.6;
      }
      h1 {
        color: #333;
      }
      .container {
        display: flex;
        gap: 20px;
        flex-wrap: wrap;
      }
      .panel {
        flex: 1;
        min-width: 300px;
      }
      textarea {
        width: 100%;
        height: 300px;
        padding: 15px;
        font-size: 16px;
        border: 2px solid #ddd;
        border-radius: 8px;
        resize: vertical;
      }
      textarea:focus {
        border-color: #007bff;
        outline: none;
      }
      .actions {
        margin: 15px 0;
        display: flex;
        gap: 10px;
        flex-wrap: wrap;
      }
      button {
        padding: 10px 20px;
        font-size: 14px;
        border: none;
        border-radius: 6px;
        cursor: pointer;
        transition: background 0.2s;
      }
      .primary {
        background: #007bff;
        color: white;
      }
      .primary:hover {
        background: #0056b3;
      }
      .secondary {
        background: #e9ecef;
        color: #333;
      }
      .secondary:hover {
        background: #dee2e6;
      }
      button:disabled {
        opacity: 0.6;
        cursor: not-allowed;
      }
      #output {
        background: #f8f9fa;
        padding: 15px;
        border-radius: 8px;
        white-space: pre-wrap;
        min-height: 300px;
      }
      .status {
        font-size: 14px;
        color: #666;
        margin-top: 10px;
      }
      .error {
        color: #dc3545;
      }
    </style>
  </head>
  <body>
    <h1>✍️ AI Writing Assistant</h1>

    <div class="container">
      <div class="panel">
        <h3>Your Text</h3>
        <textarea id="input" placeholder="Enter your text here...">
The quick brown fox jumps over the lazy dog. This is a sample text that you can improve, summarize, or expand using AI.</textarea
        >

        <div class="actions">
          <button class="primary" onclick="transform('improve')">✨ Improve</button>
          <button class="secondary" onclick="transform('summarize')">📝 Summarize</button>
          <button class="secondary" onclick="transform('expand')">📖 Expand</button>
          <button class="secondary" onclick="transform('simplify')">🎯 Simplify</button>
          <button class="secondary" onclick="transform('formal')">👔 Make Formal</button>
          <button class="secondary" onclick="transform('casual')">😊 Make Casual</button>
        </div>
      </div>

      <div class="panel">
        <h3>AI Output</h3>
        <div id="output">Click a button to transform your text...</div>
      </div>
    </div>

    <div class="status" id="status"></div>

    <script>
      const input = document.getElementById('input');
      const output = document.getElementById('output');
      const status = document.getElementById('status');
      const buttons = document.querySelectorAll('button');

      const prompts = {
        improve:
          'Improve the following text, making it clearer and more engaging while keeping the same meaning:\n\n',
        summarize: 'Summarize the following text in 2-3 sentences:\n\n',
        expand: 'Expand the following text with more detail and examples:\n\n',
        simplify: 'Simplify the following text so a 10-year-old could understand it:\n\n',
        formal: 'Rewrite the following text in a formal, professional tone:\n\n',
        casual: 'Rewrite the following text in a casual, friendly tone:\n\n',
      };

      async function transform(action) {
        // Check WebLLM availability
        if (!navigator.llm) {
          status.innerHTML =
            '<span class="error">❌ WebLLM extension not installed</span>';
          return;
        }

        const text = input.value.trim();
        if (!text) {
          status.innerHTML = '<span class="error">Please enter some text first</span>';
          return;
        }

        // Disable buttons and show loading
        buttons.forEach((b) => (b.disabled = true));
        output.textContent = '';
        status.textContent = '⏳ Processing...';

        try {
          const prompt = prompts[action] + text;
          const stream = navigator.llm.streamPrompt(prompt);

          for await (const chunk of stream) {
            output.textContent += chunk;
          }

          status.textContent = '✅ Done!';
        } catch (error) {
          status.innerHTML = `<span class="error">❌ ${error.message}</span>`;
          output.textContent = 'Error occurred. Please try again.';
        } finally {
          buttons.forEach((b) => (b.disabled = false));
        }
      }
    </script>
  </body>
</html>

Save this as writing-assistant.html and open it in Chrome. You have a functional AI writing tool.

Common Issues and Solutions

"WebLLM not found"

The extension isn't installed or isn't enabled for this page.

  • Check that the extension is installed
  • Make sure it's enabled in chrome://extensions
  • Refresh the page

"No providers available"

No AI provider is configured in the extension.

  • Open extension settings
  • Configure at least one provider (Ollama is free!)
  • Make sure the provider is working (test connection)

Permission denied

User denied AI access for this site.

  • Go to extension settings
  • Find the site in permissions
  • Reset or allow access

Slow responses

Depends on the provider:

  • Ollama: First request loads the model (slow), subsequent requests faster
  • OpenAI/Cloud: Network latency, usually 1-3 seconds
  • Large models: Slower but higher quality

Next Steps

You've built your first WebLLM app! Here's where to go next:

Build More

  • Chat interface: Full conversational AI with message history
  • Content tools: Summarizers, translators, tone adjusters
  • Code helpers: Explain code, generate tests, debug assistance
  • Data extraction: Pull structured info from unstructured text

Learn More

Join the Community


You just added AI to a web app in 5 minutes. No servers. No API keys in code. No complex setup.

This is what browser-native AI looks like.

In this article:

Share this article: