<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Daisy Auma</title>
    <description>The latest articles on Forem by Daisy Auma (@daisyauma).</description>
    <link>https://forem.com/daisyauma</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1223876%2F3f30736b-9125-455e-b2eb-96f2e5bdc848.png</url>
      <title>Forem: Daisy Auma</title>
      <link>https://forem.com/daisyauma</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/daisyauma"/>
    <language>en</language>
    <item>
      <title>How I Built Customer-Specific Documentation in Mintlify</title>
      <dc:creator>Daisy Auma</dc:creator>
      <pubDate>Tue, 03 Feb 2026 07:25:01 +0000</pubDate>
      <link>https://forem.com/daisyauma/how-i-built-customer-specific-documentation-in-mintlify-4mb6</link>
      <guid>https://forem.com/daisyauma/how-i-built-customer-specific-documentation-in-mintlify-4mb6</guid>
      <description>&lt;p&gt;&lt;em&gt;A practical guide to serving different audiences without maintaining multiple doc sites&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's a challenge I didn't expect when I started documenting an AI platform: different customers need different documentation.&lt;/p&gt;

&lt;p&gt;Some customers want security details, compliance information, and deployment guides. Others want quick starts and code examples. Some customers use specific features that others never touch.&lt;/p&gt;

&lt;p&gt;The obvious solution? Multiple documentation sites. One for each customer type, one for each use case.&lt;/p&gt;

&lt;p&gt;The obvious solution is also a maintenance nightmare.&lt;/p&gt;

&lt;p&gt;Here's how I actually solved it using Mintlify's group-based access control, serving different audiences from a single doc site without losing my mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: One Product, Many Audiences
&lt;/h2&gt;

&lt;p&gt;When you're documenting a platform with diverse customers, you quickly realize that "one size fits all" documentation doesn't work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The challenges I faced:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Different companies needed different content based on their use cases&lt;/li&gt;
&lt;li&gt;Different use cases required different getting-started paths&lt;/li&gt;
&lt;li&gt;Some features were only relevant to specific customer segments&lt;/li&gt;
&lt;li&gt;Maintaining multiple doc sites would mean duplicating shared content across all of them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I needed a way to personalize the documentation experience without creating separate silos.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Primary Solution: Group-Based Access Control
&lt;/h2&gt;

&lt;p&gt;For truly customer-specific content that should only be visible to certain users, Mintlify supports group-based access control. This is the approach I went with.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable authentication (OAuth or JWT)&lt;/li&gt;
&lt;li&gt;Your auth system returns user groups&lt;/li&gt;
&lt;li&gt;Pages can be restricted to specific groups&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Example: Restricting a page to specific users&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
title: "Advanced Configuration Guide"
groups: ["pro-users"]
---

# Advanced Configuration Guide

This guide covers features available to pro users...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Users not in the "pro-users" group won't see this page. They'll get a 404 if they try to access it directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example: User info returned by your auth system&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"groups"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"pro-users"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"beta-testers"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expiresAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1735689600&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This user can access pages tagged with either "pro-users" or "beta-testers" groups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use group-based access:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Paid features that free users shouldn't see&lt;/li&gt;
&lt;li&gt;Beta documentation for early adopters&lt;/li&gt;
&lt;li&gt;Partner-specific integrations&lt;/li&gt;
&lt;li&gt;Customer-specific content based on their plan or use case&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach lets me maintain one documentation site while serving completely different content to different customer segments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tabs for Different Audiences
&lt;/h2&gt;

&lt;p&gt;The simplest way to serve multiple audiences on the same page is tabs.&lt;/p&gt;

&lt;p&gt;Mintlify's tab component lets you present alternative content side-by-side, letting readers choose what's relevant to them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example: Different code languages&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Tabs&amp;gt;
  &amp;lt;Tab title="Python"&amp;gt;
    ```

python
    import requests

    response = requests.get("https://api.example.com/data")
    print(response.json())


    ```
  &amp;lt;/Tab&amp;gt;

  &amp;lt;Tab title="JavaScript"&amp;gt;
    ```

javascript
    fetch("https://api.example.com/data")
      .then(response =&amp;gt; response.json())
      .then(data =&amp;gt; console.log(data));


    ```
  &amp;lt;/Tab&amp;gt;

  &amp;lt;Tab title="cURL"&amp;gt;
    ```

bash
    curl https://api.example.com/data


    ```
  &amp;lt;/Tab&amp;gt;
&amp;lt;/Tabs&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When to use tabs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Content that's parallel (same topic, different approaches)&lt;/li&gt;
&lt;li&gt;Users typically only need one option&lt;/li&gt;
&lt;li&gt;All options should be equally visible/accessible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When NOT to use tabs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Content that most users need to read entirely&lt;/li&gt;
&lt;li&gt;Options that build on each other&lt;/li&gt;
&lt;li&gt;More than 4-5 alternatives (gets unwieldy)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conditional Navigation Paths
&lt;/h2&gt;

&lt;p&gt;Not all customers need to see all sections of your documentation.&lt;/p&gt;

&lt;p&gt;Mintlify lets you organize navigation in &lt;code&gt;docs.json&lt;/code&gt; to create logical groupings. Combined with authentication and groups, you can show different navigation to different users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example: Organized navigation&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"navigation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"groups"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"group"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Getting Started"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"public"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"pages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"quickstart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"installation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"first-api-call"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"group"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Core Features"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"pages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"feature-overview"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"basic-usage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"best-practices"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"group"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Advanced"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"pages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"custom-integrations"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"api-reference"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"troubleshooting"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The key insight:&lt;/strong&gt; You don't always need to hide content. Sometimes, clear organization is enough.&lt;/p&gt;

&lt;p&gt;Users will naturally gravitate to the sections relevant to them. The content is available to everyone, but the navigation makes it easy to find what's relevant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Public vs. Protected Content
&lt;/h2&gt;

&lt;p&gt;Sometimes you want most of your docs behind authentication, but certain pages public.&lt;/p&gt;

&lt;p&gt;Mintlify makes this straightforward with the &lt;code&gt;public&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make a single page public:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
title: "API Overview"
public: true
---

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Make an entire section public:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"navigation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"groups"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"group"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Public Docs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"public"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"pages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"overview"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"quickstart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"pricing"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"group"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Protected Docs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"pages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"api-reference"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"advanced-guides"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"internal-tools"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;My approach:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public: Overview, quickstart, basic concepts (helps with SEO and sales)&lt;/li&gt;
&lt;li&gt;Protected: Detailed API reference, advanced guides, customer-specific content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives potential customers enough to evaluate the product while keeping detailed documentation for actual users.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It All Fits Together
&lt;/h2&gt;

&lt;p&gt;Here's how I think about the hierarchy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────┐
│              All Documentation               │
├─────────────────────────────────────────────┤
│  Public Pages                                │
│  (Anyone can access)                         │
│  - Overview, quickstart, basic concepts      │
├─────────────────────────────────────────────┤
│  Authenticated Pages                         │
│  (Logged-in users)                           │
│  - Full API reference, guides, tutorials     │
├─────────────────────────────────────────────┤
│  Group-Restricted Pages                      │
│  (Specific user groups)                      │
│  - Pro users: advanced features              │
│  - Beta: upcoming features                   │
│  - Partners: integration guides              │
└─────────────────────────────────────────────┘

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within each level, I use tabs and clear navigation to help users find what's relevant without hiding content unnecessarily.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Start with organization, not restriction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before adding authentication or group controls, ask: "Can I solve this with better organization?"&lt;/p&gt;

&lt;p&gt;Clear sections and good navigation often eliminate the need for access control. Users self-select into the content they need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Tabs are your friend&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Any time you're tempted to create parallel pages (Python vs. JavaScript, beginner vs. advanced), consider tabs first. They keep related content together and reduce maintenance burden.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Don't over-segment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's tempting to create hyper-specific documentation for every customer segment. Resist this urge.&lt;/p&gt;

&lt;p&gt;Every segment you create is content you have to maintain. Start broad and only segment when you have clear evidence that users need it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Test with real users&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The best way to know if your documentation structure works? Watch someone use it.&lt;/p&gt;

&lt;p&gt;Do customers find the content they need? Do new users complete the quickstart? Where do people get stuck?&lt;/p&gt;

