> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getthread.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Set up Auto-Categorization

> Auto-assign ticket type, subtype, and item at intake with Magic AI Auto-Categorization — a Turn on assistive AI onboarding step for cleaner PSA data.

export const SidebarProgress = ({signals = {}}) => {
  useEffect(() => {
    const KEY = "thread-onboarding-completed";
    const TEAL = "#00B398";
    const DONE_ICON = '<svg class="thread-oc-doneicon size-4" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#00B398" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' + '<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>' + '<polyline points="22 4 12 14.01 9 11.01"/></svg>';
    const STAGES = [{
      slug: "set-up-your-workspace",
      steps: ["create-your-workspace", "configure-microsoft-sso", "configure-status-mapping", "optimize-notifications"]
    }, {
      slug: "turn-on-assistive-ai",
      steps: ["auto-prioritization", "auto-categorization", "auto-title", "magic-sentiment", "client-intelligence", "enable-super-magic", "auto-routing-flows"]
    }, {
      slug: "deploy-chat",
      steps: ["configure-messenger", "create-teams-app", "deploy-messenger"]
    }, {
      slug: "tune-ai",
      steps: ["magic-analytics", "super-magic-deep-dive", "announce-thread"]
    }, {
      slug: "equip-technicians",
      steps: ["inbox-views", "inbox-teams", "companion-app"]
    }, {
      slug: "run-a-pilot",
      steps: ["enablement-playbook", "technician-ritual"]
    }, {
      slug: "deploy-magic-agents",
      steps: ["triage-agent", "reminder-agent", "triage-agent-guide", "intent-creation"]
    }, {
      slug: "launch-to-clients",
      steps: ["marketing-assets", "communicate-rollout", "value-messaging"]
    }, {
      slug: "add-voice-ai",
      steps: ["voice-ai-config", "voice-overflow", "voice-auto-attendant"]
    }, {
      slug: "ai-service-unleashed",
      steps: ["review-analytics", "leadership-ritual"]
    }];
    const STAGE_SLUGS = new Set(STAGES.map(s => s.slug));
    const readLocal = () => {
      try {
        return JSON.parse(localStorage.getItem(KEY) || "[]");
      } catch (e) {
        return [];
      }
    };
    const isDone = (done, slug) => done.includes(slug) || !!(signals && signals[slug]);
    const decorate = () => {
      const done = readLocal();
      const scope = document.getElementById("sidebar-content") || document;
      const links = [...scope.querySelectorAll('a[href*="/onboarding/"]')].filter(a => !a.closest("main"));
      links.forEach(a => {
        const m = (a.getAttribute("href") || "").match(/\/onboarding\/([^/?#]+)/);
        if (!m) return;
        const slug = m[1];
        if (STAGE_SLUGS.has(slug)) return;
        const d = isDone(done, slug);
        const has = a.querySelector(".thread-oc-check");
        if (d && !has) {
          const s = document.createElement("span");
          s.className = "thread-oc-check";
          s.textContent = "✓ ";
          s.style.color = TEAL;
          s.style.fontWeight = "700";
          a.insertBefore(s, a.firstChild);
        } else if (!d && has) {
          has.remove();
        }
      });
      let nextMarked = false;
      STAGES.forEach(stage => {
        const li = scope.querySelector('li[id="/onboarding/' + stage.slug + '"]');
        if (!li) return;
        const row = li.querySelector(":scope > button") || li.querySelector(":scope > a") || li.firstElementChild;
        if (!row) return;
        const iconSvg = row.querySelector('svg[style*="mask"]');
        const iconWrap = iconSvg ? iconSvg.parentElement : null;
        const nameSpan = [...row.querySelectorAll("span")].find(s => s.textContent.trim() && !s.classList.contains("thread-oc-check"));
        const allDone = stage.steps.length > 0 && stage.steps.every(s => isDone(done, s));
        const isNext = !allDone && !nextMarked;
        if (isNext) nextMarked = true;
        if (iconWrap) {
          const injected = iconWrap.querySelector(".thread-oc-doneicon");
          if (allDone) {
            if (iconSvg) iconSvg.style.display = "none";
            if (!injected) iconWrap.insertAdjacentHTML("beforeend", DONE_ICON);
          } else {
            if (injected) injected.remove();
            if (iconSvg) iconSvg.style.display = "";
          }
        }
        if (iconSvg && !allDone) iconSvg.style.backgroundColor = isNext ? TEAL : "";
        if (nameSpan) {
          nameSpan.style.color = isNext ? TEAL : "";
          nameSpan.style.fontWeight = isNext ? "600" : "";
        }
      });
    };
    decorate();
    const iv = setInterval(decorate, 600);
    window.addEventListener("thread-onboarding-updated", decorate);
    window.addEventListener("storage", decorate);
    return () => {
      clearInterval(iv);
      window.removeEventListener("thread-onboarding-updated", decorate);
      window.removeEventListener("storage", decorate);
    };
  }, [signals]);
  return null;
};

export const MarkComplete = ({id, signalDone = false}) => {
  const KEY = "thread-onboarding-completed";
  const read = () => {
    try {
      return JSON.parse(localStorage.getItem(KEY) || "[]");
    } catch (e) {
      return [];
    }
  };
  const [localDone, setLocalDone] = useState(false);
  const done = signalDone || localDone;
  useEffect(() => {
    const sync = () => setLocalDone(read().includes(id));
    sync();
    window.addEventListener("thread-onboarding-updated", sync);
    return () => window.removeEventListener("thread-onboarding-updated", sync);
  }, [id]);
  const toggle = () => {
    if (signalDone) return;
    const list = read();
    const next = list.includes(id) ? list.filter(x => x !== id) : [...list, id];
    try {
      localStorage.setItem(KEY, JSON.stringify(next));
    } catch (e) {}
    setLocalDone(next.includes(id));
    window.dispatchEvent(new Event("thread-onboarding-updated"));
  };
  return <button onClick={toggle} style={{
    width: "100%",
    padding: "0.7rem 1rem",
    marginTop: "1.5rem",
    borderRadius: "8px",
    border: "1px solid " + (done ? "#00B398" : "rgba(128,128,128,0.35)"),
    background: done ? "rgba(0,179,152,0.12)" : "transparent",
    color: done ? "#00B398" : "inherit",
    fontWeight: 600,
    fontSize: "0.95rem",
    cursor: "pointer",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    gap: "0.5rem"
  }}>
      <span aria-hidden="true">{done ? "✓" : "○"}</span>
      {done ? "Completed" : "Mark as complete"}
    </button>;
};

<SidebarProgress signals={typeof user !== "undefined" ? user?.content?.onboarding : undefined} />

Have Magic AI assign Type, Subtype, and Item on every ticket so the board stays organized without manual tagging.

<Card title="Open the full guide — Auto-Categorization" icon="tags" href="/assistive-ai/getting-started-with-auto-categorization">
  Configure Auto-Categorization rules and test them in the emulator.
</Card>

<MarkComplete id="auto-categorization" signalDone={typeof user !== "undefined" ? !!(user?.content?.onboarding && user.content.onboarding["auto-categorization"]) : false} />


## Related topics

- [Configure Auto Categorization in Thread](/assistive-ai/getting-started-with-auto-categorization.md)
- [Configure Ticket Type Categorization in Autotask and Halo](/assistive-ai/configuring-ticket-type-categorization.md)
- [Configure Magic Title in Thread](/assistive-ai/setting-up-magic-title.md)