&lt;p&gt;User testing reveals gaps that internal review never will.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;With this approach, I maintain a single Mintlify documentation site that serves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New users exploring the product (public quickstart)&lt;/li&gt;
&lt;li&gt;Active customers building integrations (authenticated API reference)&lt;/li&gt;
&lt;li&gt;Specific customer segments with unique requirements (group-restricted docs)&lt;/li&gt;
&lt;li&gt;Beta testers trying new features (group-restricted beta docs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No duplicate content. No multiple sites to maintain. One source of truth with flexible presentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.mintlify.com/docs/deploy/authentication-setup" rel="noopener noreferrer"&gt;Mintlify Authentication Setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mintlify.com/docs/settings/navigation" rel="noopener noreferrer"&gt;Mintlify Navigation Configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mintlify.com/docs/content/components/tabs" rel="noopener noreferrer"&gt;Mintlify Tabs Component&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How do you handle customer-specific documentation? I'd love to hear what approaches have worked for you.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>devrel</category>
    </item>
    <item>
      <title>The Lonely Side of Being a Solo Technical Writer</title>
      <dc:creator>Daisy Auma</dc:creator>
      <pubDate>Mon, 02 Feb 2026 08:27:17 +0000</pubDate>
      <link>https://forem.com/daisyauma/the-lonely-side-of-being-a-solo-technical-writer-4pa7</link>
      <guid>https://forem.com/daisyauma/the-lonely-side-of-being-a-solo-technical-writer-4pa7</guid>
      <description>&lt;p&gt;&lt;em&gt;What it's really like to be the only one who does what you do&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I have a great team. Engineers review my documentation for accuracy. Product managers answer my endless questions. My work gets feedback before it ships.&lt;/p&gt;

&lt;p&gt;And yet, being a solo technical writer is still lonely.&lt;/p&gt;

&lt;p&gt;Not lonely in the "no one talks to me" sense. Lonely in the "no one else thinks about what I think about" sense.&lt;/p&gt;

&lt;p&gt;Here's what that actually looks like.&lt;/p&gt;

&lt;h2&gt;
  
  
  No One to Discuss the Craft With
&lt;/h2&gt;

&lt;p&gt;Last week, I spent an hour deciding whether a piece of content should be a tutorial or a conceptual guide.&lt;/p&gt;

&lt;p&gt;Tutorials are step-by-step. Conceptual guides explain the "why." The distinction matters for how users navigate documentation. It affects the structure, the tone, the level of detail.&lt;/p&gt;

&lt;p&gt;I made the decision. It was the right call. But I made it alone.&lt;/p&gt;

&lt;p&gt;Engineers review my docs for technical accuracy. They catch errors, clarify edge cases, and make sure the code examples actually work. That's invaluable.&lt;/p&gt;

&lt;p&gt;But they're not thinking about information architecture. They're not debating whether this content belongs in the quickstart or the API reference. They're not wondering if users will find this page through search or navigation.&lt;/p&gt;

&lt;p&gt;That's my job. And I'm the only one doing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wins That Don't Translate
&lt;/h2&gt;

&lt;p&gt;A few weeks ago, I restructured our documentation sidebar. It took hours — reorganizing pages, fixing broken links, testing navigation paths, making sure users could find what they needed in fewer clicks.&lt;/p&gt;

&lt;p&gt;When I finished, I was genuinely proud. The documentation was meaningfully better.&lt;/p&gt;

&lt;p&gt;I mentioned it in our team Slack. People responded positively — thumbs up, a few nice comments. But I could tell it wasn't a priority for anyone else. And why would it be? They had features to ship, bugs to fix, customers to support.&lt;/p&gt;

&lt;p&gt;The documentation sidebar wasn't keeping anyone else up at night. Just me.&lt;/p&gt;

&lt;p&gt;But it means celebrating alone. The wins that matter most to me are often invisible to everyone else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Carrying the Vision Solo
&lt;/h2&gt;

&lt;p&gt;Here's something I didn't expect about this role: I'm the only one who sees the full picture.&lt;/p&gt;

&lt;p&gt;Engineers know their features deeply. Product managers know the roadmap. Support knows the common questions.&lt;/p&gt;

&lt;p&gt;But I'm the only one who sees how all of it connects in the documentation. I'm the only one thinking about the user's journey from first visit to successful implementation. I'm the only one noticing that we have three pages explaining the same concept slightly differently.&lt;/p&gt;

&lt;p&gt;That's powerful. It's also heavy.&lt;/p&gt;

&lt;p&gt;When documentation has gaps, I feel responsible. When users get confused, I wonder what I missed. There's no one to share that weight with, no peer to say "I noticed that too" or "here's how I'd approach it."&lt;/p&gt;

&lt;h2&gt;
  
  
  Different Mental Models
&lt;/h2&gt;

&lt;p&gt;I think in user journeys. Engineers think in features.&lt;/p&gt;

&lt;p&gt;When a new feature ships, engineers are excited about what it does. I'm thinking about how users will discover it, understand it, and actually use it.&lt;/p&gt;

&lt;p&gt;"What does this feature do?" is an engineering question.&lt;br&gt;
"What problem does this solve for users, and how do we explain it so they succeed?" is a documentation question.&lt;/p&gt;

&lt;p&gt;Both are valid. But they're different languages. And sometimes it feels like I'm the only one speaking mine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Flip Side: Freedom
&lt;/h2&gt;

&lt;p&gt;I don't want this to sound like complaining. Because honestly? There's a flip side.&lt;/p&gt;

&lt;p&gt;Being the solo technical writer means I own the documentation voice. I set the standards. When I decide we're going to write in a certain style, that's the style. No committee. No endless debates. No death by consensus.&lt;/p&gt;

&lt;p&gt;I can experiment. I can try new approaches. If something doesn't work, I can change it without convincing five people first.&lt;/p&gt;

&lt;p&gt;The autonomy is real. And it's valuable.&lt;/p&gt;

&lt;p&gt;But autonomy and loneliness are two sides of the same coin. You can't have one without accepting some of the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding Your People Outside Work
&lt;/h2&gt;

&lt;p&gt;The thing that's helped most? Finding community outside my company.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.writethedocs.org/slack/" rel="noopener noreferrer"&gt;Write the Docs Slack&lt;/a&gt; has been invaluable. Thousands of technical writers sharing challenges, asking questions, and celebrating wins that actually make sense to each other.&lt;/p&gt;

&lt;p&gt;"I finally got the API reference structure right" gets real excitement there. People understand.&lt;/p&gt;

&lt;p&gt;Twitter and LinkedIn have helped too. Following other technical writers, reading about their processes, realizing that my challenges aren't unique — that's been grounding.&lt;/p&gt;

&lt;p&gt;You don't have to work with other technical writers to have a community of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I've Learned
&lt;/h2&gt;

&lt;p&gt;A few things I'd tell someone starting as a solo technical writer:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build relationships with engineers who explain things well.&lt;/strong&gt; Not all engineers communicate the same way. Find the ones who naturally translate complexity into clarity. They'll make your job easier and your documentation better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create your own feedback loops.&lt;/strong&gt; If no one's reviewing your work for documentation quality, find ways to get that feedback. Watch new users navigate your docs. Ask support what questions keep coming up. The feedback exists — you just have to seek it out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Celebrate your own wins.&lt;/strong&gt; No one else will fully understand why that sidebar restructure mattered. Celebrate it anyway. Keep a list of improvements you've made. Look at it when you need a reminder that your work matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Find your community.&lt;/strong&gt; Write the Docs, DevRel communities, Twitter, LinkedIn — the people who understand your work exist. They're just not in your office.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accept the trade-off.&lt;/strong&gt; Autonomy comes with loneliness. Freedom comes with responsibility. You can't have the good parts without accepting the harder parts too.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's Worth It
&lt;/h2&gt;

&lt;p&gt;Being a solo technical writer is lonely sometimes. That's the truth.&lt;/p&gt;

&lt;p&gt;But it's also deeply rewarding. You own something meaningful. You see the direct impact of your work. You build something that helps real people accomplish real things.&lt;/p&gt;

&lt;p&gt;The loneliness isn't a bug. It's just part of the deal.&lt;/p&gt;

&lt;p&gt;And once you accept that, you can build the support systems you need — inside and outside your company — to thrive anyway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are you a solo technical writer? What's been your experience? I'd love to hear what's worked for you.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devrel</category>
      <category>documentation</category>
    </item>
    <item>
      <title>Writing API Documentation That Developers Actually Read</title>
      <dc:creator>Daisy Auma</dc:creator>
      <pubDate>Tue, 20 Jan 2026 10:40:47 +0000</pubDate>
      <link>https://forem.com/daisyauma/writing-api-documentation-that-developers-actually-read-2jn5</link>
      <guid>https://forem.com/daisyauma/writing-api-documentation-that-developers-actually-read-2jn5</guid>
      <description>&lt;p&gt;&lt;em&gt;Lessons from documenting AI APIs as a solo technical writer&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's a truth that took me too long to learn: Most API documentation is written for the writer, not the reader.&lt;/p&gt;

&lt;p&gt;We document endpoints in alphabetical order because it's easy to organize. We list every parameter because we want to be thorough. We write comprehensive reference docs and wonder why developers still ask basic questions in support channels.&lt;/p&gt;

&lt;p&gt;As the only technical writer at an AI startup, I've had to rethink how I approach API documentation. Here's what actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start With Use Cases, Not Endpoints
&lt;/h2&gt;

&lt;p&gt;The biggest mistake I made early on was organizing documentation by endpoint.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;POST /users/createGET /users/listPUT /users/update&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Alphabetical. Comprehensive. Completely useless for someone trying to figure out how to get started.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What developers actually want to know:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do I analyze a video file?&lt;/li&gt;
&lt;li&gt;How do I monitor sensor data in real-time?&lt;/li&gt;
&lt;li&gt;How do I get my first successful API call working?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now I start every API section with a use case. Not "here's what this endpoint does" but "here's what you're trying to accomplish, and here's how."&lt;/p&gt;

&lt;p&gt;The endpoint reference still exists. But it's not the entry point. The use case is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stripe.com/docs/api" rel="noopener noreferrer"&gt;Stripe's API documentation&lt;/a&gt; is the gold standard here, they lead with use cases like "Accept a payment" before diving into endpoint references.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Quickstart Is Everything
&lt;/h2&gt;

&lt;p&gt;If your quickstart doesn't work, nothing else matters.&lt;/p&gt;

&lt;p&gt;I learned this the hard way when we had a hackathon approaching and our quickstart guide wasn't returning results. The API reference was complete. The conceptual docs were polished. But developers couldn't get past step one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A good quickstart should:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Work in under 5 minutes&lt;/li&gt;
&lt;li&gt;Require minimal setup&lt;/li&gt;
&lt;li&gt;Show a real result (not just a 200 OK)&lt;/li&gt;
&lt;li&gt;Be testable by someone who knows nothing about your product&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I now test every quickstart on a fresh environment before publishing. If I can't get it working in 5 minutes with just the docs, it's not ready.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.twilio.com/docs/quickstart" rel="noopener noreferrer"&gt;Twilio's quickstarts&lt;/a&gt; are excellent examples — they get you to a working "Hello World" in minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Examples Must Actually Run
&lt;/h2&gt;

&lt;p&gt;This sounds obvious. It's not.&lt;/p&gt;

&lt;p&gt;I've lost count of how many times I've found code examples that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use deprecated endpoints&lt;/li&gt;
&lt;li&gt;Have typos in parameter names&lt;/li&gt;
&lt;li&gt;Work in the old API version but not the current one&lt;/li&gt;
&lt;li&gt;Are missing required authentication headers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;My process now:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write the code example&lt;/li&gt;
&lt;li&gt;Actually run it&lt;/li&gt;
&lt;li&gt;Copy the exact code that worked into the docs&lt;/li&gt;
&lt;li&gt;Add a test to catch when it breaks&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last part is crucial. APIs change. If you don't have automated checks, your examples will rot.&lt;/p&gt;

&lt;p&gt;One specific lesson: JSON formatting matters more than you think. I once spent hours debugging why an API call failed, only to discover that multi-line JSON with whitespace caused errors, but compact single-line JSON worked fine. That's now documented with a warning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error Documentation Is Not Optional
&lt;/h2&gt;

&lt;p&gt;Most API docs show you the happy path. Request goes in, response comes out, everyone's happy.&lt;/p&gt;

&lt;p&gt;Real developers spend most of their time in the unhappy path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For every endpoint, I now document:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What errors can occur&lt;/li&gt;
&lt;li&gt;What each error code means&lt;/li&gt;
&lt;li&gt;What likely caused it&lt;/li&gt;
&lt;li&gt;How to fix it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;400 - Missing Required Field
{
  "code": "missing_field",
  "message": "Missing field: user_id",
  "suggestion": "Ensure your request body includes the required user_id field"
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This takes more time upfront but dramatically reduces support tickets. When developers can self-serve their way out of errors, everyone wins.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stripe.com/docs/error-codes" rel="noopener noreferrer"&gt;Stripe's error handling documentation&lt;/a&gt; is worth studying, they document every error code with causes and solutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structure Matters More Than You Think
&lt;/h2&gt;

&lt;p&gt;A few formatting lessons I've learned:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use tabs for multi-language examples.&lt;/strong&gt; If you support Python, JavaScript, and cURL, don't make developers scroll past two languages they don't care about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lead with the most common case.&lt;/strong&gt; If 90% of users need the simple version, show that first. Put the advanced options in an expandable section.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Be consistent.&lt;/strong&gt; If you show request first, then response in one section, do it that way everywhere. Developers build mental models of your docs. Don't break them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make copy-paste easy.&lt;/strong&gt; If developers need to copy an API key, a URL, or a code snippet, make sure it copies cleanly without extra whitespace or formatting artifacts.&lt;/p&gt;

&lt;p&gt;If you're using a docs-as-code approach, tools like &lt;a href="https://mintlify.com/" rel="noopener noreferrer"&gt;Mintlify&lt;/a&gt;, &lt;a href="https://readme.com/" rel="noopener noreferrer"&gt;ReadMe&lt;/a&gt;, or &lt;a href="https://redocly.com/" rel="noopener noreferrer"&gt;Redocly&lt;/a&gt; have built-in components for tabs, code groups, and interactive examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Your Own Documentation
&lt;/h2&gt;

&lt;p&gt;The best feedback on my docs comes from watching someone use them.&lt;/p&gt;

&lt;p&gt;When we onboarded new team members, I asked them to mark every point where they got confused. Every time they had to ask a question, that was a documentation failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Questions I now ask myself:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can someone with no context get from zero to working API call using just these docs?&lt;/li&gt;
&lt;li&gt;When they hit an error, can they figure out what went wrong?&lt;/li&gt;
&lt;li&gt;Are they copying code examples directly, or having to modify them?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer to any of these is "no," the docs aren't done.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.writethedocs.org/slack/" rel="noopener noreferrer"&gt;Write the Docs community&lt;/a&gt; has been invaluable for getting feedback and learning from other technical writers facing similar challenges.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 3 AM Test
&lt;/h2&gt;

&lt;p&gt;Here's my final quality check:&lt;/p&gt;

&lt;p&gt;Imagine a developer at 3 AM, deadline looming, trying to integrate your API. They're tired. They're frustrated. They don't want to read your carefully crafted conceptual overview.&lt;/p&gt;

&lt;p&gt;Can they find what they need in 30 seconds? Can they copy a working example? Can they troubleshoot errors without filing a support ticket?&lt;/p&gt;

&lt;p&gt;If yes, your docs are ready. If no, keep iterating.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm Still Learning
&lt;/h2&gt;

&lt;p&gt;API documentation is never done. I'm still figuring out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to document AI outputs that aren't deterministic ("results may vary" is true but not helpful)&lt;/li&gt;
&lt;li&gt;How to balance comprehensive reference docs with practical guides&lt;/li&gt;
&lt;li&gt;How to keep docs updated when the API changes weekly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the fundamentals stay the same: start with use cases, make the quickstart bulletproof, test everything, and always write for the developer at 3 AM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources That Have Helped Me
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/apis/design" rel="noopener noreferrer"&gt;Google's API Design Guide&lt;/a&gt; — Principles for designing (and documenting) APIs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docsfordevelopers.com/" rel="noopener noreferrer"&gt;Docs for Developers&lt;/a&gt; — The book that shaped how I think about developer docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://idratherbewriting.com/" rel="noopener noreferrer"&gt;I'd Rather Be Writing&lt;/a&gt; — Tom Johnson's blog on API documentation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.writethedocs.org/slack/" rel="noopener noreferrer"&gt;Write the Docs Slack&lt;/a&gt; — Community of technical writers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What's the best API documentation you've encountered? What made it work?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>documentation</category>
      <category>writing</category>
    </item>
    <item>
      <title>How I Turned Slack Messages Into Documentation</title>
      <dc:creator>Daisy Auma</dc:creator>
      <pubDate>Tue, 13 Jan 2026 06:40:25 +0000</pubDate>
      <link>https://forem.com/daisyauma/how-i-turned-slack-messages-into-documentation-4881</link>
      <guid>https://forem.com/daisyauma/how-i-turned-slack-messages-into-documentation-4881</guid>
      <description>&lt;p&gt;&lt;em&gt;The unsexy process behind gathering information as a solo technical writer&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At startups, knowledge doesn't live in neatly organized wikis or comprehensive handoff documents. It lives in Slack threads, Notion pages with outdated information, and most critically — in engineers' heads.&lt;/p&gt;

&lt;p&gt;As the only technical writer at an AI startup, my job isn't just writing documentation. It's excavating knowledge before it disappears.&lt;/p&gt;

&lt;p&gt;Here's the actual process I use to turn scattered internal conversations into documentation that developers can actually use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Reality of Information Gathering
&lt;/h2&gt;

&lt;p&gt;When I joined my company, I inherited documentation that was outdated and AI-generated. The product had evolved significantly, but the docs hadn't kept pace. The knowledge existed — it was just trapped in places I couldn't access.&lt;/p&gt;

&lt;p&gt;I quickly learned that the best explanations weren't in formal documents. They were in Slack messages where an engineer answered a customer question at 11 PM. In casual conversations during standups. In the comments of pull requests.&lt;/p&gt;

&lt;p&gt;My job became part technical writer, part archaeologist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Build Relationships Before You Need Them
&lt;/h2&gt;

&lt;p&gt;This is the step most technical writers skip, and it's the most important one.&lt;/p&gt;

&lt;p&gt;Engineers are busy. They're shipping features, fixing bugs, and attending meetings. Documentation is rarely their priority. If you only show up when you need something, you'll always be at the bottom of their to-do list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I do instead:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I show up in engineering channels even when I don't need anything. I react to their wins. I ask genuine questions about what they're building. When I see an interesting technical discussion, I engage with curiosity, not just for documentation purposes.&lt;/p&gt;

&lt;p&gt;When I finally do need their help documenting a feature, I'm not a stranger asking for a favor. I'm a colleague they've interacted with.&lt;/p&gt;

&lt;p&gt;This sounds soft, but it's the difference between getting a response in two hours versus two weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Ask Specific Questions, Not Open-Ended Ones
&lt;/h2&gt;

&lt;p&gt;Early on, I made the mistake of asking engineers: "Can you explain how this feature works?"&lt;/p&gt;

&lt;p&gt;The responses were either overwhelming (a 30-minute monologue covering edge cases I didn't need) or unhelpfully brief ("It does what the PR says").&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What actually works:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of "How does this work?", I ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"What happens when a user sends an invalid API key?"&lt;/li&gt;
&lt;li&gt;"What's the most common mistake customers make with this endpoint?"&lt;/li&gt;
&lt;li&gt;"If this fails, what error message do they see?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Specific questions get specific answers. Specific answers become useful documentation.&lt;/p&gt;

&lt;p&gt;The best question I've learned to ask: &lt;strong&gt;"What do you wish customers understood before they start using this?"&lt;/strong&gt; Engineers love answering this one because it lets them vent about the support tickets they're tired of seeing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Capture Conversations in Real-Time
&lt;/h2&gt;

&lt;p&gt;The gold is in the Slack threads. Here's my actual process:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When I see an engineer explain something well in Slack:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I screenshot or copy the message immediately&lt;/li&gt;
&lt;li&gt;I DM them: "That explanation was really clear. Mind if I turn this into docs?"&lt;/li&gt;
&lt;li&gt;Most say yes. Many are flattered.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;When a customer asks a question in a support channel:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I note the question (this tells me what's unclear in current docs)&lt;/li&gt;
&lt;li&gt;I watch how the engineer responds&lt;/li&gt;
&lt;li&gt;I add this to my documentation backlog&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;When I'm in meetings:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I keep a running note of anything that makes me think "wait, is that documented?" It usually isn't.&lt;/p&gt;

&lt;p&gt;The key is capturing knowledge when it's fresh. Engineers explain things clearly in the moment, but ask them to re-explain two weeks later and the clarity is gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Make Reviews Stupidly Easy
&lt;/h2&gt;

&lt;p&gt;Getting engineers to review documentation is like pulling teeth — unless you make it effortless.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What doesn't work:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"Hey, can you review the docs I wrote?"&lt;/p&gt;

&lt;p&gt;This gets ignored because it feels like an undefined time commitment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What works:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"Hey, can you check if the code example in section 3 actually runs? Should take 5 minutes."&lt;/p&gt;

&lt;p&gt;I break reviews into tiny, specific asks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Is this parameter description accurate?"&lt;/li&gt;
&lt;li&gt;"Did I get the error codes right?"&lt;/li&gt;
&lt;li&gt;"Any gotchas I should add?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also learned to set deadlines with consequences:&lt;/p&gt;

&lt;p&gt;"I'm publishing this Thursday at 2 PM. If I don't hear back, I'm assuming it's accurate."&lt;/p&gt;

&lt;p&gt;This sounds aggressive, but it works. Engineers don't want wrong documentation published under their name. They'll make time to respond when there's a deadline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Build a System, Not Just Documents
&lt;/h2&gt;

&lt;p&gt;Individual documents are nice. A system is sustainable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My simple system:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I keep a "docs debt" list — a running note of everything I've noticed needs documentation but haven't had time to write. Every Friday, I spend two hours chipping away at it.&lt;/p&gt;

&lt;p&gt;I also maintain templates for common documentation types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API endpoint documentation&lt;/li&gt;
&lt;li&gt;Tutorial structure&lt;/li&gt;
&lt;li&gt;Troubleshooting guides&lt;/li&gt;
&lt;li&gt;Release notes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Templates mean I spend my mental energy on content, not structure. They also create consistency across documents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Slack channel hack:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I created a #docs-updates channel where I post whenever I publish new documentation. Two benefits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Engineers see their work being documented (makes them more likely to help in the future)&lt;/li&gt;
&lt;li&gt;People can flag issues immediately ("Actually, we changed that endpoint last week")&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I've Learned
&lt;/h2&gt;

&lt;p&gt;After months of doing this, a few things have become clear:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Documentation is 20% writing, 80% gathering.&lt;/strong&gt; The actual writing is the easy part. Getting accurate information from busy people is the real skill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timing matters more than asking nicely.&lt;/strong&gt; Catch engineers right after they ship a feature — they're still excited and remember the details. Wait two weeks and you'll get "I don't remember, check the PR."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Perfect is the enemy of published.&lt;/strong&gt; I used to wait until documentation was comprehensive before publishing. Now I ship "good enough" and iterate. Something helpful today beats something perfect next month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your documentation is only as good as your relationships.&lt;/strong&gt; I know which engineers explain things clearly, which ones need more specific questions, and which ones prefer async communication. This knowledge is as valuable as any writing skill.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Unsexy Truth
&lt;/h2&gt;

&lt;p&gt;There's no magic tool that extracts knowledge from your organization. Mintlify makes the publishing beautiful. GitHub makes version control seamless. But the actual information gathering?&lt;/p&gt;

&lt;p&gt;That's Slack messages at 9 PM. That's coffee chats that turn into impromptu explanations. That's asking "dumb questions" in engineering channels and not caring if you look uninformed.&lt;/p&gt;

&lt;p&gt;It's not glamorous. But it's how documentation actually gets made at startups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's your biggest challenge in gathering information for documentation? I'd love to hear what works (and what doesn't) for others.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devrel</category>
      <category>documentation</category>
    </item>
    <item>
      <title># Why I Chose Mintlify (And What I Wish I Knew Earlier)</title>
      <dc:creator>Daisy Auma</dc:creator>
      <pubDate>Mon, 05 Jan 2026 20:03:20 +0000</pubDate>
      <link>https://forem.com/daisyauma/-why-i-chose-mintlify-and-what-i-wish-i-knew-earlier-1b2l</link>
      <guid>https://forem.com/daisyauma/-why-i-chose-mintlify-and-what-i-wish-i-knew-earlier-1b2l</guid>
      <description>&lt;p&gt;&lt;em&gt;An honest review from a solo technical writer at an AI startup&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let me be upfront: I didn't choose &lt;a href="https://mintlify.com/" rel="noopener noreferrer"&gt;Mintlify&lt;/a&gt;. When I joined my current company as the first and only technical writer, the platform had already been selected. The documentation needed a complete overhaul, and Mintlify was what I had to work with.&lt;/p&gt;

&lt;p&gt;At first, I wasn't sure what to expect. I'd heard of Mintlify but never used it. Now, after months of building documentation from scratch on this platform, I have opinions, and honestly? I'd choose it myself if I had to start over.&lt;/p&gt;

&lt;p&gt;Here's my honest take on Mintlify what works, what's tricky, and why it might (or might not) be right for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Mintlify Compares to Alternatives
&lt;/h2&gt;

&lt;p&gt;Even though I didn't make the initial choice, I've since evaluated whether it was the right one. Here's how Mintlify stacks up against what else is out there:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.gitbook.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;GitBook&lt;/strong&gt;&lt;/a&gt; is the go-to for many teams. It's polished, collaborative, and has a gentle learning curve. But for a developer-focused product with heavy API documentation, I found it limiting. The customization options felt restrictive, and the pricing scales quickly as your team grows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://readme.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;ReadMe&lt;/strong&gt;&lt;/a&gt; is fantastic for API documentation specifically. The interactive API explorer is genuinely impressive. But if you need more than API docs; tutorials, conceptual guides, getting started content, it starts to feel like you're fighting the platform.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docusaurus.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;Docusaurus&lt;/strong&gt;&lt;/a&gt; gives you complete control. It's open-source, React-based, and incredibly flexible. The trade-off? You're essentially maintaining a website. For a solo technical writer at a startup, that overhead wasn't something I could justify.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mintlify&lt;/strong&gt; sits in an interesting middle ground: docs-as-code workflow, beautiful defaults, and enough customization to not feel boxed in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Love About Mintlify
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Docs-as-Code Workflow Changed Everything
&lt;/h3&gt;

&lt;p&gt;This was the game-changer for me. My documentation lives in a GitHub repository, written in &lt;a href="https://mdxjs.com/" rel="noopener noreferrer"&gt;MDX&lt;/a&gt; (Markdown with components). Every change goes through a pull request. Engineers can review docs the same way they review code.&lt;/p&gt;

&lt;p&gt;Why this matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Version control&lt;/strong&gt;: I can see exactly what changed, when, and why&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaboration&lt;/strong&gt;: Engineers can suggest edits directly in the files they know&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment&lt;/strong&gt;: Push to main, docs update automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backup&lt;/strong&gt;: My documentation is just files in a repo, not locked in someone's database&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Coming from tools where documentation lived in a web interface, this felt like a revelation. No more copy-pasting between platforms. No more wondering if I saved my changes. Just Git.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Defaults Are Actually Beautiful
&lt;/h3&gt;

&lt;p&gt;I've used documentation tools where "out of the box" meant spending a week on styling before you could show anyone. Mintlify's defaults look professional from day one.&lt;/p&gt;

&lt;p&gt;The typography is clean. The navigation makes sense. Dark mode works properly. Code blocks have syntax highlighting. These sound like basics, but you'd be surprised how many platforms get them wrong.&lt;/p&gt;

&lt;p&gt;For a startup where I needed to ship documentation fast, not spending time on design decisions was a gift.&lt;/p&gt;

&lt;h3&gt;
  
  
  MDX Components Save Hours
&lt;/h3&gt;

&lt;p&gt;Mintlify comes with &lt;a href="https://www.mintlify.com/docs/components" rel="noopener noreferrer"&gt;built-in components&lt;/a&gt; that would take forever to build yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.mintlify.com/docs/components/tabs" rel="noopener noreferrer"&gt;&lt;strong&gt;Tabs&lt;/strong&gt;&lt;/a&gt; for showing multiple code examples (Python, JavaScript, cURL)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.mintlify.com/docs/components/callouts" rel="noopener noreferrer"&gt;&lt;strong&gt;Callouts&lt;/strong&gt;&lt;/a&gt; for notes, warnings, and tips&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.mintlify.com/docs/components/cards" rel="noopener noreferrer"&gt;&lt;strong&gt;Cards&lt;/strong&gt;&lt;/a&gt; for navigation and feature highlights&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.mintlify.com/docs/components/steps" rel="noopener noreferrer"&gt;&lt;strong&gt;Steps&lt;/strong&gt;&lt;/a&gt; for sequential instructions&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.mintlify.com/docs/components/code-groups#code-groups" rel="noopener noreferrer"&gt;&lt;strong&gt;Code groups&lt;/strong&gt;&lt;/a&gt; for switching between related examples&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.mintlify.com/docs/api-playground/overview" rel="noopener noreferrer"&gt;&lt;strong&gt;API playground&lt;/strong&gt;&lt;/a&gt; that lets developers test endpoints directly in the docs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't just nice-to-have. They're the difference between documentation that people skim and documentation that people actually use.&lt;/p&gt;

&lt;h3&gt;
  
  
  The API Reference Generation
&lt;/h3&gt;

&lt;p&gt;If you're documenting APIs, Mintlify can &lt;a href="https://www.mintlify.com/docs/api-playground/openapi-setup#openapi-setup" rel="noopener noreferrer"&gt;generate reference documentation from your OpenAPI spec&lt;/a&gt;. You maintain your spec file, and the docs stay in sync.&lt;/p&gt;

&lt;p&gt;Is it perfect? No. I still end up writing custom content around the generated reference. But having the baseline automatically generated with request/response examples, parameter descriptions, and authentication details saves significant time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Wish I Knew Earlier
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Learning Curve Is Real
&lt;/h3&gt;

&lt;p&gt;Mintlify markets itself as simple, and it is once you understand the patterns. But there's a learning curve that caught me off guard.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.mintlify.com/docs/organize/settings#global-settings" rel="noopener noreferrer"&gt;&lt;code&gt;docs.json&lt;/code&gt;&lt;/a&gt; &lt;a href="https://mintlify.com/docs/settings/global" rel="noopener noreferrer"&gt;configuration file&lt;/a&gt; controls your entire site structure. Navigation, styling, integrations, redirects — it's all in one file. When things break, you're often debugging JSON structure.&lt;/p&gt;

&lt;p&gt;My first few weeks involved a lot of trial and error. "Why isn't this page showing up in the sidebar?" Usually: I forgot to add it to the navigation config. "Why is this redirect not working?" Usually: path formatting issues.&lt;/p&gt;

&lt;p&gt;If you're not comfortable with JSON configuration and Git workflows, budget time for learning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Image Handling Has Quirks
&lt;/h3&gt;

&lt;p&gt;This one bit me multiple times. &lt;a href="https://www.mintlify.com/docs/create/image-embeds#images-and-embeds" rel="noopener noreferrer"&gt;Images in Mintlify&lt;/a&gt; need to be in specific locations, and the path syntax depends on where your MDX file lives.&lt;/p&gt;

&lt;p&gt;I've debugged countless "image not displaying" issues. The culprit is usually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wrong path (relative vs. absolute)&lt;/li&gt;
&lt;li&gt;Case sensitivity in filenames&lt;/li&gt;
&lt;li&gt;Forgetting to commit the image file to Git&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My advice: establish a clear image organization system from day one and stick to it religiously.&lt;/p&gt;

&lt;h3&gt;
  
  
  Some Features Need Workarounds
&lt;/h3&gt;

&lt;p&gt;Not everything works exactly as documented. I've had to get creative with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dark/light mode images&lt;/strong&gt;: Needed custom CSS classes to swap images based on theme&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nested navigation&lt;/strong&gt;: The sidebar structure has limits that required restructuring my content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom components&lt;/strong&gt;: Possible, but requires understanding the underlying system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are dealbreakers, but they're the kind of friction that adds up when you're trying to move fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enterprise Features Cost Extra
&lt;/h3&gt;

&lt;p&gt;If you need &lt;a href="https://www.mintlify.com/docs/deploy/authentication-setup#interaction-with-authentication-modes" rel="noopener noreferrer"&gt;authentication&lt;/a&gt;, &lt;a href="https://www.mintlify.com/docs/deploy/personalization-setup#personalization-setup" rel="noopener noreferrer"&gt;personalization&lt;/a&gt;, or analytics beyond the basics, you're looking at higher-tier plans. I implemented customer-specific documentation using Mintlify's authentication features, it works well, but it wasn't part of the base package.&lt;/p&gt;

&lt;p&gt;For a startup, be realistic about what features you need now vs. later. The &lt;a href="https://mintlify.com/pricing" rel="noopener noreferrer"&gt;free and lower tiers&lt;/a&gt; are generous for getting started, but factor in growth.&lt;/p&gt;




&lt;h2&gt;
  
  
  Who Should (and Shouldn't) Use Mintlify
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Mintlify is great if you:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Want docs-as-code without building your own system&lt;/li&gt;
&lt;li&gt;Have developer-focused documentation with API content&lt;/li&gt;
&lt;li&gt;Value beautiful defaults over unlimited customization&lt;/li&gt;
&lt;li&gt;Are comfortable with Git and Markdown&lt;/li&gt;
&lt;li&gt;Need to ship documentation quickly at a startup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Consider alternatives if you:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Need heavy collaboration features for non-technical writers&lt;/li&gt;
&lt;li&gt;Want a completely custom design (&lt;a href="https://docusaurus.io/" rel="noopener noreferrer"&gt;Docusaurus&lt;/a&gt; gives more control)&lt;/li&gt;
&lt;li&gt;Primarily need internal documentation (&lt;a href="https://notion.so/" rel="noopener noreferrer"&gt;Notion&lt;/a&gt; or &lt;a href="https://www.atlassian.com/software/confluence" rel="noopener noreferrer"&gt;Confluence&lt;/a&gt; might be simpler)&lt;/li&gt;
&lt;li&gt;Have a large team that needs granular permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;I didn't choose Mintlify but after months of daily use, I'm glad someone else did. If I were starting fresh at a new company and had to pick a documentation platform myself, Mintlify would be at the top of my list.&lt;/p&gt;

&lt;p&gt;The docs-as-code workflow fits how I think about documentation. The defaults are beautiful. The components save time. The platform gets out of my way and lets me focus on writing.&lt;/p&gt;

&lt;p&gt;Is it perfect? No. The learning curve is steeper than advertised. Some features need workarounds. Enterprise features add up.&lt;/p&gt;

&lt;p&gt;But for a solo technical writer at a startup building developer documentation? It's been the right tool for the job.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What documentation platform do you use? I'm curious what's worked (and what hasn't) for others. Drop a comment — I read every one.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>documentation</category>
      <category>tooling</category>
    </item>
    <item>
      <title>The Python Web Stack You've Never Heard Of: Building Apps Without Frontend Code</title>
      <dc:creator>Daisy Auma</dc:creator>
      <pubDate>Mon, 05 Jan 2026 20:02:43 +0000</pubDate>
      <link>https://forem.com/daisyauma/the-python-web-stack-youve-never-heard-of-building-apps-without-frontend-code-en6</link>
      <guid>https://forem.com/daisyauma/the-python-web-stack-youve-never-heard-of-building-apps-without-frontend-code-en6</guid>
      <description>&lt;p&gt;I recently interviewed with Anvil, and despite not getting the role, I found what they do to be really cool. As a Python developer, I’ve hit the same wall countless times: I can wrangle data, build ML models, automate workflows, but the moment someone says “put a web UI on that,” I’m stuck learning React, wrestling with JavaScript tooling, and coordinating between frontend and backend.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/" rel="noopener noreferrer"&gt;Anvil&lt;/a&gt; takes a contrarian approach: what if you could write your entire web application including the client-side code in Python? No JavaScript required. But here’s the technical puzzle: browsers only understand JavaScript. So how does this actually work?&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;

&lt;p&gt;Anvil’s stack consists of four main layers that work together to enable full-stack Python development:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Client Layer&lt;/strong&gt;: Python code that runs in the browser, compiled to JavaScript via Skulpt &lt;br&gt;
&lt;strong&gt;Server Layer&lt;/strong&gt;: Standard Python with full ecosystem access, running on Anvil’s servers &lt;br&gt;
&lt;strong&gt;Communication&lt;/strong&gt;: RPC over WebSockets connecting client and server seamlessly &lt;br&gt;
&lt;strong&gt;Database&lt;/strong&gt;: Built-in PostgreSQL (Data Tables) or connect to external databases&lt;/p&gt;

&lt;p&gt;There’s also an optional fifth component called the &lt;strong&gt;Uplink&lt;/strong&gt;, which lets you connect local Python scripts or Jupyter notebooks to your web app (more on this later).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsced6imz16xvg888ve2o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsced6imz16xvg888ve2o.png" alt="Anvil architecture" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key innovation here is a unified language across the entire stack. No context switching between Python and JavaScript, no REST API boilerplate, no separate frontend and backend codebases.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Technical Magic: Client-Side Python
&lt;/h2&gt;

&lt;p&gt;Here’s the fundamental problem: web browsers only execute JavaScript. For decades, the solution has been clear: learn JavaScript frameworks, coordinate between frontend and backend teams, manage two entirely different codebases.&lt;/p&gt;

&lt;p&gt;Anvil’s solution relies on &lt;a href="https://skulpt.org/" rel="noopener noreferrer"&gt;Skulpt&lt;/a&gt;, a JavaScript implementation of Python that runs in the browser. When you write Python code for the client-side, it gets compiled to JavaScript at runtime. This means your Python code is actually executing in the browser, handling events, manipulating the DOM, and updating the UI.&lt;/p&gt;

&lt;p&gt;Here’s what this looks like in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This Python code runs IN THE BROWSER
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyFormTemplate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;button_click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;event_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Client-side event handling in Python
&lt;/span&gt;    &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello from the browser!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Call server seamlessly (looks like a regular function call)
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;process_data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text_box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is genuinely running as Python in your browser. The button_click method handles a UI event, displays an alert, calls a server function, and updates a label, all without writing a single line of JavaScript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Familiar Python syntax and patterns with no mental context switching&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Seamless client-server communication where RPC calls look like regular Python functions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unified codebase with one language and one set of conventions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rapid development without frontend/backend coordination overhead&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The crucial insight is understanding where code actually runs. UI event handlers execute in the browser (Python via Skulpt), but heavy data processing, machine learning, and pandas operations run on the server in native Python. Database queries happen server-side. The architecture naturally guides you toward the right separation of concerns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Code Example
&lt;/h2&gt;

&lt;p&gt;Let’s build a mini data dashboard that demonstrates how everything fits together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# === SERVER MODULE (server.py) ===
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;plotly.express&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anvil.server&lt;/span&gt;

&lt;span class="nd"&gt;@anvil.server.callable&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;analyze_sales&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploaded_file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="c1"&gt;# Full Python ecosystem available here
&lt;/span&gt;  &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploaded_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;revenue&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;reset_index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="n"&gt;fig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;revenue&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
               &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Sales by Category&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fig&lt;/span&gt;

&lt;span class="nd"&gt;@anvil.server.callable&lt;/span&gt;  
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save_to_database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="c1"&gt;# Built-in database access
&lt;/span&gt;  &lt;span class="n"&gt;app_tables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Saved successfully&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="c1"&gt;# === CLIENT MODULE (Form1.py) ===
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anvil.server&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Form1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Form1Template&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upload_change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;event_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# This runs in the browser
&lt;/span&gt;    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="c1"&gt;# Call server function - seamless RPC
&lt;/span&gt;    &lt;span class="n"&gt;chart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;analyze_sales&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;figure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chart&lt;/span&gt;

    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save_button_click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;event_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dropdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selected_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text_box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;save_to_database&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What’s happening here:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;File upload is handled client-side (Python in browser via Skulpt)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Heavy processing with pandas happens server-side with full Python capabilities&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;RPC calls look like regular Python function calls (no HTTP requests to construct)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No JSON serialization code (Python objects are serialized automatically)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Security is built-in: server functions decorated with @anvil.server.callable form your API boundary&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notice there’s no REST API definition, no endpoint configuration, no fetch calls with headers and error handling. The anvil.server.call() function handles all of that transparently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integration &amp;amp; Ecosystem
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What Works Where&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Server-side, you have access to the full Python ecosystem. Install any pip package: pandas, scikit-learn, requests, TensorFlow. This is native Python execution.&lt;/p&gt;

&lt;p&gt;Client-side is more limited. Plotly works for visualizations, and you have access to a subset of the standard library, but compute-heavy libraries like NumPy won’t run in the browser. This limitation actually makes sense architecturally, heavy computation belongs on the server anyway.&lt;/p&gt;

&lt;p&gt;JavaScript libraries can be wrapped and called from Python using Anvil’s &lt;a href="https://anvil.works/learn/tutorials/javascript-libraries" rel="noopener noreferrer"&gt;JavaScript integration features&lt;/a&gt;. You’re not completely cut off from the JavaScript ecosystem when you need it.&lt;/p&gt;

&lt;p&gt;Anvil includes &lt;a href="https://anvil.works/docs/integrations" rel="noopener noreferrer"&gt;built-in integrations&lt;/a&gt; for common services: Stripe for payments, Google and Microsoft for authentication and APIs, and email sending. These work out of the box with minimal configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Uplink: The Secret Weapon&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://anvil.works/docs/uplink" rel="noopener noreferrer"&gt;Uplink&lt;/a&gt; is perhaps Anvil’s most unique feature. It lets you connect existing Python code running anywhere; your laptop, a Jupyter notebook, a GPU server to your web application.&lt;/p&gt;

&lt;p&gt;Plain textANTLR4BashCC#CSSCoffeeScriptCMakeDartDjangoDockerEJSErlangGitGoGraphQLGroovyHTMLJavaJavaScriptJSONJSXKotlinLaTeXLessLuaMakefileMarkdownMATLABMarkupObjective-CPerlPHPPowerShell.propertiesProtocol BuffersPythonRRubySass (Sass)Sass (Scss)SchemeSQLShellSwiftSVGTSXTypeScriptWebAssemblyYAMLXML&lt;code&gt;# Your local Jupyter notebook or scriptimport anvil.serveranvil.server.connect("YOUR_UPLINK_KEY")@anvil.server.callabledef run_ml_model(data):  # Use your local GPU, existing code, trained models  return model.predict(data)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Your web app calls this like any server function, but it executes on your machine. This is brilliant for connecting data science workflows to web interfaces without rewriting everything. Your Jupyter notebook becomes a backend service.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use This
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Choose Anvil when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Your team is Python-heavy but frontend-light&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Building internal tools, dashboards, or admin panels&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rapidly prototyping data applications&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connecting Jupyter notebooks or data science code to web UIs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You value development speed over pixel-perfect custom UI&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creating CRUD applications where Python’s full ecosystem matters more than complex client-side interactions&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Anvil makes a specific technical bet: for certain types of applications, the productivity gains from using a unified language outweigh the performance overhead and ecosystem constraints of client-side Python.&lt;/p&gt;

&lt;p&gt;This isn’t about replacing React or Vue. It’s about offering an alternative path for Python developers who view web UIs as a necessary interface layer rather than the core of what they’re building. When your value is in data processing, business logic, or machine learning models, Anvil lets you wrap those capabilities in a web interface without learning an entirely new stack.&lt;/p&gt;

&lt;p&gt;The platform is part of a broader trend of Python expanding beyond its traditional backend role.&lt;/p&gt;

&lt;p&gt;Is this the future of web development? Probably not. But for specific use cases (particularly in data science, internal tooling, and rapid prototyping) it’s a genuinely productive alternative that more Python developers should know about.&lt;/p&gt;

&lt;p&gt;This article may not be perfect and I would appreciate any feedback!&lt;/p&gt;

&lt;p&gt;Buy me a coffee: &lt;a href="https://buymeacoffee.com/daisyauma" rel="noopener noreferrer"&gt;https://buymeacoffee.com/daisyauma&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The Python Web Stack You've Never Heard Of: Building Apps Without Frontend Code</title>
      <dc:creator>Daisy Auma</dc:creator>
      <pubDate>Thu, 09 Oct 2025 16:56:34 +0000</pubDate>
      <link>https://forem.com/daisyauma/the-python-web-stack-youve-never-heard-of-building-apps-without-frontend-code-4oj5</link>
      <guid>https://forem.com/daisyauma/the-python-web-stack-youve-never-heard-of-building-apps-without-frontend-code-4oj5</guid>
      <description>&lt;p&gt;I recently interviewed with Anvil, and despite not getting the role, I found what they do to be really cool. As a Python developer, I’ve hit the same wall countless times: I can wrangle data, build ML models, automate workflows, but the moment someone says “put a web UI on that,” I’m stuck learning React, wrestling with JavaScript tooling, and coordinating between frontend and backend.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/" rel="noopener noreferrer"&gt;Anvil&lt;/a&gt; takes a contrarian approach: what if you could write your entire web application including the client-side code in Python? No JavaScript required. But here’s the technical puzzle: browsers only understand JavaScript. So how does this actually work?&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;

&lt;p&gt;Anvil’s stack consists of four main layers that work together to enable full-stack Python development:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Client Layer&lt;/strong&gt;: Python code that runs in the browser, compiled to JavaScript via Skulpt &lt;br&gt;
&lt;strong&gt;Server Layer&lt;/strong&gt;: Standard Python with full ecosystem access, running on Anvil’s servers &lt;br&gt;
&lt;strong&gt;Communication&lt;/strong&gt;: RPC over WebSockets connecting client and server seamlessly &lt;br&gt;
&lt;strong&gt;Database&lt;/strong&gt;: Built-in PostgreSQL (Data Tables) or connect to external databases&lt;/p&gt;

&lt;p&gt;There’s also an optional fifth component called the &lt;strong&gt;Uplink&lt;/strong&gt;, which lets you connect local Python scripts or Jupyter notebooks to your web app (more on this later).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsced6imz16xvg888ve2o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsced6imz16xvg888ve2o.png" alt="Anvil architecture" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key innovation here is a unified language across the entire stack. No context switching between Python and JavaScript, no REST API boilerplate, no separate frontend and backend codebases.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Technical Magic: Client-Side Python
&lt;/h2&gt;

&lt;p&gt;Here’s the fundamental problem: web browsers only execute JavaScript. For decades, the solution has been clear: learn JavaScript frameworks, coordinate between frontend and backend teams, manage two entirely different codebases.&lt;/p&gt;

&lt;p&gt;Anvil’s solution relies on &lt;a href="https://skulpt.org/" rel="noopener noreferrer"&gt;Skulpt&lt;/a&gt;, a JavaScript implementation of Python that runs in the browser. When you write Python code for the client-side, it gets compiled to JavaScript at runtime. This means your Python code is actually executing in the browser, handling events, manipulating the DOM, and updating the UI.&lt;/p&gt;

&lt;p&gt;Here’s what this looks like in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This Python code runs IN THE BROWSER
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyFormTemplate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;button_click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;event_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Client-side event handling in Python
&lt;/span&gt;    &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello from the browser!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Call server seamlessly (looks like a regular function call)
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;process_data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text_box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is genuinely running as Python in your browser. The &lt;code&gt;button\_click&lt;/code&gt; method handles a UI event, displays an alert, calls a server function, and updates a label, all without writing a single line of JavaScript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Familiar Python syntax and patterns with no mental context switching&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Seamless client-server communication where RPC calls look like regular Python functions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unified codebase with one language and one set of conventions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rapid development without frontend/backend coordination overhead&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The crucial insight is understanding where code actually runs. UI event handlers execute in the browser (Python via Skulpt), but heavy data processing, machine learning, and pandas operations run on the server in native Python. Database queries happen server-side. The architecture naturally guides you toward the right separation of concerns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Code Example
&lt;/h2&gt;

&lt;p&gt;Let’s build a mini data dashboard that demonstrates how everything fits together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# === SERVER MODULE (server.py) ===
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;plotly.express&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anvil.server&lt;/span&gt;

&lt;span class="nd"&gt;@anvil.server.callable&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;analyze_sales&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploaded_file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="c1"&gt;# Full Python ecosystem available here
&lt;/span&gt;  &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploaded_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;revenue&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;reset_index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="n"&gt;fig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;revenue&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
               &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Sales by Category&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fig&lt;/span&gt;

&lt;span class="nd"&gt;@anvil.server.callable&lt;/span&gt;  
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save_to_database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="c1"&gt;# Built-in database access
&lt;/span&gt;  &lt;span class="n"&gt;app_tables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Saved successfully&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="c1"&gt;# === CLIENT MODULE (Form1.py) ===
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anvil.server&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Form1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Form1Template&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upload_change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;event_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# This runs in the browser
&lt;/span&gt;    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="c1"&gt;# Call server function - seamless RPC
&lt;/span&gt;    &lt;span class="n"&gt;chart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;analyze_sales&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;figure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chart&lt;/span&gt;

    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save_button_click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;event_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dropdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selected_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text_box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;save_to_database&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What’s happening here:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;File upload is handled client-side (Python in browser via Skulpt)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Heavy processing with pandas happens server-side with full Python capabilities&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;RPC calls look like regular Python function calls (no HTTP requests to construct)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No JSON serialization code (Python objects are serialized automatically)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Security is built-in: server functions decorated with &lt;code&gt;@anvil.server.callable&lt;/code&gt; form your API boundary&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notice there’s no REST API definition, no endpoint configuration, no fetch calls with headers and error handling. The &lt;code&gt;anvil.server.call()&lt;/code&gt; function handles all of that transparently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integration &amp;amp; Ecosystem
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What Works Where&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Server-side, you have access to the full Python ecosystem. Install any pip package: pandas, scikit-learn, requests, TensorFlow. This is native Python execution.&lt;/p&gt;

&lt;p&gt;Client-side is more limited. Plotly works for visualizations, and you have access to a subset of the standard library, but compute-heavy libraries like NumPy won’t run in the browser. This limitation actually makes sense architecturally, heavy computation belongs on the server anyway.&lt;/p&gt;

&lt;p&gt;JavaScript libraries can be wrapped and called from Python using Anvil’s &lt;a href="https://anvil.works/learn/tutorials/javascript-libraries" rel="noopener noreferrer"&gt;JavaScript integration features&lt;/a&gt;. You’re not completely cut off from the JavaScript ecosystem when you need it.&lt;/p&gt;

&lt;p&gt;Anvil includes &lt;a href="https://anvil.works/docs/integrations" rel="noopener noreferrer"&gt;built-in integrations&lt;/a&gt; for common services: Stripe for payments, Google and Microsoft for authentication and APIs, and email sending. These work out of the box with minimal configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Uplink: The Secret Weapon&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://anvil.works/docs/uplink" rel="noopener noreferrer"&gt;Uplink&lt;/a&gt; is perhaps Anvil’s most unique feature. It lets you connect existing Python code running anywhere; your laptop, a Jupyter notebook, a GPU server to your web application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Your local Jupyter notebook or script
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anvil.server&lt;/span&gt;
&lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_UPLINK_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@anvil.server.callable&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_ml_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="c1"&gt;# Use your local GPU, existing code, trained models
&lt;/span&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your web app calls this like any server function, but it executes on your machine. This is brilliant for connecting data science workflows to web interfaces without rewriting everything. Your Jupyter notebook becomes a backend service.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use This
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Choose Anvil when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Your team is Python-heavy but frontend-light&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Building internal tools, dashboards, or admin panels&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rapidly prototyping data applications&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connecting Jupyter notebooks or data science code to web UIs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You value development speed over pixel-perfect custom UI&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creating CRUD applications where Python’s full ecosystem matters more than complex client-side interactions&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Anvil makes a specific technical bet: for certain types of applications, the productivity gains from using a unified language outweigh the performance overhead and ecosystem constraints of client-side Python.&lt;/p&gt;

&lt;p&gt;This isn’t about replacing React or Vue. It’s about offering an alternative path for Python developers who view web UIs as a necessary interface layer rather than the core of what they’re building. When your value is in data processing, business logic, or machine learning models, Anvil lets you wrap those capabilities in a web interface without learning an entirely new stack.&lt;/p&gt;

&lt;p&gt;The platform is part of a broader trend of Python expanding beyond its traditional backend role.&lt;/p&gt;

&lt;p&gt;Is this the future of web development? Probably not. But for specific use cases (particularly in data science, internal tooling, and rapid prototyping) it’s a genuinely productive alternative that more Python developers should know about.&lt;/p&gt;

&lt;p&gt;This article may not be perfect and I would appreciate any feedback!&lt;/p&gt;

&lt;p&gt;Buy me a coffee: &lt;a href="https://buymeacoffee.com/daisyauma" rel="noopener noreferrer"&gt;https://buymeacoffee.com/daisyauma&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>architecture</category>
      <category>webdev</category>
      <category>tooling</category>
    </item>
    <item>
      <title># Build an Interactive Sales Chart with Plotly</title>
      <dc:creator>Daisy Auma</dc:creator>
      <pubDate>Thu, 25 Sep 2025 02:07:11 +0000</pubDate>
      <link>https://forem.com/daisyauma/-build-an-interactive-sales-chart-with-plotly-2gg9</link>
      <guid>https://forem.com/daisyauma/-build-an-interactive-sales-chart-with-plotly-2gg9</guid>
      <description>&lt;p&gt;Static sales charts in Excel or spreadsheets do not let you explore your data dynamically. Want to zoom in on a specific month? Compare different products? You need to create new charts manually. In this tutorial, you will build an interactive sales chart in Python using Plotly that lets users hover for details, zoom into timeframes, and filter by product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we begin, make sure you have:&lt;/p&gt;

&lt;p&gt;Python 3.7 or higher installed on your system. You will need two libraries: Plotly for creating interactive visualizations and Pandas for handling data. Install them using pip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;plotly pandas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can run this code in any Python environment: VS Code, Jupyter Notebook, PyCharm, or even the command line. The interactive charts will open in your default browser.&lt;/p&gt;

&lt;p&gt;This tutorial assumes you are comfortable with basic Python syntax and have used Pandas DataFrames before. You should know how to load data and access columns. You do not need any prior experience with Plotly. We will be using Plotly Express, the high-level interface that makes creating charts simple and intuitive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data
&lt;/h2&gt;

&lt;p&gt;We will work with a year's worth of monthly sales data for three products: Laptops, Tablets, and Smartphones. The dataset contains revenue figures (in dollars) for each product across all 12 months of 2024.&lt;/p&gt;

&lt;p&gt;I have hosted the data on GitHub (&lt;a href="https://raw.githubusercontent.com/Daisy-Faith-Auma/plotly-sales-tutorial/refs/heads/main/sales_data.csv" rel="noopener noreferrer"&gt;https://raw.githubusercontent.com/Daisy-Faith-Auma/plotly-sales-tutorial/refs/heads/main/sales_data.csv&lt;/a&gt;) for easy access. Let us load it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;plotly.express&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt;

&lt;span class="c1"&gt;# Load the sales data
&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://raw.githubusercontent.com/Daisy-Faith-Auma/plotly-sales-tutorial/refs/heads/main/sales_data.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Convert Month column to datetime for proper time-series plotting
&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Month&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Month&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Take a look at the data
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;head&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a DataFrame with three columns: Month, Product, and Revenue. The data shows interesting patterns: Laptops have steady growth, Tablets show some seasonal variation, and Smartphones spike during the holiday season.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Basic Line Chart
&lt;/h2&gt;

&lt;p&gt;Let us create our first chart. Plotly Express makes this remarkably simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create a basic line chart
&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Month&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Revenue&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Monthly Sales Revenue&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F681ahytxstwg8qsqie5y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F681ahytxstwg8qsqie5y.png" alt="Create a basic line chart" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you run this, a browser window opens showing your visualization. Try hovering over the line. You will see data points appear. You can also zoom by clicking and dragging, and pan by holding shift while dragging.&lt;/p&gt;

&lt;p&gt;But there is a problem: this chart combines all three products into a single line, which is not useful for comparison. We are summing up all the revenue without distinguishing between Laptops, Tablets, and Smartphones.&lt;/p&gt;

&lt;p&gt;Notice what Plotly provides: automatic axis labels, a title, grid lines, and basic interactivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Add Hover Interactivity
&lt;/h2&gt;

&lt;p&gt;Now let us separate our products and make the hover information more useful. We will tell Plotly to create a different line for each product:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create separate lines for each product
&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
              &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Month&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
              &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Revenue&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
              &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Product&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Monthly Sales Revenue by Product&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkuyi8uqbvpuev77wdbkb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkuyi8uqbvpuev77wdbkb.png" alt="Create separate lines for each product" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you have three distinct lines, each in a different color. The legend appears automatically, and you can click on product names to show or hide specific lines.&lt;/p&gt;

&lt;p&gt;Let us improve the hover tooltips to show properly formatted currency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Customize hover information
&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
              &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Month&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
              &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Revenue&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
              &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Product&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Monthly Sales Revenue by Product&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Revenue&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Revenue ($)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Month&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Month&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# Format the hover template
&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_traces&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hovertemplate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;b&amp;gt;%{fullData.name}&amp;lt;/b&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Month: %{x|%B %Y}&amp;lt;br&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Revenue: $%{y:,.0f}&amp;lt;br&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;extra&amp;gt;&amp;lt;/extra&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faadcikpqypmyq252fu2o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faadcikpqypmyq252fu2o.png" alt="Customize hover information" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now when you hover, you see formatted information: the product name in bold, the full month name with year, and revenue formatted as currency with comma separators. The &lt;code&gt;&amp;lt;extra&amp;gt;&amp;lt;/extra&amp;gt;&lt;/code&gt; removes the redundant trace name that appears by default.&lt;/p&gt;

&lt;p&gt;Try hovering over different points. The formatting makes the data much more readable and professional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Multiple Products with Legend
&lt;/h2&gt;

&lt;p&gt;Our chart already shows multiple products, but let us polish it further. We will improve the layout and make the legend more functional:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Enhanced multi-product chart
&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
              &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Month&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
              &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Revenue&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
              &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Product&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2024 Sales Performance Dashboard&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Revenue&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Revenue ($)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Month&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Month&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# Customize hover information
&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_traces&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hovertemplate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;b&amp;gt;%{fullData.name}&amp;lt;/b&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Month: %{x|%B %Y}&amp;lt;br&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Revenue: $%{y:,.0f}&amp;lt;br&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;extra&amp;gt;&amp;lt;/extra&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Improve layout
&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;hovermode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;x unified&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Show all products when hovering over a month
&lt;/span&gt;    &lt;span class="n"&gt;legend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Product Line&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;orientation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;h&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;yanchor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bottom&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;xanchor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;right&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F58siureffeer82o5hlrc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F58siureffeer82o5hlrc.png" alt="Enhanced multi-product chart" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two key improvements here:&lt;/p&gt;

&lt;p&gt;First, &lt;code&gt;hovermode='x unified'&lt;/code&gt; shows data for all products simultaneously when you hover over a specific month. This makes comparisons easier. You can instantly see which product performed best in any given month.&lt;/p&gt;

&lt;p&gt;Second, we have repositioned the legend horizontally above the chart. This saves vertical space and keeps all product information visible while you explore the data. Try clicking on legend items. You can show or hide products to focus on specific comparisons.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Dropdown Product Selector
&lt;/h2&gt;

&lt;p&gt;For the final enhancement, let's add a dropdown menu that lets users filter by product. This is particularly useful when presenting to stakeholders who want to focus on specific product lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create the base figure
&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
              &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Month&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
              &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Revenue&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
              &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Product&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2024 Sales Performance Dashboard&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Revenue&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Revenue ($)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Month&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Month&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# Customize hover
&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_traces&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hovertemplate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;b&amp;gt;%{fullData.name}&amp;lt;/b&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Month: %{x|%B %Y}&amp;lt;br&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Revenue: $%{y:,.0f}&amp;lt;br&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;extra&amp;gt;&amp;lt;/extra&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Add dropdown menu
&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;updatemenus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;buttons&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;All Products&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;update&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;visible&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;]}]),&lt;/span&gt;
                &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Laptop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;update&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;visible&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;]}]),&lt;/span&gt;
                &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tablet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;update&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;visible&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;]}]),&lt;/span&gt;
                &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Smartphone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;update&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;visible&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;]}])&lt;/span&gt;
            &lt;span class="p"&gt;]),&lt;/span&gt;
            &lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;down&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;showactive&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;xanchor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;center&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.15&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3szrriuvyx5wyagr4aps.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3szrriuvyx5wyagr4aps.png" alt="Add dropdown menu" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzmxb7qm744l2kmmfi2vu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzmxb7qm744l2kmmfi2vu.png" alt="Add dropdown menu" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The dropdown menu appears at the top of your chart. Users can select "All Products" to see everything, or choose individual products for focused analysis. This transforms your chart into an interactive dashboard that serves multiple purposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;You have now built an interactive sales dashboard with hover tooltips showing formatted data, synchronized hovering across products, and a dropdown filter. These features make data exploration intuitive and engaging.&lt;/p&gt;

&lt;p&gt;You can save your chart as a standalone HTML file with &lt;code&gt;fig.write_html('sales_dashboard.html')&lt;/code&gt; for easy sharing. The HTML file can be opened in any browser without requiring Python or any additional software.&lt;/p&gt;

&lt;p&gt;Try experimenting with your own sales data by replacing the CSV URL, or explore other chart types like bar charts and scatter plots using the same Plotly Express patterns you have learned here.&lt;/p&gt;

</description>
      <category>datascience</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SearchGPT: OpenAI’s Latest Tool Set to Transform the Search Engine Landscape</title>
      <dc:creator>Daisy Auma</dc:creator>
      <pubDate>Sun, 28 Jul 2024 23:58:58 +0000</pubDate>
      <link>https://forem.com/daisyauma/searchgpt-openais-latest-tool-set-to-transform-the-search-engine-landscape-1m81</link>
      <guid>https://forem.com/daisyauma/searchgpt-openais-latest-tool-set-to-transform-the-search-engine-landscape-1m81</guid>
      <description>&lt;p&gt;SearchGPT is the latest AI search tool that has been announced by OpenAI. SearchGPT runs on GPT-4 AI models and is expected to be launched as a prototype for a given amount of time before its best features are incorporated into ChatGPT. Here are some of the key features to expect from SearchGPT.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;   SearchGPT will have real-time access to the internet, which will ensure that users have access to the most current information available. This puts the search engine above other AI models that do not have the feature.
&lt;/li&gt;
&lt;li&gt;    SearchGPT is set to have a conversational interface that will allow users to interact with the tool more naturally and intuitively. Users will be able to ask follow-up questions and have context built up in the conversation. The search engine is expected to summarize and organize information for users. &lt;/li&gt;
&lt;li&gt;   The results provided by SearchGPT will have in-line attributions and links that will guide you to information sources, while allowing users to check out these links for more information. To improve this experience, OpenAI is collaborating with various publishers to enable them to manage how they appear in SearchGPT results.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What impact is SearchGPT likely to have on the search market?
&lt;/h3&gt;

&lt;p&gt;It will be interesting to see how the current players react to OpenAI’s newest creation. While Google dominates the search market, SearchGPT is bound to make waves and attract some traffic when it is eventually rolled out. Similar products such as Perplexity are also likely to come up with a response to this competitive search engine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Different Approach
&lt;/h3&gt;

&lt;p&gt;SearchGPT is seeking to gain an edge by partnering with publishers such as News Corp and The Atlantic and actively involving them in the layout of search results. This is particularly significant as a direct competitor, Perplexity, is currently facing legal proceedings brought forth by publishers. SearchGPT’s collaboration with publishers therefore makes for an interesting game-changer.  &lt;/p&gt;

&lt;h3&gt;
  
  
  How to access SearchGPT.
&lt;/h3&gt;

&lt;p&gt;While it will be initially rolled out as a prototype, SearchGPT will only be made available to thousands of people for its trial period. There is currently no information on how long this test period is expected to last. To be able to sign up for the trial, you need to have a ChatGPT account. You can join the waitlist from &lt;a href="http://chatgpt.com/search" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Was this article helpful?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://buymeacoffee.com/daisyauma" rel="noopener noreferrer"&gt;https://buymeacoffee.com/daisyauma&lt;/a&gt;&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>openai</category>
      <category>google</category>
      <category>seo</category>
    </item>
    <item>
      <title>Learning to Learn: A Tech Girl’s Guide to Staying Ahead</title>
      <dc:creator>Daisy Auma</dc:creator>
      <pubDate>Sun, 26 May 2024 16:37:16 +0000</pubDate>
      <link>https://forem.com/daisyauma/learning-to-learn-a-tech-girls-guide-to-staying-ahead-30ec</link>
      <guid>https://forem.com/daisyauma/learning-to-learn-a-tech-girls-guide-to-staying-ahead-30ec</guid>
      <description>&lt;p&gt;Let’s be honest, the tech world is a treadmill on fast-forward. By the time you master one programming language like Python, there’s a hot new framework like Django out there threatening to make it seem outdated. Cloud platforms shift from on-premise servers to behemoths like AWS, methodologies evolve from waterfall to agile, and the pressure to stay relevant feels constant. It’s enough to make any ambitious twenty-something like me want to throw in the towel.&lt;/p&gt;

&lt;p&gt;But here’s the secret weapon I’ve discovered: it’s not about desperately cramming every new tech trend into your brain. It’s about mastering the art of learning to learn.&lt;/p&gt;

&lt;p&gt;Yes, you read that right. In this ever-changing landscape, the most valuable skill you can develop is the ability to adapt and acquire new information efficiently. Here’s how I’m tackling this challenge:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Focus on Fundamentals:&lt;/strong&gt; Before diving into the newest thing, solidify your grasp of core concepts. Think of these fundamentals as the building blocks of your tech knowledge. Understanding things like data structures (like arrays and linked lists) and algorithms (like sorting and searching) creates a strong foundation for future learning. You can check sites like Leetcode for the most sought after questions. These concepts are evergreen and apply across different programming languages and technologies. Sites like The Odin Project offer fantastic, interactive introductions to these core concepts, with comprehensive curriculums and hands-on projects that make learning engaging and practical.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Befriend Online Resources:&lt;/strong&gt; The tech world is a goldmine of free and paid resources. From online courses like Coursera to informative blogs and YouTube channels, there’s something for everyone. Find learning styles that resonate with you — some people learn best by reading in-depth articles, while others prefer video tutorials or attending live webinars. There are fantastic resources for every budget. Sites like Coursera and edX offer affordable courses from top universities, while platforms like YouTube have a wealth of free, high-quality content from tech experts. For instance, Google offers a free course on its own developer platform, Google Developers, covering a wide range of topics from building web applications to machine learning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Embrace the Power of Communities:&lt;/strong&gt; Don’t underestimate the power of connecting with other tech enthusiasts. Online forums like Stack Overflow are great places to get help with specific coding problems, while meetups and conferences allow you to network with like-minded individuals. There are even dedicated online communities for women in tech, like Women Who Code, which offer mentorship, workshops, and a supportive network. You can also find tech communities on platforms like Meetup, where you can connect with local developers and attend events focused on specific technologies. Organizations like Women Techmakers and GDG provide resources and programs specifically geared towards women in technology.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learn by Doing:&lt;/strong&gt; The best way to solidify your knowledge is to apply it. Don’t be afraid to experiment with personal projects. Maybe you could build a simple website using HTML, CSS, and JavaScript, or develop a mobile app to automate a mundane task. There are also a plethora of open-source initiatives on platforms like GitHub where you can contribute to existing projects and learn from experienced developers. Contributing to open-source not only enhances your skills but also gives you valuable experience to showcase on your resume. You can find open-source projects on GitHub that cater to all skill levels, so don’t be afraid to jump in and start exploring!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Develop a Growth Mindset:&lt;/strong&gt; Shift your perspective from fearing change to embracing it as an opportunity to grow. Celebrate challenges as learning experiences, and view mistakes as stepping stones to mastery. Instead of getting discouraged when you get stuck on a coding problem, reframe it as a puzzle to be solved. Approach new technologies with curiosity and a willingness to learn. There are countless books and online resources on developing a growth mindset, such as Carol Dweck’s influential book “Mindset: The New Psychology of Success.” You can also find articles and videos on the topic through a quick Google search.&lt;/p&gt;

&lt;p&gt;Remember, learning to learn is a continuous journey. There will be days when you feel overwhelmed by the sheer volume of new information, but don’t let that discourage you. Embrace the constant evolution of tech, focus on the joy of discovery, and you’ll find yourself not just keeping up, but thriving in this dynamic field. Here are some additional tips to keep in mind:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Curate Your Learning:&lt;/strong&gt; Don’t try to learn everything at once. Instead, identify your specific goals and tailor your learning resources accordingly. For example: Are you interested in becoming a web developer? Focus on learning front-end technologies like HTML, CSS, and JavaScript, along with a popular back-end framework like Node.js or Python.&lt;/p&gt;

&lt;p&gt;Goodluck!&lt;/p&gt;

&lt;p&gt;Was this article helpful?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://buymeacoffee.com/daisyauma"&gt;https://buymeacoffee.com/daisyauma&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Google Announces Major Updates to Gemini AI: Enhancing Capabilities and Expanding Access</title>
      <dc:creator>Daisy Auma</dc:creator>
      <pubDate>Thu, 23 May 2024 21:23:19 +0000</pubDate>
      <link>https://forem.com/daisyauma/google-announces-major-updates-to-gemini-ai-enhancing-capabilities-and-expanding-access-41k7</link>
      <guid>https://forem.com/daisyauma/google-announces-major-updates-to-gemini-ai-enhancing-capabilities-and-expanding-access-41k7</guid>
      <description>&lt;p&gt;In a series of announcements made in May 2024, Google unveiled substantial updates to its Gemini AI models, marking significant advancements in artificial intelligence technology. These updates include the enhancement of the Gemini 1.5 Pro model, the introduction of the Gemini 1.5 Flash model, and new features available through the Gemini API. These developments are designed to make the AI more capable, accessible, and useful across a variety of applications.&lt;/p&gt;

&lt;h4&gt;
  
  
  Enhancements to Gemini 1.5 Pro
&lt;/h4&gt;

&lt;p&gt;One of the standout updates is the enhancement of the Gemini 1.5 Pro model, which now supports a context window of up to 1 million tokens. This is the longest context window available for any consumer chatbot, allowing the model to process and understand extensive amounts of text. This capability is particularly useful for tasks that involve long documents, detailed email threads, and comprehensive datasets. With this update, users can expect improved performance in generating coherent and contextually accurate responses even when dealing with large volumes of information.&lt;/p&gt;

&lt;p&gt;The Gemini 1.5 Pro model has also received updates that improve its data analysis capabilities. Users can now upload various file types, including spreadsheets and documents, directly into the model for analysis. The AI can generate visualisations and charts from the data, making it a powerful tool for businesses and researchers who need to interpret and present complex information quickly and accurately.&lt;/p&gt;

&lt;h4&gt;
  
  
  Introduction of Gemini 1.5 Flash
&lt;/h4&gt;

&lt;p&gt;In addition to the enhancements to the Gemini 1.5 Pro model, Google introduced the Gemini 1.5 Flash model. This new model is optimized for tasks that require rapid response times, making it ideal for applications where speed is critical. Despite being a smaller model, Gemini 1.5 Flash maintains high performance and accuracy, ensuring that users do not have to compromise on quality for the sake of speed.&lt;/p&gt;

&lt;h4&gt;
  
  
  New Features in the Gemini API
&lt;/h4&gt;

&lt;p&gt;Developers have much to look forward to with the new features available through the Gemini API. One of the most notable additions is the first-ever 2 million token context window, which allows for even more complex data processing and analysis. This feature is particularly beneficial for applications that require the AI to handle extensive and intricate datasets, providing developers with more flexibility and power in their projects.&lt;/p&gt;

&lt;p&gt;The Gemini API also includes new capabilities for native audio understanding, system instructions, and JSON mode. These features enhance the model's ability to interact with and process different types of data, broadening the scope of potential applications. Whether it's interpreting audio files, following detailed system instructions, or handling structured data in JSON format, the updated API provides the tools needed to leverage Gemini AI's full potential.&lt;/p&gt;

&lt;h4&gt;
  
  
  Expanding Practical Applications
&lt;/h4&gt;

&lt;p&gt;The recent updates to Gemini AI are part of Google's broader strategy to integrate advanced AI functionalities into everyday tools. Users can now upload files via Google Drive or directly from their devices, enabling the AI to perform in-depth analysis and provide insights on dense documents. This feature is particularly useful for professionals who need to analyse reports, research papers, and other extensive documents quickly and efficiently.&lt;/p&gt;

&lt;p&gt;Moreover, the Gemini 1.5 Pro model's multimodal capabilities have been enhanced. This means the AI is better equipped to understand and interact with images and audio, making it a versatile tool for a wide range of applications. Whether it's analysing visual data or processing spoken language, the updated model provides a more comprehensive and intuitive user experience.&lt;/p&gt;

&lt;h4&gt;
  
  
  Availability and Future Prospects
&lt;/h4&gt;

&lt;p&gt;Both the Gemini 1.5 Pro and Gemini 1.5 Flash models are available in preview across more than 200 countries and territories as of May 14, 2024. These models will be generally available in June 2024, allowing a wider audience to benefit from their advanced capabilities. Developers can start using these models by obtaining an API key through the &lt;a href="https://aistudio.google.com"&gt;Google AI Studio&lt;/a&gt; and exploring the &lt;a href="https://github.com"&gt;Gemini API Cookbook&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The continuous evolution of Gemini AI reflects Google's commitment to advancing AI technology and making it more accessible. By enhancing the capabilities of its models and expanding their practical applications, Google is paving the way for a future where AI plays a central role in various aspects of daily life and professional work.&lt;/p&gt;

&lt;p&gt;For more detailed updates and information, you can visit Google's official blog posts on the recent announcements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.google/technology/ai/gemini-1-5-pro-updates"&gt;Gemini 1.5 Pro updates and new models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.googleblog.com/gemini-1-5-flash-api"&gt;Introducing Gemini 1.5 Flash and API enhancements&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>googlecloud</category>
      <category>ai</category>
      <category>programming</category>
      <category>datascience</category>
    </item>
    <item>
      <title>"Community Communication: Slack vs. Discord - Deciding the Ideal Platform"</title>
      <dc:creator>Daisy Auma</dc:creator>
      <pubDate>Mon, 08 Apr 2024 19:15:25 +0000</pubDate>
      <link>https://forem.com/daisyauma/community-communication-slack-vs-discord-deciding-the-ideal-platform-2kdj</link>
      <guid>https://forem.com/daisyauma/community-communication-slack-vs-discord-deciding-the-ideal-platform-2kdj</guid>
      <description>&lt;p&gt;In the ever-evolving landscape of online communities, selecting the right platform is crucial to fostering effective communication and engagement. Two prominent players in this arena, Slack and Discord, offer unique features catering to different needs. To make an informed decision, it’s essential to weigh the pros and cons of each platform based on your community’s goals and characteristics.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Purpose and Audience:&lt;br&gt;
— Slack: Positioned as a professional collaboration tool, Slack is ideal for workplace communication. It exudes a polished and business-oriented environment, making it suitable for teams aiming for a more formal setting.&lt;br&gt;
— Discord: Originally designed for gamers, Discord has transitioned into a versatile platform for communities of all types. Its casual and gamer-friendly vibe appeals to a broader audience beyond traditional workplaces.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Features:&lt;br&gt;
— Slack: Renowned for its extensive integrations with productivity tools, Slack excels in supporting work-related tasks. Its advanced features, including file sharing and project management, make it a powerhouse for professional teams.&lt;br&gt;
— Discord: Offering voice channels, video calls, and screen sharing, Discord shines in real-time communication. It prioritizes community engagement with features like emojis, reactions, and customizable roles, enhancing the overall user experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cost:&lt;br&gt;
— Slack: While a free version is available, many advanced features are reserved for paid plans, with pricing based on the number of users. This makes Slack a potentially more expensive option for larger communities.&lt;br&gt;
— Discord: The majority of Discord’s features are accessible for free. While there is a paid subscription called Nitro with additional perks, it’s not essential for basic community functionality, making Discord a cost-effective choice for many.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;User Interface:&lt;br&gt;
— Slack: Boasting a clean and professional interface, Slack follows a more traditional chat and channel structure. Its design caters to those seeking a formal and structured communication environment.&lt;br&gt;
— Discord: Featuring a modern and gamer-friendly interface, Discord emphasizes simplicity and customization. The interface is user-friendly and appeals to a younger and more diverse audience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Customization:&lt;br&gt;
— Slack: Known for its high level of customization, Slack allows users to tailor channels, notifications, and integrations to suit diverse business needs. This makes it a versatile platform for teams with specific requirements.&lt;br&gt;
— Discord: Offers extensive server customization, enabling the creation of different channels, roles, and permissions. This flexibility caters to a wide range of community types and allows for a tailored user experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Moderation and Security:&lt;br&gt;
— Slack: Equipped with robust security features suitable for professional use, Slack provides adequate moderation tools. However, its moderation capabilities may not be as comprehensive as Discord’s, particularly for larger communities.&lt;br&gt;
— Discord: Renowned for its powerful moderation tools, Discord allows for precise control over roles and permissions. While its moderation features are strong, some may argue that it might not be as secure as Slack, especially in a professional context.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Community Size:&lt;br&gt;
— Slack: Better suited for smaller to medium-sized groups due to potential scalability challenges and pricing structures that may become less favourable for larger communities.&lt;br&gt;
— Discord: Built with scalability in mind, Discord excels in hosting larger communities and can comfortably accommodate thousands of members within a single server.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To conclude, the choice between Slack and Discord boils down to the specific needs and characteristics of your community. If you’re seeking a formal, professional environment with a focus on workplace collaboration, Slack may be the preferred option. On the other hand, if your community is more casual, diverse, and large, Discord offers a feature-rich and cost-effective solution for fostering engagement. Carefully evaluating these factors will help you choose the platform that aligns best with your community’s goals and culture.&lt;/p&gt;

&lt;p&gt;Tell me which one you prefer! Or better still, do have better options?&lt;/p&gt;

&lt;p&gt;Buy me coffee! &lt;a href="https://www.buymeacoffee.com/daisyauma"&gt;https://www.buymeacoffee.com/daisyauma&lt;/a&gt;&lt;/p&gt;

</description>
      <category>community</category>
      <category>discord</category>
      <category>slack</category>
      <category>socialmedia</category>
    </item>
  </channel>
</rss>
