diff --git a/web/src/compose_setup.js b/web/src/compose_setup.js
index 7f410e9321..409d90990c 100644
--- a/web/src/compose_setup.js
+++ b/web/src/compose_setup.js
@@ -186,16 +186,16 @@ export function initialize() {
 
                     // Renarrow to the unresolved topic if currently viewing the resolved topic.
                     const current_filter = narrow_state.filter();
-                    const channel_name = sub_store.maybe_get_stream_name(stream_id);
+                    const stream_id_string = stream_id.toString();
                     if (
                         current_filter &&
                         (current_filter.is_conversation_view() ||
                             current_filter.is_conversation_view_with_near()) &&
-                        current_filter.has_topic(channel_name, topic_name)
+                        current_filter.has_topic(stream_id_string, topic_name)
                     ) {
                         message_view.show(
                             [
-                                {operator: "channel", operand: channel_name},
+                                {operator: "channel", operand: stream_id_string},
                                 {operator: "topic", operand: new_topic},
                             ],
                             {},
diff --git a/web/src/copy_and_paste.ts b/web/src/copy_and_paste.ts
index 48d8188367..8d26cd6a40 100644
--- a/web/src/copy_and_paste.ts
+++ b/web/src/copy_and_paste.ts
@@ -9,6 +9,7 @@ import * as compose_ui from "./compose_ui";
 import * as hash_util from "./hash_util";
 import * as message_lists from "./message_lists";
 import * as rows from "./rows";
+import * as stream_data from "./stream_data";
 import * as topic_link_util from "./topic_link_util";
 import * as util from "./util";
 
@@ -662,7 +663,10 @@ export function try_stream_topic_syntax_text(text: string): string | null {
         return null;
     }
 
-    if (topic_link_util.will_produce_broken_stream_topic_link(stream_topic.stream_name)) {
+    const stream = stream_data.get_sub_by_id(stream_topic.stream_id);
+    assert(stream !== undefined);
+    const stream_name = stream.name;
+    if (topic_link_util.will_produce_broken_stream_topic_link(stream_name)) {
         return null;
     }
 
@@ -673,7 +677,7 @@ export function try_stream_topic_syntax_text(text: string): string | null {
         return null;
     }
 
-    let syntax_text = "#**" + stream_topic.stream_name;
+    let syntax_text = "#**" + stream_name;
     if (stream_topic.topic_name) {
         syntax_text += ">" + stream_topic.topic_name;
     }
diff --git a/web/src/drafts_overlay_ui.js b/web/src/drafts_overlay_ui.js
index d201c3f1b1..e1b1493f6a 100644
--- a/web/src/drafts_overlay_ui.js
+++ b/web/src/drafts_overlay_ui.js
@@ -12,7 +12,6 @@ import * as messages_overlay_ui from "./messages_overlay_ui";
 import * as overlays from "./overlays";
 import * as people from "./people";
 import * as rendered_markdown from "./rendered_markdown";
-import * as stream_data from "./stream_data";
 import * as user_card_popover from "./user_card_popover";
 import * as user_group_popover from "./user_group_popover";
 
@@ -30,7 +29,7 @@ function restore_draft(draft_id) {
                 [
                     {
                         operator: "channel",
-                        operand: stream_data.get_stream_name_from_id(compose_args.stream_id),
+                        operand: compose_args.stream_id.toString(),
                     },
                     {operator: "topic", operand: compose_args.topic},
                 ],
diff --git a/web/src/filter.ts b/web/src/filter.ts
index 6c062e19d7..a069611eea 100644
--- a/web/src/filter.ts
+++ b/web/src/filter.ts
@@ -18,7 +18,6 @@ import type {UserPillItem} from "./search_suggestion";
 import {current_user, realm} from "./state_data";
 import type {NarrowTerm} from "./state_data";
 import * as stream_data from "./stream_data";
-import type {StreamSubscription} from "./sub_store";
 import * as unread from "./unread";
 import * as user_topics from "./user_topics";
 import * as util from "./util";
@@ -75,12 +74,15 @@ type ValidOrInvalidUser =
 const CHANNEL_SYNONYM = "stream";
 const CHANNELS_SYNONYM = "streams";
 
-function zephyr_stream_name_match(message: Message & {type: "stream"}, operand: string): boolean {
+function zephyr_stream_name_match(
+    message: Message & {type: "stream"},
+    stream_name: string,
+): boolean {
     // Zephyr users expect narrowing to "social" to also show messages to /^(un)*social(.d)*$/
     // (unsocial, ununsocial, social.d, etc)
     // TODO: hoist the regex compiling out of the closure
-    const m = /^(?:un)*(.+?)(?:\.d)*$/i.exec(operand);
-    let base_stream_name = operand;
+    const m = /^(?:un)*(.+?)(?:\.d)*$/i.exec(stream_name);
+    let base_stream_name = stream_name;
     if (m?.[1] !== undefined) {
         base_stream_name = m[1];
     }
@@ -88,8 +90,8 @@ function zephyr_stream_name_match(message: Message & {type: "stream"}, operand:
         /^(un)*/.source + _.escapeRegExp(base_stream_name) + /(\.d)*$/.source,
         "i",
     );
-    const stream_name = stream_data.get_stream_name_from_id(message.stream_id);
-    return related_regexp.test(stream_name);
+    const message_stream_name = stream_data.get_stream_name_from_id(message.stream_id);
+    return related_regexp.test(message_stream_name);
 }
 
 function zephyr_topic_name_match(message: Message & {type: "stream"}, operand: string): boolean {
@@ -209,15 +211,12 @@ function message_matches_search_term(message: Message, operator: string, operand
                 return false;
             }
 
-            operand = operand.toLowerCase();
             if (realm.realm_is_zephyr_mirror_realm) {
-                return zephyr_stream_name_match(message, operand);
+                const stream = stream_data.get_sub_by_id_string(operand);
+                return zephyr_stream_name_match(message, stream?.name ?? "");
             }
 
-            // Try to match by stream_id if have a valid sub for
-            // the operand. If we can't find the id, we return false.
-            const stream_id = stream_data.get_stream_id(operand);
-            return stream_id !== undefined && message.stream_id === stream_id;
+            return message.stream_id.toString() === operand;
         }
 
         case "topic":
@@ -291,7 +290,6 @@ const USER_OPERATORS = new Set([
 
 export class Filter {
     _terms: NarrowTerm[];
-    _sub?: StreamSubscription | undefined;
     _sorted_term_types?: string[] = undefined;
     _predicate?: (message: Message) => boolean;
     _can_mark_messages_read?: boolean;
@@ -355,7 +353,6 @@ export class Filter {
                 break;
 
             case "channel":
-                operand = stream_data.get_name(operand);
                 break;
             case "topic":
                 break;
@@ -495,6 +492,18 @@ export class Filter {
                 }
                 operand = Filter.decodeOperand(parts.join(":"), operator);
 
+                // Check for user-entered channel name. If the name is valid,
+                // convert it to id.
+                if (
+                    (operator === "stream" || operator === "channel") &&
+                    Number.isNaN(Number.parseInt(operand, 10))
+                ) {
+                    const sub = stream_data.get_sub(operand);
+                    if (sub) {
+                        operand = sub.stream_id.toString();
+                    }
+                }
+
                 // We use Filter.operator_to_prefix() to check if the
                 // operator is known.  If it is not known, then we treat
                 // it as a search for the given string (which may contain
@@ -545,7 +554,7 @@ export class Filter {
                 return Number.isInteger(Number(term.operand));
             case "channel":
             case "stream":
-                return stream_data.get_sub(term.operand) !== undefined;
+                return stream_data.get_sub_by_id_string(term.operand) !== undefined;
             case "channels":
             case "streams":
                 return term.operand === "public";
@@ -715,7 +724,7 @@ export class Filter {
                 Filter.canonicalize_operator(term.operator) === expected && !term.negated;
 
             if (is(terms[0], "channel") && is(terms[1], "topic")) {
-                const channel = terms[0].operand;
+                const channel = stream_data.get_valid_sub_by_id_string(terms[0].operand).name;
                 const topic = terms[1].operand;
                 parts.push({
                     type: "channel_topic",
@@ -783,6 +792,18 @@ export class Filter {
                 };
             }
             if (prefix_for_operator !== "") {
+                if (canonicalized_operator === "channel") {
+                    const stream = stream_data.get_sub_by_id_string(operand);
+                    if (stream) {
+                        return {
+                            type: "prefix_for_operator",
+                            prefix_for_operator,
+                            operand: stream.name,
+                        };
+                    }
+                    // Assume the operand is a partially formed name and return
+                    // the operator as the stream name in the next block.
+                }
                 return {
                     type: "prefix_for_operator",
                     prefix_for_operator,
@@ -863,12 +884,11 @@ export class Filter {
             const adjusted_term = {...term};
             if (
                 Filter.canonicalize_operator(term.operator) === "channel" &&
-                !util.lower_same(term.operand, message.display_recipient)
+                term.operand !== message.stream_id.toString()
             ) {
-                adjusted_term.operand = message.display_recipient;
+                adjusted_term.operand = message.stream_id.toString();
                 terms_changed = true;
             }
-
             if (
                 Filter.canonicalize_operator(term.operator) === "topic" &&
                 !util.lower_same(term.operand, message.topic)
@@ -890,11 +910,6 @@ export class Filter {
     setup_filter(terms: NarrowTerm[]): void {
         this._terms = this.fix_terms(terms);
         this.cached_sorted_terms_for_comparison = undefined;
-        if (this.has_operator("channel")) {
-            this._sub = stream_data.get_sub_by_name(this.operands("channel")[0]!);
-        } else {
-            this._sub = undefined;
-        }
     }
 
     equals(filter: Filter, excluded_operators?: string[]): boolean {
@@ -948,12 +963,15 @@ export class Filter {
     public_terms(): NarrowTerm[] {
         const safe_to_return = this._terms.filter(
             // Filter out the embedded narrow (if any).
-            (term) =>
-                !(
-                    page_params.narrow_stream !== undefined &&
-                    term.operator === "channel" &&
-                    term.operand.toLowerCase() === page_params.narrow_stream.toLowerCase()
-                ),
+            (term) => {
+                // TODO(stream_id): Ideally we have `page_params.narrow_stream_id`
+                if (page_params.narrow_stream === undefined || term.operator !== "channel") {
+                    return true;
+                }
+                const narrow_stream = stream_data.get_sub_by_name(page_params.narrow_stream);
+                assert(narrow_stream !== undefined);
+                return Number.parseInt(term.operand, 10) === narrow_stream.stream_id;
+            },
         );
         return safe_to_return;
     }
@@ -1170,15 +1188,16 @@ export class Filter {
             return "/#narrow/has/reaction/sender/me";
         }
         if (_.isEqual(term_types, ["channel", "topic", "search"])) {
+            const sub = stream_data.get_sub_by_id_string(this.operands("channel")[0]!);
             // if channel does not exist, redirect to home view
-            if (!this._sub) {
+            if (!sub) {
                 return "#";
             }
             return (
                 "/#narrow/" +
                 CHANNEL_SYNONYM +
                 "/" +
-                stream_data.name_to_slug(this.operands("channel")[0]!) +
+                stream_data.id_to_slug(sub.stream_id) +
                 "/topic/" +
                 this.operands("topic")[0]
             );
@@ -1191,17 +1210,16 @@ export class Filter {
 
         if (term_types[1] === "search") {
             switch (term_types[0]) {
-                case "channel":
+                case "channel": {
+                    const sub = stream_data.get_sub_by_id_string(this.operands("channel")[0]!);
                     // if channel does not exist, redirect to home view
-                    if (!this._sub) {
+                    if (!sub) {
                         return "#";
                     }
                     return (
-                        "/#narrow/" +
-                        CHANNEL_SYNONYM +
-                        "/" +
-                        stream_data.name_to_slug(this.operands("channel")[0]!)
+                        "/#narrow/" + CHANNEL_SYNONYM + "/" + stream_data.id_to_slug(sub.stream_id)
                     );
+                }
                 case "is-dm":
                     return "/#narrow/is/dm";
                 case "is-starred":
@@ -1250,21 +1268,23 @@ export class Filter {
             case "in-all":
                 icon = "home";
                 break;
-            case "channel":
-                if (!this._sub) {
+            case "channel": {
+                const sub = stream_data.get_sub_by_id_string(this.operands("channel")[0]!);
+                if (!sub) {
                     icon = "question-circle-o";
                     break;
                 }
-                if (this._sub.invite_only) {
+                if (sub.invite_only) {
                     zulip_icon = "lock";
                     break;
                 }
-                if (this._sub.is_web_public) {
+                if (sub.is_web_public) {
                     zulip_icon = "globe";
                     break;
                 }
                 zulip_icon = "hashtag";
                 break;
+            }
             case "is-dm":
                 zulip_icon = "user";
                 break;
@@ -1302,11 +1322,11 @@ export class Filter {
             (term_types.length === 2 && _.isEqual(term_types, ["channel", "topic"])) ||
             (term_types.length === 1 && _.isEqual(term_types, ["channel"]))
         ) {
-            if (!this._sub) {
-                const search_text = this.operands("channel")[0];
-                return $t({defaultMessage: "Unknown channel #{search_text}"}, {search_text});
+            const sub = stream_data.get_sub_by_id_string(this.operands("channel")[0]!);
+            if (!sub) {
+                return $t({defaultMessage: "Unknown channel"});
             }
-            return this._sub.name;
+            return sub.name;
         }
         if (
             (term_types.length === 2 && _.isEqual(term_types, ["dm", "near"])) ||
@@ -1512,8 +1532,10 @@ export class Filter {
         return new Filter(terms);
     }
 
-    has_topic(stream_name: string, topic: string): boolean {
-        return this.has_operand("channel", stream_name) && this.has_operand("topic", topic);
+    has_topic(stream_id: number, topic: string): boolean {
+        return (
+            this.has_operand("channel", stream_id.toString()) && this.has_operand("topic", topic)
+        );
     }
 
     sorted_term_types(): string[] {
@@ -1668,12 +1690,12 @@ export class Filter {
         this.requires_adjustment_for_moved_with_target = false;
     }
 
-    can_newly_match_moved_messages(new_channel: string, new_topic: string): boolean {
+    can_newly_match_moved_messages(new_channel_id: string, new_topic: string): boolean {
         // Checks if any of the operators on this Filter object have
         // the property that it's possible for their true value to
         // change as a result of messages being moved into the
         // channel/topic pair provided in the parameters.
-        if (this.has_operand_case_insensitive("channel", new_channel)) {
+        if (this.has_operand_case_insensitive("channel", new_channel_id)) {
             return true;
         }
 
diff --git a/web/src/hash_util.ts b/web/src/hash_util.ts
index 38850b14aa..39fc631a3d 100644
--- a/web/src/hash_util.ts
+++ b/web/src/hash_util.ts
@@ -36,18 +36,19 @@ export function encode_operand(operator: string, operand: string): string {
     }
 
     if (operator === "stream") {
-        return encode_stream_name(operand);
+        const stream_id = Number.parseInt(operand, 10);
+        return encode_stream_id(stream_id);
     }
 
     return internal_url.encodeHashComponent(operand);
 }
 
-export function encode_stream_name(operand: string): string {
-    // stream_data prefixes the stream id, but it does not do the
+export function encode_stream_id(stream_id: number): string {
+    // stream_data postfixes the stream name, but it does not do the
     // URI encoding piece
-    operand = stream_data.name_to_slug(operand);
+    const slug = stream_data.id_to_slug(stream_id);
 
-    return internal_url.encodeHashComponent(operand);
+    return internal_url.encodeHashComponent(slug);
 }
 
 export function decode_operand(operator: string, operand: string): string {
@@ -67,13 +68,7 @@ export function decode_operand(operator: string, operand: string): string {
     operand = internal_url.decodeHashComponent(operand);
 
     if (util.canonicalize_stream_synonyms(operator) === "stream") {
-        const stream_id = stream_data.slug_to_stream_id(operand);
-        if (stream_id) {
-            const stream = stream_data.get_sub_by_id(stream_id);
-            if (stream) {
-                return stream.name;
-            }
-        }
+        return stream_data.slug_to_stream_id(operand)?.toString() ?? "";
     }
 
     return operand;
@@ -294,7 +289,7 @@ export function validate_group_settings_hash(hash: string): string {
 
 export function decode_stream_topic_from_url(
     url_str: string,
-): {stream_name: string; topic_name?: string} | null {
+): {stream_id: number; topic_name?: string} | null {
     try {
         const url = new URL(url_str);
         if (url.origin !== window.location.origin || !url.hash.startsWith("#narrow")) {
@@ -314,13 +309,18 @@ export function decode_stream_topic_from_url(
         if (terms[0]?.operator !== "stream" && terms[0]?.operator !== "channel") {
             return null;
         }
+        const stream_id = Number.parseInt(terms[0].operand, 10);
+        // This can happen if we don't recognize the stream operand as a valid id.
+        if (Number.isNaN(stream_id)) {
+            return null;
+        }
         if (terms.length === 1) {
-            return {stream_name: terms[0].operand};
+            return {stream_id};
         }
         if (terms[1]?.operator !== "topic") {
             return null;
         }
-        return {stream_name: terms[0].operand, topic_name: terms[1].operand};
+        return {stream_id, topic_name: terms[1].operand};
     } catch {
         return null;
     }
diff --git a/web/src/hotkey.js b/web/src/hotkey.js
index 41e9caaee7..0c6fb3f2ef 100644
--- a/web/src/hotkey.js
+++ b/web/src/hotkey.js
@@ -54,7 +54,6 @@ import * as sidebar_ui from "./sidebar_ui";
 import * as spectators from "./spectators";
 import * as starred_messages_ui from "./starred_messages_ui";
 import {realm} from "./state_data";
-import * as stream_data from "./stream_data";
 import * as stream_list from "./stream_list";
 import * as stream_popover from "./stream_popover";
 import * as stream_settings_ui from "./stream_settings_ui";
@@ -1234,7 +1233,7 @@ export function process_hotkey(e, hotkey) {
                         [
                             {
                                 operator: "channel",
-                                operand: stream_data.get_stream_name_from_id(msg.stream_id),
+                                operand: msg.stream_id.toString(),
                             },
                             {operator: "topic", operand: msg.topic},
                             {operator: "near", operand: msg.id},
diff --git a/web/src/message_events.js b/web/src/message_events.js
index 0282becfdf..fff6593d5e 100644
--- a/web/src/message_events.js
+++ b/web/src/message_events.js
@@ -472,7 +472,6 @@ export function update_messages(events) {
                 });
             }
 
-            const old_stream_name = stream_archived ? undefined : old_stream.name;
             if (
                 going_forward_change &&
                 // This logic is a bit awkward.  What we're trying to
@@ -490,7 +489,8 @@ export function update_messages(events) {
                 // messages within a narrow.
                 selection_changed_topic &&
                 current_filter &&
-                current_filter.has_topic(old_stream_name, orig_topic)
+                old_stream_id &&
+                current_filter.has_topic(old_stream_id, orig_topic)
             ) {
                 let new_filter = current_filter;
                 if (new_filter && stream_changed) {
@@ -501,10 +501,9 @@ export function update_messages(events) {
                     // stream_data lookup here to fail.
                     //
                     // The fix is likely somewhat involved, so punting for now.
-                    const new_stream_name = sub_store.get(new_stream_id).name;
                     new_filter = new_filter.filter_with_new_params({
                         operator: "channel",
-                        operand: new_stream_name,
+                        operand: new_stream_id.toString(),
                     });
                     changed_narrow = true;
                 }
@@ -533,10 +532,10 @@ export function update_messages(events) {
             // If a message was moved to the current narrow and we don't have
             // the message cached, we need to refresh the narrow to display the message.
             if (!changed_narrow && local_cache_missing_messages && current_filter) {
-                let moved_message_stream = old_stream_name;
+                let moved_message_stream_id = old_stream_id;
                 let moved_message_topic = orig_topic;
                 if (stream_changed) {
-                    moved_message_stream = sub_store.get(new_stream_id).name;
+                    moved_message_stream_id = sub_store.get(new_stream_id).stream_id.toString();
                 }
 
                 if (topic_edited) {
@@ -545,7 +544,7 @@ export function update_messages(events) {
 
                 if (
                     current_filter.can_newly_match_moved_messages(
-                        moved_message_stream,
+                        moved_message_stream_id,
                         moved_message_topic,
                     )
                 ) {
diff --git a/web/src/message_fetch.ts b/web/src/message_fetch.ts
index e941eb1e3c..02d1086193 100644
--- a/web/src/message_fetch.ts
+++ b/web/src/message_fetch.ts
@@ -340,7 +340,25 @@ export function load_messages(opts: MessageFetchOptions, attempt = 1): void {
         if (page_params.narrow !== undefined) {
             terms = [...terms, ...page_params.narrow];
         }
-        data.narrow = JSON.stringify(terms);
+        // TODO(stream_id): The server would ideally work with stream ids instead
+        // of stream names.
+        const server_terms = [];
+        for (const term of terms) {
+            if (term.operator === "channel") {
+                const sub = stream_data.get_sub_by_id_string(term.operand);
+                server_terms.push({
+                    ...term,
+                    // If the sub is undefined, we'll get an error which is handled
+                    // later by showing a "this channel does not exist or is private"
+                    // notice.
+                    operand: sub?.name,
+                });
+            } else {
+                server_terms.push({...term});
+            }
+        }
+
+        data.narrow = JSON.stringify(server_terms);
     }
 
     let update_loading_indicator =
diff --git a/web/src/message_view.js b/web/src/message_view.js
index d635af3590..41a1d89908 100644
--- a/web/src/message_view.js
+++ b/web/src/message_view.js
@@ -450,10 +450,11 @@ export function show(raw_terms, opts) {
                 // location, then we should retarget this narrow operation
                 // to where the message is located now.
                 const narrow_topic = filter.operands("topic")[0];
-                const narrow_stream_name = filter.operands("channel")[0];
-                const narrow_stream_data = stream_data.get_sub(narrow_stream_name);
+                const narrow_stream_data = stream_data.get_sub_by_id_string(
+                    filter.operands("channel")[0],
+                );
                 if (!narrow_stream_data) {
-                    // The stream name is invalid or incorrect in the URL.
+                    // The stream id is invalid or incorrect in the URL.
                     // We reconstruct the narrow with the data from the
                     // target message ID that we have.
                     const adjusted_terms = Filter.adjusted_terms_if_moved(
@@ -1085,10 +1086,9 @@ export function render_message_list_with_selected_message(opts) {
     narrow_history.save_narrow_state_and_flush();
 }
 
-export function activate_stream_for_cycle_hotkey(stream_id) {
-    const stream_name = stream_data.get_stream_name_from_id(stream_id);
+function activate_stream_for_cycle_hotkey(stream_id) {
     // This is the common code for A/D hotkeys.
-    const filter_expr = [{operator: "channel", operand: stream_name}];
+    const filter_expr = [{operator: "channel", operand: stream_id.toString()}];
     show(filter_expr, {});
 }
 
@@ -1158,9 +1158,8 @@ export function narrow_to_next_topic(opts = {}) {
         return;
     }
 
-    const stream_name = stream_data.get_stream_name_from_id(next_narrow.stream_id);
     const filter_expr = [
-        {operator: "channel", operand: stream_name},
+        {operator: "channel", operand: next_narrow.stream_id.toString()},
         {operator: "topic", operand: next_narrow.topic},
     ];
 
@@ -1218,9 +1217,8 @@ export function narrow_by_topic(target_id, opts) {
         unread_ops.notify_server_message_read(original);
     }
 
-    const stream_name = stream_data.get_stream_name_from_id(original.stream_id);
     const search_terms = [
-        {operator: "channel", operand: stream_name},
+        {operator: "channel", operand: original.stream_id.toString()},
         {operator: "topic", operand: original.topic},
     ];
     opts = {then_select_id: target_id, ...opts};
@@ -1264,7 +1262,7 @@ export function narrow_by_recipient(target_id, opts) {
                 [
                     {
                         operator: "stream",
-                        operand: stream_data.get_stream_name_from_id(message.stream_id),
+                        operand: message.stream_id.toString(),
                     },
                 ],
                 opts,
@@ -1289,10 +1287,9 @@ export function to_compose_target() {
         if (!stream_id) {
             return;
         }
-        const stream_name = stream_data.get_sub_by_id(stream_id).name;
         // If we are composing to a new topic, we narrow to the stream but
         // grey-out the message view instead of narrowing to an empty view.
-        const terms = [{operator: "channel", operand: stream_name}];
+        const terms = [{operator: "channel", operand: stream_id.toString()}];
         const topic = compose_state.topic();
         if (topic !== "") {
             terms.push({operator: "topic", operand: topic});
diff --git a/web/src/message_view_header.ts b/web/src/message_view_header.ts
index b2fc9fe056..fce682086a 100644
--- a/web/src/message_view_header.ts
+++ b/web/src/message_view_header.ts
@@ -14,6 +14,7 @@ import * as recent_view_util from "./recent_view_util";
 import * as rendered_markdown from "./rendered_markdown";
 import * as search from "./search";
 import {current_user} from "./state_data";
+import * as stream_data from "./stream_data";
 import type {SettingsSubscription} from "./stream_settings_data";
 import type {StreamSubscription} from "./sub_store";
 
@@ -99,22 +100,21 @@ function get_message_view_header_context(filter: Filter | undefined): MessageVie
         is_spectator: page_params.is_spectator,
     });
 
-    if (filter.has_operator("channel") && !filter._sub) {
-        return {
-            ...context,
-            sub_count: "0",
-            formatted_sub_count: "0",
-            rendered_narrow_description: $t({
-                defaultMessage: "This channel does not exist or is private.",
-            }),
-        };
-    }
-
-    if (filter._sub) {
+    if (filter.has_operator("channel")) {
+        const current_stream = stream_data.get_sub_by_id_string(filter.operands("channel")[0]!);
+        if (!current_stream) {
+            return {
+                ...context,
+                sub_count: "0",
+                formatted_sub_count: "0",
+                rendered_narrow_description: $t({
+                    defaultMessage: "This channel does not exist or is private.",
+                }),
+            };
+        }
         // We can now be certain that the narrow
         // involves a stream which exists and
         // the current user can access.
-        const current_stream = filter._sub;
         const sub_count = peer_data.get_subscriber_count(current_stream.stream_id);
         return {
             ...context,
@@ -131,11 +131,15 @@ function get_message_view_header_context(filter: Filter | undefined): MessageVie
 
 export function colorize_message_view_header(): void {
     const filter = narrow_state.filter();
-    if (filter === undefined || !filter._sub) {
+    let current_stream;
+    if (filter?.has_operator("channel")) {
+        current_stream = stream_data.get_valid_sub_by_id_string(filter.operands("channel")[0]!);
+    }
+    if (!current_stream) {
         return;
     }
     // selecting i instead of .fa because web public streams have custom icon.
-    $("#message_view_header a.stream i").css("color", filter._sub.color);
+    $("#message_view_header a.stream i").css("color", current_stream.color);
 }
 
 function append_and_display_title_area(context: MessageViewHeaderContext): void {
@@ -175,10 +179,10 @@ export function render_title_area(): void {
 
 // This function checks if "modified_sub" which is the stream whose values
 // have been updated is the same as the stream which is currently
-// narrowed (filter._sub) and rerenders if necessary
+// narrowed and rerenders if necessary
 export function maybe_rerender_title_area_for_stream(modified_sub: SettingsSubscription): void {
-    const filter = narrow_state.filter();
-    if (filter && filter._sub && filter._sub.stream_id === modified_sub.stream_id) {
+    const current_stream_id = narrow_state.stream_id();
+    if (current_stream_id === modified_sub.stream_id) {
         render_title_area();
     }
 }
diff --git a/web/src/narrow_banner.ts b/web/src/narrow_banner.ts
index 9405fcc632..528b2015ba 100644
--- a/web/src/narrow_banner.ts
+++ b/web/src/narrow_banner.ts
@@ -76,10 +76,11 @@ function retrieve_search_query_data(): SearchData {
 
     // Add in stream:foo and topic:bar if present
     if (current_filter.has_operator("channel") || current_filter.has_operator("topic")) {
-        const stream = current_filter.operands("channel")[0];
+        const stream_id = current_filter.operands("channel")[0];
         const topic = current_filter.operands("topic")[0];
-        if (stream) {
-            search_string_result.stream_query = stream;
+        if (stream_id) {
+            const stream_name = stream_data.get_valid_sub_by_id_string(stream_id).name;
+            search_string_result.stream_query = stream_name;
         }
         if (topic) {
             search_string_result.topic_query = topic;
@@ -184,7 +185,7 @@ export function pick_empty_narrow_banner(): NarrowBannerData {
         if (
             page_params.is_spectator &&
             first_operator === "channel" &&
-            !stream_data.is_web_public_by_stream_name(first_operand)
+            !stream_data.is_web_public_by_stream_id(Number.parseInt(first_operand, 10))
         ) {
             // For non web-public streams, show `login_to_access` modal.
             spectators.login_to_access(true);
@@ -264,7 +265,7 @@ export function pick_empty_narrow_banner(): NarrowBannerData {
             // fallthrough to default case if no match is found
             break;
         case "channel":
-            if (!stream_data.is_subscribed_by_name(first_operand)) {
+            if (!stream_data.is_subscribed(Number.parseInt(first_operand, 10))) {
                 // You are narrowed to a stream which does not exist or is a private stream
                 // in which you were never subscribed.
 
@@ -280,7 +281,7 @@ export function pick_empty_narrow_banner(): NarrowBannerData {
                         return false;
                     }
 
-                    const stream_sub = stream_data.get_sub(first_operand);
+                    const stream_sub = stream_data.get_sub_by_id_string(first_operand);
                     return stream_sub && stream_data.can_toggle_subscription(stream_sub);
                 }
 
diff --git a/web/src/narrow_state.ts b/web/src/narrow_state.ts
index e7a255b31a..8d595e7ddf 100644
--- a/web/src/narrow_state.ts
+++ b/web/src/narrow_state.ts
@@ -114,21 +114,26 @@ export function set_compose_defaults(): {
     return opts;
 }
 
-export function stream_name(current_filter: Filter | undefined = filter()): string | undefined {
+export function stream_id(current_filter: Filter | undefined = filter()): number | undefined {
     if (current_filter === undefined) {
         return undefined;
     }
     const stream_operands = current_filter.operands("channel");
     if (stream_operands.length === 1 && stream_operands[0] !== undefined) {
-        const name = stream_operands[0];
-
-        // Use get_name() to get the most current stream
-        // name (considering renames and capitalization).
-        return stream_data.get_name(name);
+        return Number.parseInt(stream_operands[0], 10);
     }
     return undefined;
 }
 
+export function stream_name(current_filter: Filter | undefined = filter()): string | undefined {
+    const id = stream_id(current_filter);
+    if (id === undefined) {
+        return undefined;
+    }
+    const sub = stream_data.get_sub_by_id(id);
+    return sub?.name;
+}
+
 export function stream_sub(
     current_filter: Filter | undefined = filter(),
 ): StreamSubscription | undefined {
@@ -140,19 +145,7 @@ export function stream_sub(
     if (stream_operands.length !== 1 || stream_operands[0] === undefined) {
         return undefined;
     }
-
-    const name = stream_operands[0];
-    const sub = stream_data.get_sub_by_name(name);
-
-    return sub;
-}
-
-export function stream_id(filter?: Filter): number | undefined {
-    const sub = stream_sub(filter);
-    if (sub === undefined) {
-        return undefined;
-    }
-    return sub.stream_id;
+    return stream_data.get_sub_by_id_string(stream_operands[0]);
 }
 
 export function topic(current_filter: Filter | undefined = filter()): string | undefined {
diff --git a/web/src/narrow_title.ts b/web/src/narrow_title.ts
index 0437d5631a..35d7ef7e84 100644
--- a/web/src/narrow_title.ts
+++ b/web/src/narrow_title.ts
@@ -8,6 +8,7 @@ import * as inbox_util from "./inbox_util";
 import * as people from "./people";
 import * as recent_view_util from "./recent_view_util";
 import {realm} from "./state_data";
+import * as stream_data from "./stream_data";
 import * as unread from "./unread";
 import type {FullUnreadCountsData} from "./unread";
 
@@ -34,10 +35,11 @@ export function compute_narrow_title(filter?: Filter): string {
     }
 
     if (filter.has_operator("channel")) {
-        if (!filter._sub) {
+        const sub = stream_data.get_sub_by_id_string(filter.operands("channel")[0]!);
+        if (!sub) {
             // The stream is not set because it does not currently
-            // exist (possibly due to a stream name change), or it
-            // is a private stream and the user is not subscribed.
+            // exist, or it is a private stream and the user is not
+            // subscribed.
             return filter_title;
         }
         if (filter.has_operator("topic")) {
diff --git a/web/src/scheduled_messages_ui.js b/web/src/scheduled_messages_ui.js
index e863cc91fd..4864597106 100644
--- a/web/src/scheduled_messages_ui.js
+++ b/web/src/scheduled_messages_ui.js
@@ -8,7 +8,6 @@ import {$t} from "./i18n";
 import * as message_view from "./message_view";
 import * as people from "./people";
 import * as scheduled_messages from "./scheduled_messages";
-import * as stream_data from "./stream_data";
 import * as timerender from "./timerender";
 
 export function hide_scheduled_message_success_compose_banner(scheduled_message_id) {
@@ -23,7 +22,7 @@ function narrow_via_edit_scheduled_message(compose_args) {
             [
                 {
                     operator: "channel",
-                    operand: stream_data.get_stream_name_from_id(compose_args.stream_id),
+                    operand: compose_args.stream_id,
                 },
                 {operator: "topic", operand: compose_args.topic},
             ],
diff --git a/web/src/search_pill.ts b/web/src/search_pill.ts
index 9bd3184272..6f1fd52a9b 100644
--- a/web/src/search_pill.ts
+++ b/web/src/search_pill.ts
@@ -10,6 +10,7 @@ import type {InputPill, InputPillContainer} from "./input_pill";
 import * as people from "./people";
 import type {User} from "./people";
 import type {NarrowTerm} from "./state_data";
+import * as stream_data from "./stream_data";
 import * as user_status from "./user_status";
 import type {UserStatusEmojiInfo} from "./user_status";
 import * as util from "./util";
@@ -56,7 +57,7 @@ export function create_item_from_search_string(search_string: string): SearchPil
 
 export function get_search_string_from_item(item: SearchPill): string {
     const sign = item.negated ? "-" : "";
-    return `${sign}${item.operator}: ${get_search_operand(item)}`;
+    return `${sign}${item.operator}: ${get_search_operand(item, true)}`;
 }
 
 // This is called when the a pill is closed. We have custom logic here
@@ -215,16 +216,19 @@ export function set_search_bar_contents(
     }
 }
 
-function get_search_operand(item: SearchPill): string {
+function get_search_operand(item: SearchPill, for_display: boolean): string {
     if (item.type === "search_user") {
         return item.users.map((user) => user.email).join(",");
     }
+    if (for_display && item.operator === "channel") {
+        return stream_data.get_valid_sub_by_id_string(item.operand).name;
+    }
     return item.operand;
 }
 
 export function get_current_search_pill_terms(pill_widget: SearchPillWidget): NarrowTerm[] {
     return pill_widget.items().map((item) => ({
         ...item,
-        operand: get_search_operand(item),
+        operand: get_search_operand(item, false),
     }));
 }
diff --git a/web/src/search_suggestion.ts b/web/src/search_suggestion.ts
index e9e4a2ba46..314aa85253 100644
--- a/web/src/search_suggestion.ts
+++ b/web/src/search_suggestion.ts
@@ -159,21 +159,23 @@ function get_channel_suggestions(last: NarrowTerm, terms: NarrowTerm[]): Suggest
     const query = last.operand;
     let channels = stream_data.subscribed_streams();
 
-    channels = channels.filter((channel) => channel_matches_query(channel, query));
+    channels = channels.filter((channel_name) => channel_matches_query(channel_name, query));
 
     channels = typeahead_helper.sorter(query, channels, (x) => x);
 
     const regex = typeahead_helper.build_highlight_regex(query);
     const highlight_query = typeahead_helper.highlight_with_escaping_and_regex;
 
-    return channels.map((channel) => {
+    return channels.map((channel_name) => {
         const prefix = "channel";
-        const highlighted_channel = highlight_query(regex, channel);
+        const highlighted_channel = highlight_query(regex, channel_name);
         const verb = last.negated ? "exclude " : "";
         const description_html = verb + prefix + " " + highlighted_channel;
+        const channel = stream_data.get_sub_by_name(channel_name);
+        assert(channel !== undefined);
         const term = {
             operator: "channel",
-            operand: channel,
+            operand: channel.stream_id.toString(),
             negated: last.negated,
         };
         const search_string = Filter.unparse([term]);
@@ -473,7 +475,7 @@ function get_topic_suggestions(last: NarrowTerm, terms: NarrowTerm[]): Suggestio
     const operator = Filter.canonicalize_operator(last.operator);
     const operand = last.operand;
     const negated = operator === "topic" && last.negated;
-    let channel: string | undefined;
+    let channel_id: string | undefined;
     let guess: string | undefined;
     const filter = new Filter(terms);
     const suggest_terms: NarrowTerm[] = [];
@@ -496,28 +498,28 @@ function get_topic_suggestions(last: NarrowTerm, terms: NarrowTerm[]): Suggestio
     switch (operator) {
         case "channel":
             guess = "";
-            channel = operand;
+            channel_id = operand;
             suggest_terms.push(last);
             break;
         case "topic":
         case "search":
             guess = operand;
             if (filter.has_operator("channel")) {
-                channel = filter.operands("channel")[0];
+                channel_id = filter.operands("channel")[0];
             } else {
-                channel = narrow_state.stream_name();
-                if (channel) {
-                    suggest_terms.push({operator: "channel", operand: channel});
+                channel_id = narrow_state.stream_id()?.toString();
+                if (channel_id) {
+                    suggest_terms.push({operator: "channel", operand: channel_id});
                 }
             }
             break;
     }
 
-    if (!channel) {
+    if (!channel_id) {
         return [];
     }
 
-    const subscription = stream_data.get_sub(channel);
+    const subscription = stream_data.get_sub_by_id_string(channel_id);
     if (!subscription) {
         return [];
     }
@@ -849,12 +851,12 @@ function suggestion_search_string(suggestion_line: SuggestionLine): string {
 }
 
 function suggestions_for_current_filter(): SuggestionLine[] {
-    if (narrow_state.stream_name() && narrow_state.topic() !== "") {
+    if (narrow_state.stream_id() && narrow_state.topic() !== "") {
         return [
             get_default_suggestion_line([
                 {
                     operator: "channel",
-                    operand: narrow_state.stream_name()!,
+                    operand: narrow_state.stream_id()!.toString(),
                 },
             ]),
             get_default_suggestion_line(narrow_state.search_terms()),
diff --git a/web/src/stream_data.ts b/web/src/stream_data.ts
index 8c5e81d28a..392a1eae6b 100644
--- a/web/src/stream_data.ts
+++ b/web/src/stream_data.ts
@@ -1,3 +1,5 @@
+import assert from "minimalistic-assert";
+
 import * as blueslip from "./blueslip";
 import * as color_data from "./color_data";
 import {FoldDict} from "./fold_dict";
@@ -177,6 +179,18 @@ export function get_sub(stream_name: string): StreamSubscription | undefined {
     return undefined;
 }
 
+export function get_sub_by_id_string(stream_id_string: string): StreamSubscription | undefined {
+    const stream_id = Number.parseInt(stream_id_string, 10);
+    const stream = stream_info.get(stream_id);
+    return stream;
+}
+
+export function get_valid_sub_by_id_string(stream_id_string: string): StreamSubscription {
+    const stream = get_sub_by_id_string(stream_id_string);
+    assert(stream !== undefined);
+    return stream;
+}
+
 export function get_sub_by_id(stream_id: number): StreamSubscription | undefined {
     return stream_info.get(stream_id);
 }
@@ -222,13 +236,8 @@ export function get_sub_by_name(name: string): StreamSubscription | undefined {
     return sub_store.get(stream_id);
 }
 
-export function name_to_slug(name: string): string {
-    const stream_id = get_stream_id(name);
-
-    if (!stream_id) {
-        return name;
-    }
-
+export function id_to_slug(stream_id: number): string {
+    let name = get_stream_name_from_id(stream_id);
     // The name part of the URL doesn't really matter, so we try to
     // make it pretty.
     name = name.replaceAll(" ", "-");
@@ -605,11 +614,6 @@ export function can_post_messages_in_stream(stream: StreamSubscription): boolean
     return true;
 }
 
-export function is_subscribed_by_name(stream_name: string): boolean {
-    const sub = get_sub(stream_name);
-    return sub ? sub.subscribed : false;
-}
-
 export function is_subscribed(stream_id: number): boolean {
     const sub = sub_store.get(stream_id);
     return sub ? sub.subscribed : false;
@@ -643,8 +647,8 @@ export function is_invite_only_by_stream_id(stream_id: number): boolean {
     return sub.invite_only;
 }
 
-export function is_web_public_by_stream_name(stream_name: string): boolean {
-    const sub = get_sub(stream_name);
+export function is_web_public_by_stream_id(stream_id: number): boolean {
+    const sub = get_sub_by_id(stream_id);
     if (sub === undefined) {
         return false;
     }
@@ -667,22 +671,6 @@ export function is_default_stream_id(stream_id: number): boolean {
     return default_stream_ids.has(stream_id);
 }
 
-export function get_name(stream_name: string): string {
-    // This returns the actual name of a stream if we are subscribed to
-    // it (e.g. "Denmark" vs. "denmark"), while falling thru to
-    // stream_name if we don't have a subscription.  (Stream names
-    // are case-insensitive, but we try to display the actual name
-    // when we know it.)
-    //
-    // This function will also do the right thing if we have
-    // an old stream name in memory for a recently renamed stream.
-    const sub = get_sub_by_name(stream_name);
-    if (sub === undefined) {
-        return stream_name;
-    }
-    return sub.name;
-}
-
 export function is_user_subscribed(stream_id: number, user_id: number): boolean {
     const sub = sub_store.get(stream_id);
     if (sub === undefined || !can_view_subscribers(sub)) {
diff --git a/web/src/stream_list.ts b/web/src/stream_list.ts
index 253fb2d504..ee212e9397 100644
--- a/web/src/stream_list.ts
+++ b/web/src/stream_list.ts
@@ -682,8 +682,7 @@ export function get_sidebar_stream_topic_info(filter: Filter): {
         return result;
     }
 
-    const stream_name = op_stream[0];
-    const stream_id = stream_data.get_stream_id(stream_name);
+    const stream_id = Number.parseInt(op_stream[0], 10);
 
     if (!stream_id) {
         return result;
diff --git a/web/src/stream_popover.js b/web/src/stream_popover.js
index 4b1d495799..3974e31e94 100644
--- a/web/src/stream_popover.js
+++ b/web/src/stream_popover.js
@@ -138,7 +138,7 @@ function build_stream_popover(opts) {
                     [
                         {
                             operator: "stream",
-                            operand: sub.name,
+                            operand: sub.stream_id.toString(),
                         },
                     ],
                     {trigger: "stream-popover"},
diff --git a/web/src/stream_settings_ui.js b/web/src/stream_settings_ui.js
index baf4276840..8684d83dda 100644
--- a/web/src/stream_settings_ui.js
+++ b/web/src/stream_settings_ui.js
@@ -903,7 +903,7 @@ export function view_stream() {
     const row_data = get_row_data(active_data.$row);
     if (row_data) {
         const stream_narrow_hash =
-            "#narrow/stream/" + hash_util.encode_stream_name(row_data.object.name);
+            "#narrow/stream/" + hash_util.encode_stream_id(row_data.object.stream_id);
         browser_history.go_to_location(stream_narrow_hash);
     }
 }
diff --git a/web/src/topic_generator.ts b/web/src/topic_generator.ts
index 5d1ee7d0a6..4880b512d2 100644
--- a/web/src/topic_generator.ts
+++ b/web/src/topic_generator.ts
@@ -1,5 +1,4 @@
 import _ from "lodash";
-import assert from "minimalistic-assert";
 
 import * as narrow_state from "./narrow_state";
 import * as pm_conversations from "./pm_conversations";
@@ -87,7 +86,6 @@ export function get_next_topic(
 
     function get_unmuted_topics(stream_id: number): string[] {
         const narrowed_steam_id = narrow_state.stream_id();
-        assert(stream_id !== undefined);
         const topics = stream_topic_history.get_recent_topic_names(stream_id);
         const narrowed_topic = narrow_state.topic();
         if (
@@ -119,16 +117,11 @@ export function get_next_topic(
         return topics;
     }
 
-    function has_unread_messages(stream_id: number, topic: string): boolean {
-        assert(stream_id !== undefined);
-        return unread.topic_has_any_unread(stream_id, topic);
-    }
-
     if (only_followed_topics) {
         return next_topic(
             my_streams,
             get_followed_topics,
-            has_unread_messages,
+            unread.topic_has_any_unread,
             curr_stream_id,
             curr_topic,
         );
@@ -137,7 +130,7 @@ export function get_next_topic(
     return next_topic(
         my_streams,
         get_unmuted_topics,
-        has_unread_messages,
+        unread.topic_has_any_unread,
         curr_stream_id,
         curr_topic,
     );
diff --git a/web/src/ui_init.js b/web/src/ui_init.js
index a22ff92bdb..c72ff0c3dd 100644
--- a/web/src/ui_init.js
+++ b/web/src/ui_init.js
@@ -549,7 +549,7 @@ export function initialize_everything(state_data) {
                 [
                     {
                         operator: "stream",
-                        operand: sub.name,
+                        operand: sub.stream_id.toString(),
                     },
                 ],
                 {trigger},
@@ -658,7 +658,7 @@ export function initialize_everything(state_data) {
             const sub = sub_store.get(stream_id);
             message_view.show(
                 [
-                    {operator: "channel", operand: sub.name},
+                    {operator: "channel", operand: sub.stream_id.toString()},
                     {operator: "topic", operand: topic},
                 ],
                 {trigger: "sidebar"},
diff --git a/web/src/unread_ops.ts b/web/src/unread_ops.ts
index dd5e9d7d7f..e7f5307023 100644
--- a/web/src/unread_ops.ts
+++ b/web/src/unread_ops.ts
@@ -22,7 +22,6 @@ import * as overlays from "./overlays";
 import * as people from "./people";
 import * as recent_view_ui from "./recent_view_ui";
 import type {NarrowTerm} from "./state_data";
-import * as stream_data from "./stream_data";
 import * as ui_report from "./ui_report";
 import * as unread from "./unread";
 import * as unread_ui from "./unread_ui";
@@ -610,22 +609,20 @@ export function process_visible(): void {
 }
 
 export function mark_stream_as_read(stream_id: number): void {
-    const stream_name = stream_data.get_stream_name_from_id(stream_id);
     bulk_update_read_flags_for_narrow(
         [
             {operator: "is", operand: "unread", negated: false},
-            {operator: "channel", operand: stream_name},
+            {operator: "channel", operand: stream_id.toString()},
         ],
         "add",
     );
 }
 
 export function mark_topic_as_read(stream_id: number, topic: string): void {
-    const stream_name = stream_data.get_stream_name_from_id(stream_id);
     bulk_update_read_flags_for_narrow(
         [
             {operator: "is", operand: "unread", negated: false},
-            {operator: "channel", operand: stream_name},
+            {operator: "channel", operand: stream_id.toString()},
             {operator: "topic", operand: topic},
         ],
         "add",
@@ -633,10 +630,9 @@ export function mark_topic_as_read(stream_id: number, topic: string): void {
 }
 
 export function mark_topic_as_unread(stream_id: number, topic: string): void {
-    const stream_name = stream_data.get_stream_name_from_id(stream_id);
     bulk_update_read_flags_for_narrow(
         [
-            {operator: "channel", operand: stream_name},
+            {operator: "channel", operand: stream_id.toString()},
             {operator: "topic", operand: topic},
         ],
         "remove",
diff --git a/web/tests/activity.test.js b/web/tests/activity.test.js
index 3275a07c5b..d6b9394bbd 100644
--- a/web/tests/activity.test.js
+++ b/web/tests/activity.test.js
@@ -105,7 +105,7 @@ const $fred_stub = $.create("fred stub");
 const rome_sub = {name: "Rome", subscribed: true, stream_id: 1001};
 function add_sub_and_set_as_current_narrow(sub) {
     stream_data.add_sub(sub);
-    const filter = new Filter([{operator: "stream", operand: sub.name}]);
+    const filter = new Filter([{operator: "stream", operand: sub.stream_id}]);
     message_lists.set_current({
         data: {
             filter,
diff --git a/web/tests/example3.test.js b/web/tests/example3.test.js
index bbc5360de1..b95a17d306 100644
--- a/web/tests/example3.test.js
+++ b/web/tests/example3.test.js
@@ -34,7 +34,7 @@ run_test("filter", () => {
     stream_data.add_sub(denmark_stream);
 
     const filter_terms = [
-        {operator: "stream", operand: "Denmark"},
+        {operator: "stream", operand: denmark_stream.stream_id.toString()},
         {operator: "topic", operand: "copenhagen"},
     ];
 
@@ -85,7 +85,7 @@ run_test("narrow_state", () => {
 
     // Now set up a Filter object.
     const filter_terms = [
-        {operator: "stream", operand: "Denmark"},
+        {operator: "stream", operand: denmark_stream.stream_id.toString()},
         {operator: "topic", operand: "copenhagen"},
     ];
 
diff --git a/web/tests/filter.test.js b/web/tests/filter.test.js
index ef7fd88ffd..a2f4feb9b5 100644
--- a/web/tests/filter.test.js
+++ b/web/tests/filter.test.js
@@ -75,6 +75,26 @@ function make_sub(name, stream_id) {
     stream_data.add_sub(sub);
 }
 
+let _stream_id = 0;
+function new_stream_id() {
+    _stream_id += 1;
+    return _stream_id;
+}
+
+const foo_stream_id = new_stream_id();
+const foo_sub = {
+    name: "Foo",
+    stream_id: foo_stream_id,
+};
+
+const general_sub = {
+    name: "general",
+    stream_id: new_stream_id(),
+};
+stream_data.add_sub(general_sub);
+
+const invalid_sub_id = new_stream_id();
+
 function test(label, f) {
     run_test(label, (helpers) => {
         stream_data.clear_subscriptions();
@@ -84,21 +104,20 @@ function test(label, f) {
 
 test("basics", () => {
     let terms = [
-        {operator: "channel", operand: "foo"},
-        {operator: "channel", operand: "exclude_me", negated: true},
+        {operator: "channel", operand: foo_stream_id.toString()},
+        {operator: "channel", operand: invalid_sub_id.toString(), negated: true}, // excluded
         {operator: "topic", operand: "bar"},
     ];
     let filter = new Filter(terms);
 
     assert_same_terms(filter.terms(), terms);
-    assert.deepEqual(filter.operands("channel"), ["foo"]);
+    assert.deepEqual(filter.operands("channel"), [foo_stream_id.toString()]);
 
     assert.ok(filter.has_operator("channel"));
     assert.ok(!filter.has_operator("search"));
 
-    assert.ok(filter.has_operand("channel", "foo"));
-    assert.ok(!filter.has_operand("channel", "exclude_me"));
-    assert.ok(!filter.has_operand("channel", "nada"));
+    assert.ok(filter.has_operand("channel", foo_stream_id.toString()));
+    assert.ok(!filter.has_operand("channel", invalid_sub_id.toString()));
 
     assert.ok(!filter.is_keyword_search());
     assert.ok(!filter.can_mark_messages_read());
@@ -114,27 +133,26 @@ test("basics", () => {
     assert.ok(filter.can_bucket_by("channel", "topic"));
 
     // "stream" was renamed to "channel"
-    terms = [{operator: "stream", operand: "foo"}];
+    terms = [{operator: "stream", operand: foo_stream_id.toString()}];
     assert.ok(filter.has_operator("channel"));
-    assert.deepEqual(filter.operands("channel"), ["foo"]);
+    assert.deepEqual(filter.operands("channel"), [foo_stream_id.toString()]);
     assert.ok(filter.includes_full_stream_history());
     assert.ok(filter.can_apply_locally());
 
     terms = [
-        {operator: "channel", operand: "foo"},
-        {operator: "channel", operand: "exclude_me", negated: true},
+        {operator: "channel", operand: foo_stream_id.toString()},
+        {operator: "channel", operand: invalid_sub_id.toString(), negated: true}, // excluded
         {operator: "topic", operand: "bar"},
     ];
     filter = new Filter(terms);
 
-    assert.deepEqual(filter.operands("channel"), ["foo"]);
+    assert.deepEqual(filter.operands("channel"), [foo_stream_id.toString()]);
 
     assert.ok(filter.has_operator("channel"));
     assert.ok(!filter.has_operator("search"));
 
-    assert.ok(filter.has_operand("channel", "foo"));
-    assert.ok(!filter.has_operand("channel", "exclude_me"));
-    assert.ok(!filter.has_operand("channel", "nada"));
+    assert.ok(filter.has_operand("channel", foo_stream_id.toString()));
+    assert.ok(!filter.has_operand("channel", invalid_sub_id.toString()));
 
     assert.ok(!filter.is_keyword_search());
     assert.ok(!filter.can_mark_messages_read());
@@ -147,7 +165,7 @@ test("basics", () => {
     assert.ok(!filter.is_conversation_view());
 
     terms = [
-        {operator: "channel", operand: "foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "topic", operand: "bar"},
         {operator: "search", operand: "pizza"},
     ];
@@ -165,7 +183,7 @@ test("basics", () => {
     assert.ok(!filter.is_conversation_view());
 
     terms = [
-        {operator: "channel", operand: "foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "topic", operand: "bar"},
         {operator: "near", operand: 17},
     ];
@@ -186,7 +204,7 @@ test("basics", () => {
     // If our only channel operator is negated, then for all intents and purposes,
     // we don't consider ourselves to have a channel operator, because we don't
     // want to have the channel in the tab bar or unsubscribe messaging, etc.
-    terms = [{operator: "channel", operand: "exclude", negated: true}];
+    terms = [{operator: "channel", operand: invalid_sub_id.toString(), negated: true}];
     filter = new Filter(terms);
     assert.ok(!filter.contains_only_private_messages());
     assert.ok(!filter.has_operator("channel"));
@@ -394,7 +412,7 @@ test("basics", () => {
     assert.ok(!filter.is_conversation_view());
 
     terms = [
-        {operator: "channel", operand: "foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "topic", operand: "bar"},
     ];
     filter = new Filter(terms);
@@ -411,7 +429,7 @@ test("basics", () => {
     assert.ok(!filter.is_conversation_view_with_near());
 
     terms = [
-        {operator: "channel", operand: "foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "topic", operand: "bar"},
         {operator: "with", operand: 17},
     ];
@@ -431,7 +449,7 @@ test("basics", () => {
 
     // "stream" was renamed to "channel"
     terms = [
-        {operator: "stream", operand: "foo"},
+        {operator: "stream", operand: foo_stream_id.toString()},
         {operator: "topic", operand: "bar"},
     ];
     filter = new Filter(terms);
@@ -545,19 +563,19 @@ test("can_mark_messages_read", () => {
     assert_not_mark_read_with_is_operands();
     assert_not_mark_read_when_searching();
 
-    const channel_term = [{operator: "channel", operand: "foo"}];
+    const channel_term = [{operator: "channel", operand: foo_stream_id.toString()}];
     let filter = new Filter(channel_term);
     assert.ok(filter.can_mark_messages_read());
     assert_not_mark_read_with_has_operands(channel_term);
     assert_not_mark_read_with_is_operands(channel_term);
     assert_not_mark_read_when_searching(channel_term);
 
-    const channel_negated_operator = [{operator: "channel", operand: "foo", negated: true}];
+    const channel_negated_operator = [{operator: "channel", operand: foo_stream_id, negated: true}];
     filter = new Filter(channel_negated_operator);
     assert.ok(!filter.can_mark_messages_read());
 
     const channel_topic_terms = [
-        {operator: "channel", operand: "foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "topic", operand: "bar"},
     ];
     filter = new Filter(channel_topic_terms);
@@ -567,7 +585,7 @@ test("can_mark_messages_read", () => {
     assert_not_mark_read_when_searching(channel_topic_terms);
 
     const channel_negated_topic_terms = [
-        {operator: "channel", operand: "foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "topic", operand: "bar", negated: true},
     ];
     filter = new Filter(channel_negated_topic_terms);
@@ -665,34 +683,34 @@ test("show_first_unread", () => {
 
 test("filter_with_new_params_topic", () => {
     const terms = [
-        {operator: "channel", operand: "foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "topic", operand: "old topic"},
     ];
     const filter = new Filter(terms);
 
-    assert.ok(filter.has_topic("foo", "old topic"));
-    assert.ok(!filter.has_topic("wrong", "old topic"));
-    assert.ok(!filter.has_topic("foo", "wrong"));
+    assert.ok(filter.has_topic(foo_stream_id.toString(), "old topic"));
+    assert.ok(!filter.has_topic(invalid_sub_id.toString(), "old topic"));
+    assert.ok(!filter.has_topic(foo_stream_id.toString(), "wrong"));
 
     const new_filter = filter.filter_with_new_params({
         operator: "topic",
         operand: "new topic",
     });
 
-    assert.deepEqual(new_filter.operands("channel"), ["foo"]);
+    assert.deepEqual(new_filter.operands("channel"), [foo_stream_id.toString()]);
     assert.deepEqual(new_filter.operands("topic"), ["new topic"]);
 });
 
 test("filter_with_new_params_channel", () => {
     const terms = [
-        {operator: "channel", operand: "foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "topic", operand: "old topic"},
     ];
     const filter = new Filter(terms);
 
-    assert.ok(filter.has_topic("foo", "old topic"));
-    assert.ok(!filter.has_topic("wrong", "old topic"));
-    assert.ok(!filter.has_topic("foo", "wrong"));
+    assert.ok(filter.has_topic(foo_stream_id.toString(), "old topic"));
+    assert.ok(!filter.has_topic(invalid_sub_id.toString(), "old topic"));
+    assert.ok(!filter.has_topic(foo_stream_id.toString(), "wrong"));
 
     const new_filter = filter.filter_with_new_params({
         operator: "channel",
@@ -706,33 +724,41 @@ test("filter_with_new_params_channel", () => {
 test("new_style_terms", () => {
     const term = {
         operator: "channel",
-        operand: "foo",
+        operand: foo_stream_id.toString(),
     };
     const terms = [term];
     const filter = new Filter(terms);
 
-    assert.deepEqual(filter.operands("channel"), ["foo"]);
+    assert.deepEqual(filter.operands("channel"), [foo_stream_id.toString()]);
     assert.ok(filter.can_bucket_by("channel"));
 });
 
-test("public_terms", ({override}) => {
+test("public_terms", ({override, override_rewire}) => {
     stream_data.clear_subscriptions();
+    const some_channel_id = new_stream_id();
     let terms = [
-        {operator: "channel", operand: "some_channel"},
+        {operator: "channel", operand: some_channel_id},
         {operator: "in", operand: "all"},
         {operator: "topic", operand: "bar"},
     ];
     let filter = new Filter(terms);
     const expected_terms = [
-        {operator: "channel", operand: "some_channel"},
+        {operator: "channel", operand: some_channel_id},
         {operator: "in", operand: "all"},
         {operator: "topic", operand: "bar"},
     ];
     override(page_params, "narrow_stream", undefined);
+    override_rewire(stream_data, "get_sub_by_name", (name) => {
+        assert.equal(name, "default");
+        return {
+            name,
+            some_channel_id,
+        };
+    });
     assert_same_terms(filter.public_terms(), expected_terms);
     assert.ok(filter.can_bucket_by("channel"));
 
-    terms = [{operator: "channel", operand: "default"}];
+    terms = [{operator: "channel", operand: some_channel_id}];
     filter = new Filter(terms);
     override(page_params, "narrow_stream", "default");
     assert_same_terms(filter.public_terms(), []);
@@ -845,34 +871,34 @@ test("predicate_basics", ({override}) => {
     // To keep these tests simple, we only pass objects with a few relevant attributes
     // rather than full-fledged message objects.
 
-    const stream_id = 42;
-    make_sub("Foo", stream_id);
+    stream_data.add_sub(foo_sub);
     let predicate = get_predicate([
-        ["channel", "Foo"],
+        ["channel", foo_stream_id.toString()],
         ["topic", "Bar"],
     ]);
 
-    assert.ok(predicate({type: stream_message, stream_id, topic: "bar"}));
-    assert.ok(!predicate({type: stream_message, stream_id, topic: "whatever"}));
+    assert.ok(predicate({type: stream_message, stream_id: foo_stream_id, topic: "bar"}));
+    assert.ok(!predicate({type: stream_message, stream_id: foo_stream_id, topic: "whatever"}));
     // 9999999 doesn't exist, testing no match
     assert.ok(!predicate({type: stream_message, stream_id: 9999999}));
     assert.ok(!predicate({type: direct_message}));
 
     // For old channels that we are no longer subscribed to, we may not have
     // a subscription, but these should still match by channel name.
+    const old_sub_id = new_stream_id();
     const old_sub = {
         name: "old-subscription",
-        stream_id: 5,
+        stream_id: old_sub_id,
         subscribed: false,
     };
     stream_data.add_sub(old_sub);
     predicate = get_predicate([
-        ["channel", "old-subscription"],
+        ["channel", old_sub_id.toString()],
         ["topic", "Bar"],
     ]);
-    assert.ok(predicate({type: stream_message, stream_id: 5, topic: "bar"}));
+    assert.ok(predicate({type: stream_message, stream_id: old_sub.stream_id, topic: "bar"}));
     // 99999 doesn't exist, testing no match
-    assert.ok(!predicate({type: stream_message, stream_id: 99999, topic: "whatever"}));
+    assert.ok(!predicate({type: stream_message, stream_id: invalid_sub_id, topic: "whatever"}));
 
     predicate = get_predicate([["search", "emoji"]]);
     assert.ok(predicate({}));
@@ -921,7 +947,7 @@ test("predicate_basics", ({override}) => {
     override(user_topics, "is_topic_followed", () => true);
     assert.ok(predicate({type: "stream", topic: "foo", stream_id: 5}));
 
-    const unknown_stream_id = 999;
+    const unknown_stream_id = new_stream_id();
     override(user_topics, "is_topic_muted", () => false);
     override(user_topics, "is_topic_unmuted_or_followed", () => false);
     predicate = get_predicate([["in", "home"]]);
@@ -931,7 +957,7 @@ test("predicate_basics", ({override}) => {
     // Muted topic is not part of in-home.
     with_overrides(({override}) => {
         override(user_topics, "is_topic_muted", () => true);
-        assert.ok(!predicate({stream_id, topic: "bar"}));
+        assert.ok(!predicate({stream_id: foo_stream_id, topic: "bar"}));
     });
 
     // Muted stream is not part of in-home.
@@ -1141,10 +1167,10 @@ test("negated_predicates", () => {
     let predicate;
     let narrow;
 
-    const social_stream_id = 555;
+    const social_stream_id = new_stream_id();
     make_sub("social", social_stream_id);
 
-    narrow = [{operator: "channel", operand: "social", negated: true}];
+    narrow = [{operator: "channel", operand: social_stream_id.toString(), negated: true}];
     predicate = new Filter(narrow).predicate();
     assert.ok(predicate({type: stream_message, stream_id: 999999}));
     assert.ok(!predicate({type: stream_message, stream_id: social_stream_id}));
@@ -1155,10 +1181,10 @@ test("negated_predicates", () => {
 });
 
 function test_mit_exceptions() {
-    const foo_stream_id = 555;
+    const foo_stream_id = new_stream_id();
     make_sub("Foo", foo_stream_id);
     let predicate = get_predicate([
-        ["channel", "Foo"],
+        ["channel", foo_stream_id.toString()],
         ["topic", "personal"],
     ]);
     assert.ok(predicate({type: stream_message, stream_id: foo_stream_id, topic: "personal"}));
@@ -1169,7 +1195,7 @@ function test_mit_exceptions() {
     assert.ok(!predicate({type: direct_message}));
 
     predicate = get_predicate([
-        ["channel", "Foo"],
+        ["channel", foo_stream_id.toString()],
         ["topic", "bar"],
     ]);
     assert.ok(predicate({type: stream_message, stream_id: foo_stream_id, topic: "bar.d"}));
@@ -1184,7 +1210,7 @@ function test_mit_exceptions() {
 
     // Try to get the MIT regex to explode for an empty topic.
     terms = [
-        {operator: "channel", operand: "foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "topic", operand: ""},
     ];
     predicate = new Filter(terms).predicate();
@@ -1216,10 +1242,10 @@ test("predicate_edge_cases", () => {
     assert.ok(!predicate({}));
 
     // Exercise caching feature.
-    const stream_id = 101;
+    const stream_id = new_stream_id();
     make_sub("Off topic", stream_id);
     const terms = [
-        {operator: "channel", operand: "Off topic"},
+        {operator: "channel", operand: stream_id.toString()},
         {operator: "topic", operand: "Mars"},
     ];
     const filter = new Filter(terms);
@@ -1237,18 +1263,27 @@ test("parse", () => {
         assert_same_terms(result, terms);
     }
 
-    string = "channel:Foo topic:Bar yo";
+    make_sub(foo_sub.name, foo_stream_id);
+    string = "channel:Foo topic:bar yo";
     terms = [
-        {operator: "channel", operand: "Foo"},
-        {operator: "topic", operand: "Bar"},
+        {operator: "channel", operand: foo_stream_id.toString()},
+        {operator: "topic", operand: "bar"},
+        {operator: "search", operand: "yo"},
+    ];
+    _test();
+
+    string = `channel:${foo_stream_id} topic:bar yo`;
+    terms = [
+        {operator: "channel", operand: foo_stream_id.toString()},
+        {operator: "topic", operand: "bar"},
         {operator: "search", operand: "yo"},
     ];
     _test();
 
     // "stream" was renamed to "channel"
-    string = "stream:Foo topic:Bar yo";
+    string = `stream:${foo_stream_id} topic:Bar yo`;
     terms = [
-        {operator: "channel", operand: "Foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "topic", operand: "Bar"},
         {operator: "search", operand: "yo"},
     ];
@@ -1262,41 +1297,21 @@ test("parse", () => {
     terms = [{operator: "sender", operand: "leo+test@zulip.com"}];
     _test();
 
-    string = "channel:With+Space";
-    terms = [{operator: "channel", operand: "With Space"}];
-    _test();
-
-    string = 'channel:"with quoted space" topic:and separate';
-    terms = [
-        {operator: "channel", operand: "with quoted space"},
-        {operator: "topic", operand: "and"},
-        {operator: "search", operand: "separate"},
-    ];
-    _test();
-
-    string = 'channel:"unclosed quote';
-    terms = [{operator: "channel", operand: "unclosed quote"}];
-    _test();
-
-    string = 'channel:""';
-    terms = [{operator: "channel", operand: ""}];
-    _test();
-
     string = "https://www.google.com";
     terms = [{operator: "search", operand: "https://www.google.com"}];
     _test();
 
-    string = "channel:foo -channel:exclude";
+    string = `channel:${foo_stream_id} -channel:${invalid_sub_id}`;
     terms = [
-        {operator: "channel", operand: "foo"},
-        {operator: "channel", operand: "exclude", negated: true},
+        {operator: "channel", operand: foo_stream_id.toString()},
+        {operator: "channel", operand: invalid_sub_id.toString(), negated: true},
     ];
     _test();
 
-    string = "text channel:foo more text";
+    string = `text channel:${foo_stream_id} more text`;
     terms = [
         {operator: "search", operand: "text"},
-        {operator: "channel", operand: "foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "search", operand: "more text"},
     ];
     _test();
@@ -1322,25 +1337,25 @@ test("parse", () => {
     terms = [{operator: "channels", operand: "public"}];
     _test();
 
-    string = "channel:foo :emoji: are cool";
+    string = `channel:${foo_stream_id} :emoji: are cool`;
     terms = [
-        {operator: "channel", operand: "foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "search", operand: ":emoji: are cool"},
     ];
     _test();
 
-    string = ":channel: channel:foo :emoji: are cool";
+    string = `:channel: channel:${foo_stream_id} :emoji: are cool`;
     terms = [
         {operator: "search", operand: ":channel:"},
-        {operator: "channel", operand: "foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "search", operand: ":emoji: are cool"},
     ];
     _test();
 
-    string = ":channel: channel:foo -:emoji: are cool";
+    string = `:channel: channel:${foo_stream_id} -:emoji: are cool`;
     terms = [
         {operator: "search", operand: ":channel:"},
-        {operator: "channel", operand: "foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "search", operand: "-:emoji: are cool"},
     ];
     _test();
@@ -1349,10 +1364,10 @@ test("parse", () => {
     terms = [];
     _test();
 
-    string = 'channel: separated topic: "with space"';
+    string = `channel: ${foo_stream_id} topic: "separated with space"`;
     terms = [
-        {operator: "channel", operand: "separated"},
-        {operator: "topic", operand: "with space"},
+        {operator: "channel", operand: foo_stream_id.toString()},
+        {operator: "topic", operand: "separated with space"},
     ];
     _test();
 });
@@ -1362,11 +1377,11 @@ test("unparse", () => {
     let terms;
 
     terms = [
-        {operator: "channel", operand: "Foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "topic", operand: "Bar", negated: true},
         {operator: "search", operand: "yo"},
     ];
-    string = "channel:Foo -topic:Bar yo";
+    string = `channel:${foo_stream_id} -topic:Bar yo`;
     assert.deepEqual(Filter.unparse(terms), string);
 
     terms = [
@@ -1400,10 +1415,10 @@ test("unparse", () => {
     // canonical version of the operator is
     // used in the unparsed search string
     terms = [
-        {operator: "stream", operand: "Foo"},
+        {operator: "stream", operand: foo_stream_id.toString()},
         {operator: "subject", operand: "Bar"},
     ];
-    string = "channel:Foo topic:Bar";
+    string = `channel:${foo_stream_id} topic:Bar`;
     assert.deepEqual(Filter.unparse(terms), string);
 });
 
@@ -1420,22 +1435,27 @@ test("describe", ({mock_template}) => {
     string = "exclude channels public";
     assert.equal(Filter.search_description_as_html(narrow), string);
 
+    const devel_id = new_stream_id();
+    make_sub("devel", devel_id);
+
     narrow = [
-        {operator: "channel", operand: "devel"},
+        {operator: "channel", operand: devel_id.toString()},
         {operator: "is", operand: "starred"},
     ];
     string = "channel devel, starred messages";
     assert.equal(Filter.search_description_as_html(narrow), string);
 
+    const river_id = new_stream_id();
+    make_sub("river", river_id);
     narrow = [
-        {operator: "channel", operand: "river"},
+        {operator: "channel", operand: river_id.toString()},
         {operator: "is", operand: "unread"},
     ];
     string = "channel river, unread messages";
     assert.equal(Filter.search_description_as_html(narrow), string);
 
     narrow = [
-        {operator: "channel", operand: "devel"},
+        {operator: "channel", operand: devel_id.toString()},
         {operator: "topic", operand: "JS"},
     ];
     string = "channel devel > JS";
@@ -1482,7 +1502,7 @@ test("describe", ({mock_template}) => {
     assert.equal(Filter.search_description_as_html(narrow), string);
 
     narrow = [
-        {operator: "channel", operand: "devel"},
+        {operator: "channel", operand: devel_id.toString()},
         {operator: "topic", operand: "JS", negated: true},
     ];
     string = "channel devel, exclude topic JS";
@@ -1496,14 +1516,14 @@ test("describe", ({mock_template}) => {
     assert.equal(Filter.search_description_as_html(narrow), string);
 
     narrow = [
-        {operator: "channel", operand: "devel"},
+        {operator: "channel", operand: devel_id.toString()},
         {operator: "is", operand: "starred", negated: true},
     ];
     string = "channel devel, exclude starred messages";
     assert.equal(Filter.search_description_as_html(narrow), string);
 
     narrow = [
-        {operator: "channel", operand: "devel"},
+        {operator: "channel", operand: devel_id.toString()},
         {operator: "has", operand: "image", negated: true},
     ];
     string = "channel devel, exclude messages with images";
@@ -1511,14 +1531,14 @@ test("describe", ({mock_template}) => {
 
     narrow = [
         {operator: "has", operand: "abc", negated: true},
-        {operator: "channel", operand: "devel"},
+        {operator: "channel", operand: devel_id.toString()},
     ];
     string = "invalid abc operand for has operator, channel devel";
     assert.equal(Filter.search_description_as_html(narrow), string);
 
     narrow = [
         {operator: "has", operand: "image", negated: true},
-        {operator: "channel", operand: "devel"},
+        {operator: "channel", operand: devel_id.toString()},
     ];
     string = "exclude messages with images, channel devel";
     assert.equal(Filter.search_description_as_html(narrow), string);
@@ -1529,7 +1549,7 @@ test("describe", ({mock_template}) => {
 
     // canonical version of the operator is used in description
     narrow = [
-        {operator: "stream", operand: "devel"},
+        {operator: "stream", operand: devel_id.toString()},
         {operator: "subject", operand: "JS", negated: true},
     ];
     string = "channel devel, exclude topic JS";
@@ -1537,7 +1557,9 @@ test("describe", ({mock_template}) => {
 });
 
 test("can_bucket_by", () => {
-    let terms = [{operator: "channel", operand: "My channel"}];
+    const channel_id = new_stream_id();
+    make_sub("My channel", channel_id);
+    let terms = [{operator: "channel", operand: channel_id.toString()}];
     let filter = new Filter(terms);
     assert.equal(filter.can_bucket_by("channel"), true);
     assert.equal(filter.can_bucket_by("channel", "topic"), false);
@@ -1546,7 +1568,7 @@ test("can_bucket_by", () => {
     terms = [
         // try a non-orthodox ordering
         {operator: "topic", operand: "My topic"},
-        {operator: "channel", operand: "My channel"},
+        {operator: "channel", operand: channel_id.toString()},
     ];
     filter = new Filter(terms);
     assert.equal(filter.can_bucket_by("channel"), true);
@@ -1554,7 +1576,7 @@ test("can_bucket_by", () => {
     assert.equal(filter.can_bucket_by("dm"), false);
 
     terms = [
-        {operator: "channel", operand: "My channel", negated: true},
+        {operator: "channel", operand: channel_id.toString(), negated: true},
         {operator: "topic", operand: "My topic"},
     ];
     filter = new Filter(terms);
@@ -1670,7 +1692,7 @@ test("term_type", () => {
     const terms = [
         {operator: "topic", operand: "lunch"},
         {operator: "sender", operand: "steve@foo.com"},
-        {operator: "channel", operand: "Verona"},
+        {operator: "channel", operand: new_stream_id().toString()},
     ];
     let filter = new Filter(terms);
     const term_types = filter.sorted_term_types();
@@ -1737,8 +1759,8 @@ test("is_valid_search_term", () => {
         ["in: nowhere", false],
         ["id: 4", true],
         ["near: home", false],
-        ["channel: Denmark", true],
-        ["channel: GhostTown", false],
+        ["channel: " + denmark.stream_id, true],
+        [`channel: ${invalid_sub_id}`, false],
         ["channels: public", true],
         ["channels: private", false],
         ["topic: GhostTown", true],
@@ -1778,9 +1800,25 @@ test("update_email", () => {
 });
 
 test("try_adjusting_for_moved_with_target", ({override}) => {
+    const scotland_id = new_stream_id();
+    make_sub("Scotland", scotland_id);
+    const verona_id = new_stream_id();
+    make_sub("Verona", verona_id);
     const messages = {
-        12: {type: "stream", display_recipient: "Scotland", topic: "Test 1", id: 12},
-        17: {type: "stream", display_recipient: "Verona", topic: "Test 2", id: 17},
+        12: {
+            type: "stream",
+            stream_id: scotland_id,
+            display_recipient: "Scotland",
+            topic: "Test 1",
+            id: 12,
+        },
+        17: {
+            type: "stream",
+            stream_id: verona_id,
+            display_recipient: "Verona",
+            topic: "Test 2",
+            id: 17,
+        },
         2: {type: "direct", id: 2, display_recipient: [{id: 3, email: "user3@zulip.com"}]},
     };
 
@@ -1788,7 +1826,7 @@ test("try_adjusting_for_moved_with_target", ({override}) => {
 
     // When the narrow terms are correct, it returns the same terms
     let terms = [
-        {operator: "channel", operand: "Scotland", negated: false},
+        {operator: "channel", operand: scotland_id.toString(), negated: false},
         {operator: "topic", operand: "Test 1", negated: false},
         {operator: "with", operand: "12", negated: false},
     ];
@@ -1802,7 +1840,7 @@ test("try_adjusting_for_moved_with_target", ({override}) => {
     // When the narrow terms are incorrect, the narrow is corrected
     // to the narrow of the `with` operand.
     const incorrect_terms = [
-        {operator: "channel", operand: "Verona", negated: false},
+        {operator: "channel", operand: verona_id.toString(), negated: false},
         {operator: "topic", operand: "Test 2", negated: false},
         {operator: "with", operand: "12", negated: false},
     ];
@@ -1816,7 +1854,7 @@ test("try_adjusting_for_moved_with_target", ({override}) => {
     // when message specified in `with` operator does not exist in
     // message_store, we rather go to the server, without any updates.
     terms = [
-        {operator: "channel", operand: "Scotland", negated: false},
+        {operator: "channel", operand: scotland_id.toString(), negated: false},
         {operator: "topic", operand: "Test 1", negated: false},
         {operator: "with", operand: "11", negated: false},
     ];
@@ -1830,7 +1868,7 @@ test("try_adjusting_for_moved_with_target", ({override}) => {
     // the `with` operator corresponds to that of a direct message, then
     // the narrow is adjusted to point to the narrow containing the message.
     terms = [
-        {operator: "channel", operand: "Scotland", negated: false},
+        {operator: "channel", operand: scotland_id.toString(), negated: false},
         {operator: "topic", operand: "Test 1", negated: false},
         {operator: "with", operand: "2", negated: false},
     ];
@@ -1852,7 +1890,7 @@ test("try_adjusting_for_moved_with_target", ({override}) => {
     filter.try_adjusting_for_moved_with_target();
     assert.deepEqual(filter.requires_adjustment_for_moved_with_target, false);
     assert.deepEqual(filter.terms(), [
-        {operator: "channel", operand: "Scotland", negated: false},
+        {operator: "channel", operand: scotland_id.toString(), negated: false},
         {operator: "topic", operand: "Test 1", negated: false},
         {operator: "with", operand: "12", negated: false},
     ]);
@@ -1861,7 +1899,7 @@ test("try_adjusting_for_moved_with_target", ({override}) => {
     // and is present in the same narrow as the original one, then
     // no hash change is required.
     terms = [
-        {operator: "channel", operand: "Verona", negated: false},
+        {operator: "channel", operand: verona_id.toString(), negated: false},
         {operator: "topic", operand: "Test 2", negated: false},
         {operator: "with", operand: "17", negated: false},
     ];
@@ -1873,7 +1911,7 @@ test("try_adjusting_for_moved_with_target", ({override}) => {
     // locally, but messages fetched are in same narrow as
     // original narrow, then no hash change is required.
     terms = [
-        {operator: "channel", operand: "Verona", negated: false},
+        {operator: "channel", operand: verona_id.toString(), negated: false},
         {operator: "topic", operand: "Test 2", negated: false},
         {operator: "with", operand: "1", negated: false},
     ];
@@ -1888,7 +1926,7 @@ test("try_adjusting_for_moved_with_target", ({override}) => {
     // and is not present in the same narrow as the original one,
     // then hash change is required.
     terms = [
-        {operator: "channel", operand: "Verona", negated: false},
+        {operator: "channel", operand: verona_id.toString(), negated: false},
         {operator: "topic", operand: "Test 2", negated: false},
         {operator: "with", operand: "12", negated: false},
     ];
@@ -1900,7 +1938,7 @@ test("try_adjusting_for_moved_with_target", ({override}) => {
     // locally, and messages fetched are in different narrow from
     // original narrow, then hash change is required.
     terms = [
-        {operator: "channel", operand: "Verona", negated: false},
+        {operator: "channel", operand: verona_id.toString(), negated: false},
         {operator: "topic", operand: "Test 2", negated: false},
         {operator: "with", operand: "1", negated: false},
     ];
@@ -1931,14 +1969,7 @@ function make_web_public_sub(name, stream_id) {
 }
 
 test("navbar_helpers", () => {
-    const sub = {
-        name: "Foo",
-        stream_id: 12,
-    };
-    stream_data.add_sub(sub);
-
-    const stream_id = 43;
-    make_sub("Foo", stream_id);
+    stream_data.add_sub(foo_sub);
 
     // make sure title has names separated with correct delimiters
     function properly_separated_names(names) {
@@ -2007,7 +2038,7 @@ test("navbar_helpers", () => {
     const is_followed = [{operator: "is", operand: "followed"}];
     const channels_public = [{operator: "channels", operand: "public"}];
     const channel_topic_terms = [
-        {operator: "channel", operand: "foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "topic", operand: "bar"},
     ];
     const has_reaction_sender_me = [
@@ -2015,16 +2046,19 @@ test("navbar_helpers", () => {
         {operator: "sender", operand: "me"},
     ];
     // foo channel exists
-    const channel_term = [{operator: "channel", operand: "foo"}];
-    make_private_sub("psub", "22");
-    const private_channel_term = [{operator: "channel", operand: "psub"}];
-    make_web_public_sub("webPublicSub", "12"); // capitalized just to try be tricky and robust.
-    const web_public_channel = [{operator: "channel", operand: "webPublicSub"}];
-    const non_existent_channel = [{operator: "channel", operand: "Elephant"}];
-    const non_existent_channel_topic = [
-        {operator: "channel", operand: "Elephant"},
-        {operator: "topic", operand: "pink"},
+    const channel_term = [{operator: "channel", operand: foo_stream_id.toString()}];
+    const invalid_channel_id = new_stream_id();
+    const invalid_channel = [{operator: "channel", operand: invalid_channel_id.toString()}];
+    const invalid_channel_with_topic = [
+        {operator: "channel", operand: invalid_channel_id.toString()},
+        {operator: "topic", operand: "bar"},
     ];
+    const public_sub_id = new_stream_id();
+    make_private_sub("psub", public_sub_id);
+    const private_channel_term = [{operator: "channel", operand: public_sub_id.toString()}];
+    const web_public_sub_id = new_stream_id();
+    make_web_public_sub("webPublicSub", web_public_sub_id); // capitalized just to try be tricky and robust.
+    const web_public_channel = [{operator: "channel", operand: web_public_sub_id.toString()}];
     const dm = [{operator: "dm", operand: "joe@example.com"}];
     const dm_with = [
         {operator: "dm", operand: "joe@example.com"},
@@ -2042,7 +2076,7 @@ test("navbar_helpers", () => {
     const is_alerted = [{operator: "is", operand: "alerted"}];
     const is_unread = [{operator: "is", operand: "unread"}];
     const channel_topic_near = [
-        {operator: "channel", operand: "foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "topic", operand: "bar"},
         {operator: "near", operand: "12"},
     ];
@@ -2051,7 +2085,7 @@ test("navbar_helpers", () => {
         {operator: "near", operand: "12"},
     ];
     const channel_with = [
-        {operator: "channel", operand: "foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "topic", operand: "bar"},
         {operator: "with", operand: "12"},
     ];
@@ -2131,7 +2165,14 @@ test("navbar_helpers", () => {
             is_common_narrow: true,
             zulip_icon: "hashtag",
             title: "Foo",
-            redirect_url_with_search: "/#narrow/stream/43-Foo/topic/bar",
+            redirect_url_with_search: `/#narrow/stream/${foo_stream_id}-Foo/topic/bar`,
+        },
+        {
+            terms: invalid_channel_with_topic,
+            is_common_narrow: true,
+            icon: "question-circle-o",
+            title: "translated: Unknown channel",
+            redirect_url_with_search: "#",
         },
         {
             terms: channels_public,
@@ -2145,20 +2186,13 @@ test("navbar_helpers", () => {
             is_common_narrow: true,
             zulip_icon: "hashtag",
             title: "Foo",
-            redirect_url_with_search: "/#narrow/stream/43-Foo",
+            redirect_url_with_search: `/#narrow/stream/${foo_stream_id}-Foo`,
         },
         {
-            terms: non_existent_channel,
+            terms: invalid_channel,
             is_common_narrow: true,
             icon: "question-circle-o",
-            title: "translated: Unknown channel #Elephant",
-            redirect_url_with_search: "#",
-        },
-        {
-            terms: non_existent_channel_topic,
-            is_common_narrow: true,
-            icon: "question-circle-o",
-            title: "translated: Unknown channel #Elephant",
+            title: "translated: Unknown channel",
             redirect_url_with_search: "#",
         },
         {
@@ -2166,14 +2200,14 @@ test("navbar_helpers", () => {
             is_common_narrow: true,
             zulip_icon: "lock",
             title: "psub",
-            redirect_url_with_search: "/#narrow/stream/22-psub",
+            redirect_url_with_search: `/#narrow/stream/${public_sub_id}-psub`,
         },
         {
             terms: web_public_channel,
             is_common_narrow: true,
             zulip_icon: "globe",
             title: "webPublicSub",
-            redirect_url_with_search: "/#narrow/stream/12-webPublicSub",
+            redirect_url_with_search: `/#narrow/stream/${web_public_sub_id}-webPublicSub`,
         },
         {
             terms: dm,
@@ -2300,7 +2334,7 @@ test("navbar_helpers", () => {
 
     // incomplete and weak test cases just to restore coverage of filter.ts
     const complex_term = [
-        {operator: "channel", operand: "foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "topic", operand: "bar"},
         {operator: "sender", operand: "me"},
     ];
@@ -2312,7 +2346,7 @@ test("navbar_helpers", () => {
     assert.equal(filter.is_common_narrow(), false);
 
     const channel_topic_search_term = [
-        {operator: "channel", operand: "foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "topic", operand: "bar"},
         {operator: "search", operand: "potato"},
     ];
@@ -2357,7 +2391,7 @@ test("navbar_helpers", () => {
     // this is actually wrong, but the code is currently not robust enough to throw an error here
     // also, used as an example of triggering last return statement.
     const default_redirect = {
-        terms: [{operator: "channel", operand: "foo"}],
+        terms: [{operator: "channel", operand: foo_stream_id.toString()}],
         redirect_url: "#",
     };
 
@@ -2392,10 +2426,15 @@ run_test("is_spectator_compatible", () => {
     assert.ok(
         !Filter.is_spectator_compatible([{operator: "dm-including", operand: "hamlet@zulip.com"}]),
     );
-    assert.ok(Filter.is_spectator_compatible([{operator: "channel", operand: "Denmark"}]));
+
+    const denmark_id = new_stream_id();
+    make_sub("Denmark", denmark_id);
+    assert.ok(
+        Filter.is_spectator_compatible([{operator: "channel", operand: denmark_id.toString()}]),
+    );
     assert.ok(
         Filter.is_spectator_compatible([
-            {operator: "channel", operand: "Denmark"},
+            {operator: "channel", operand: denmark_id.toString()},
             {operator: "topic", operand: "logic"},
         ]),
     );
@@ -2438,7 +2477,7 @@ run_test("is_in_home", () => {
 });
 
 run_test("equals", () => {
-    let terms = [{operator: "channel", operand: "Foo"}];
+    let terms = [{operator: "channel", operand: foo_stream_id.toString()}];
     let filter = new Filter(terms);
 
     assert.ok(filter.equals(new Filter(terms)));
@@ -2447,7 +2486,7 @@ run_test("equals", () => {
     assert.ok(!filter.equals(new Filter([...terms, {operator: "topic", operand: "Bar"}])));
 
     terms = [
-        {operator: "channel", operand: "Foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "topic", operand: "Bar"},
     ];
     filter = new Filter(terms);
@@ -2455,7 +2494,7 @@ run_test("equals", () => {
         filter.equals(
             new Filter([
                 {operator: "topic", operand: "Bar"},
-                {operator: "channel", operand: "Foo"},
+                {operator: "channel", operand: foo_stream_id.toString()},
             ]),
         ),
     );
@@ -2468,7 +2507,7 @@ run_test("equals", () => {
             new Filter([
                 {operator: "near", operand: "10"},
                 {operator: "topic", operand: "Bar"},
-                {operator: "channel", operand: "Foo"},
+                {operator: "channel", operand: foo_stream_id.toString()},
                 {operator: "near", operand: "101"},
             ]),
             ["near"],
@@ -2480,7 +2519,7 @@ run_test("adjusted_terms_if_moved", () => {
     current_user.email = me.email;
     // should return null for non-stream messages containing no
     // `with` operator
-    let raw_terms = [{operator: "channel", operand: "Foo"}];
+    let raw_terms = [{operator: "channel", operand: foo_stream_id.toString()}];
     let message = {type: "private"};
     let result = Filter.adjusted_terms_if_moved(raw_terms, message);
     assert.strictEqual(result, null);
@@ -2496,7 +2535,7 @@ run_test("adjusted_terms_if_moved", () => {
         ],
     };
     raw_terms = [
-        {operator: "channel", operand: "Foo"},
+        {operator: "channel", operand: foo_stream_id.toString()},
         {operator: "with", operand: `${message.id}`},
     ];
     result = Filter.adjusted_terms_if_moved(raw_terms, message);
@@ -2521,33 +2560,53 @@ run_test("adjusted_terms_if_moved", () => {
     ]);
 
     // should return null if no terms are changed
-    raw_terms = [{operator: "channel", operand: "general"}];
-    message = {type: "stream", display_recipient: "general", topic: "discussion"};
+    raw_terms = [{operator: "channel", operand: general_sub.stream_id.toString()}];
+    message = {
+        type: "stream",
+        stream_id: general_sub.stream_id,
+        display_recipient: "general",
+        topic: "discussion",
+    };
     result = Filter.adjusted_terms_if_moved(raw_terms, message);
     assert.strictEqual(result, null);
 
     // should adjust channel term to match message's display_recipient
-    raw_terms = [{operator: "channel", operand: "random"}];
-    message = {type: "stream", display_recipient: "general", topic: "discussion"};
-    let expected = [{operator: "channel", operand: "general"}];
+    raw_terms = [{operator: "channel", operand: "999"}];
+    message = {
+        type: "stream",
+        stream_id: general_sub.stream_id,
+        display_recipient: "general",
+        topic: "discussion",
+    };
+    let expected = [{operator: "channel", operand: general_sub.stream_id.toString()}];
     result = Filter.adjusted_terms_if_moved(raw_terms, message);
     assert.deepStrictEqual(result, expected);
 
     // should adjust topic term to match message's topic
     raw_terms = [{operator: "topic", operand: "random"}];
-    message = {type: "stream", display_recipient: "general", topic: "discussion"};
+    message = {
+        type: "stream",
+        stream_id: general_sub.stream_id,
+        display_recipient: "general",
+        topic: "discussion",
+    };
     expected = [{operator: "topic", operand: "discussion"}];
     result = Filter.adjusted_terms_if_moved(raw_terms, message);
     assert.deepStrictEqual(result, expected);
 
     // should adjust both channel and topic terms when both are different
     raw_terms = [
-        {operator: "channel", operand: "random"},
+        {operator: "channel", operand: "999"},
         {operator: "topic", operand: "random"},
     ];
-    message = {type: "stream", display_recipient: "general", topic: "discussion"};
+    message = {
+        type: "stream",
+        stream_id: general_sub.stream_id,
+        display_recipient: "general",
+        topic: "discussion",
+    };
     expected = [
-        {operator: "channel", operand: "general"},
+        {operator: "channel", operand: general_sub.stream_id.toString()},
         {operator: "topic", operand: "discussion"},
     ];
     result = Filter.adjusted_terms_if_moved(raw_terms, message);
@@ -2555,13 +2614,18 @@ run_test("adjusted_terms_if_moved", () => {
 
     // should not adjust terms that are not channel or topic
     raw_terms = [
-        {operator: "channel", operand: "random"},
+        {operator: "channel", operand: "999"},
         {operator: "topic", operand: "random"},
         {operator: "sender", operand: "alice"},
     ];
-    message = {type: "stream", display_recipient: "general", topic: "discussion"};
+    message = {
+        type: "stream",
+        stream_id: general_sub.stream_id,
+        display_recipient: "general",
+        topic: "discussion",
+    };
     expected = [
-        {operator: "channel", operand: "general"},
+        {operator: "channel", operand: general_sub.stream_id.toString()},
         {operator: "topic", operand: "discussion"},
         {operator: "sender", operand: "alice"},
     ];
@@ -2589,7 +2653,9 @@ run_test("can_newly_match_moved_messages", () => {
     filter = new Filter([{operator: "is", operand: "starred"}]);
     assert.deepEqual(filter.can_newly_match_moved_messages("general", "test"), false);
 
-    filter = new Filter([{negated: true, operator: "channel", operand: "general"}]);
+    filter = new Filter([
+        {negated: true, operator: "channel", operand: general_sub.stream_id.toString()},
+    ]);
     assert.deepEqual(filter.can_newly_match_moved_messages("something-else", "test"), true);
 
     filter = new Filter([{negated: true, operator: "is", operand: "followed"}]);
diff --git a/web/tests/hash_util.test.js b/web/tests/hash_util.test.js
index 4c3cdfea30..1b03a21187 100644
--- a/web/tests/hash_util.test.js
+++ b/web/tests/hash_util.test.js
@@ -21,8 +21,9 @@ const hamlet = {
 
 people.add_active_user(hamlet);
 
+const frontend_id = 99;
 const frontend = {
-    stream_id: 99,
+    stream_id: frontend_id,
     name: "frontend",
 };
 
@@ -44,7 +45,7 @@ run_test("hash_util", () => {
     encode_decode_operand(operator, operand, "15-Hamlet");
 
     operator = "stream";
-    operand = "frontend";
+    operand = frontend_id.toString();
 
     encode_decode_operand(operator, operand, "99-frontend");
 
@@ -175,19 +176,19 @@ run_test("test_is_in_specified_hash_category", () => {
 
 run_test("test_parse_narrow", () => {
     assert.deepEqual(hash_util.parse_narrow(["narrow", "stream", "99-frontend"]), [
-        {negated: false, operator: "stream", operand: "frontend"},
+        {negated: false, operator: "stream", operand: frontend_id.toString()},
     ]);
 
     assert.deepEqual(hash_util.parse_narrow(["narrow", "-stream", "99-frontend"]), [
-        {negated: true, operator: "stream", operand: "frontend"},
+        {negated: true, operator: "stream", operand: frontend_id.toString()},
     ]);
 
     assert.equal(hash_util.parse_narrow(["narrow", "BOGUS"]), undefined);
 
-    // For nonexistent streams, we get the full slug.
-    // We possibly should remove the prefix and fix this test.
+    // For nonexistent streams, we get an empty string. We don't use this
+    // anywhere, and just show "Invalid stream" in the navbar.
     assert.deepEqual(hash_util.parse_narrow(["narrow", "stream", "42-bogus"]), [
-        {negated: false, operator: "stream", operand: "42-bogus"},
+        {negated: false, operator: "stream", operand: ""},
     ]);
 });
 
diff --git a/web/tests/hashchange.test.js b/web/tests/hashchange.test.js
index b7a4382668..3c583fad9d 100644
--- a/web/tests/hashchange.test.js
+++ b/web/tests/hashchange.test.js
@@ -38,48 +38,60 @@ const message_view = zrequire("../src/message_view");
 const stream_data = zrequire("stream_data");
 const {Filter} = zrequire("../src/filter");
 
+const devel_id = 100;
+const devel = {
+    name: "devel",
+    stream_id: devel_id,
+    color: "blue",
+    subscribed: true,
+};
+stream_data.add_sub(devel);
+
 run_test("terms_round_trip", () => {
     let terms;
     let hash;
     let narrow;
 
     terms = [
-        {operator: "stream", operand: "devel"},
+        {operator: "stream", operand: devel_id.toString()},
         {operator: "topic", operand: "algol"},
     ];
     hash = hash_util.search_terms_to_hash(terms);
-    assert.equal(hash, "#narrow/stream/devel/topic/algol");
+    assert.equal(hash, "#narrow/stream/100-devel/topic/algol");
 
     narrow = hash_util.parse_narrow(hash.split("/"));
     assert.deepEqual(narrow, [
-        {operator: "stream", operand: "devel", negated: false},
+        {operator: "stream", operand: devel_id.toString(), negated: false},
         {operator: "topic", operand: "algol", negated: false},
     ]);
 
     terms = [
-        {operator: "stream", operand: "devel"},
+        {operator: "stream", operand: devel_id.toString()},
         {operator: "topic", operand: "visual c++", negated: true},
     ];
     hash = hash_util.search_terms_to_hash(terms);
-    assert.equal(hash, "#narrow/stream/devel/-topic/visual.20c.2B.2B");
+    assert.equal(hash, "#narrow/stream/100-devel/-topic/visual.20c.2B.2B");
 
     narrow = hash_util.parse_narrow(hash.split("/"));
     assert.deepEqual(narrow, [
-        {operator: "stream", operand: "devel", negated: false},
+        {operator: "stream", operand: devel_id.toString(), negated: false},
         {operator: "topic", operand: "visual c++", negated: true},
     ]);
 
     // test new encodings, where we have a stream id
+    const florida_id = 987;
     const florida_stream = {
         name: "Florida, USA",
-        stream_id: 987,
+        stream_id: florida_id,
     };
     stream_data.add_sub(florida_stream);
-    terms = [{operator: "stream", operand: "Florida, USA"}];
+    terms = [{operator: "stream", operand: florida_id.toString()}];
     hash = hash_util.search_terms_to_hash(terms);
     assert.equal(hash, "#narrow/stream/987-Florida.2C-USA");
     narrow = hash_util.parse_narrow(hash.split("/"));
-    assert.deepEqual(narrow, [{operator: "stream", operand: "Florida, USA", negated: false}]);
+    assert.deepEqual(narrow, [
+        {operator: "stream", operand: florida_id.toString(), negated: false},
+    ]);
 });
 
 run_test("stream_to_channel_rename", () => {
@@ -90,13 +102,15 @@ run_test("stream_to_channel_rename", () => {
 
     // Confirm the URLs generated from search terms use "stream" and "streams"
     // and that the new Filter has the new "channel" and "channels" operators.
-    terms = [{operator: "channel", operand: "devel"}];
+    terms = [{operator: "channel", operand: devel_id.toString()}];
     hash = hash_util.search_terms_to_hash(terms);
-    assert.equal(hash, "#narrow/stream/devel");
+    assert.equal(hash, "#narrow/stream/100-devel");
     narrow = hash_util.parse_narrow(hash.split("/"));
-    assert.deepEqual(narrow, [{operator: "stream", operand: "devel", negated: false}]);
+    assert.deepEqual(narrow, [{operator: "stream", operand: devel_id.toString(), negated: false}]);
     filter = new Filter(narrow);
-    assert.deepEqual(filter.terms(), [{operator: "channel", operand: "devel", negated: false}]);
+    assert.deepEqual(filter.terms(), [
+        {operator: "channel", operand: devel_id.toString(), negated: false},
+    ]);
 
     terms = [{operator: "channels", operand: "public"}];
     hash = hash_util.search_terms_to_hash(terms);
@@ -108,23 +122,28 @@ run_test("stream_to_channel_rename", () => {
 
     // Confirm that a narrow URL with "channel" and an enocoded stream/channel ID,
     // will be decoded correctly.
+    const test_stream_id = 34;
     const test_channel = {
         name: "decode",
-        stream_id: 34,
+        stream_id: test_stream_id,
     };
     stream_data.add_sub(test_channel);
     hash = "#narrow/channel/34-decode";
     narrow = hash_util.parse_narrow(hash.split("/"));
-    assert.deepEqual(narrow, [{operator: "channel", operand: "decode", negated: false}]);
+    assert.deepEqual(narrow, [
+        {operator: "channel", operand: test_stream_id.toString(), negated: false},
+    ]);
     filter = new Filter(narrow);
-    assert.deepEqual(filter.terms(), [{operator: "channel", operand: "decode", negated: false}]);
+    assert.deepEqual(filter.terms(), [
+        {operator: "channel", operand: test_stream_id.toString(), negated: false},
+    ]);
 });
 
 run_test("terms_trailing_slash", () => {
-    const hash = "#narrow/stream/devel/topic/algol/";
+    const hash = "#narrow/stream/100-devel/topic/algol/";
     const narrow = hash_util.parse_narrow(hash.split("/"));
     assert.deepEqual(narrow, [
-        {operator: "stream", operand: "devel", negated: false},
+        {operator: "stream", operand: devel_id.toString(), negated: false},
         {operator: "topic", operand: "algol", negated: false},
     ]);
 });
@@ -270,6 +289,12 @@ run_test("hash_interactions", ({override, override_rewire}) => {
     ]);
     assert.equal(window.location.hash, "#recent");
 
+    const denmark_id = 1;
+    stream_data.add_sub({
+        subscribed: true,
+        name: "Denmark",
+        stream_id: denmark_id,
+    });
     window.location.hash = "#narrow/stream/Denmark";
 
     helper.clear_events();
@@ -280,7 +305,7 @@ run_test("hash_interactions", ({override, override_rewire}) => {
         "message_view.show",
     ]);
     let terms = helper.get_narrow_terms();
-    assert.equal(terms[0].operand, "Denmark");
+    assert.equal(terms[0].operand, denmark_id.toString());
 
     window.location.hash = "#narrow";
 
diff --git a/web/tests/message_view.test.js b/web/tests/message_view.test.js
index 6311bd4e1f..2dcdf965ac 100644
--- a/web/tests/message_view.test.js
+++ b/web/tests/message_view.test.js
@@ -242,7 +242,7 @@ run_test("show_empty_narrow_message", ({mock_template}) => {
     );
 
     // for non-existent or private stream
-    set_filter([["stream", "Foo"]]);
+    set_filter([["stream", "999"]]);
     narrow_banner.show_empty_narrow_message();
     assert.equal(
         $(".empty_feed_notice_main").html(),
@@ -250,8 +250,9 @@ run_test("show_empty_narrow_message", ({mock_template}) => {
     );
 
     // for non-subbed public stream
-    stream_data.add_sub({name: "ROME", stream_id: 99});
-    set_filter([["stream", "Rome"]]);
+    const rome_id = 99;
+    stream_data.add_sub({name: "ROME", stream_id: rome_id});
+    set_filter([["stream", rome_id.toString()]]);
     narrow_banner.show_empty_narrow_message();
     assert.equal(
         $(".empty_feed_notice_main").html(),
@@ -263,7 +264,7 @@ run_test("show_empty_narrow_message", ({mock_template}) => {
 
     // for non-web-public stream for spectator
     page_params.is_spectator = true;
-    set_filter([["stream", "Rome"]]);
+    set_filter([["stream", rome_id.toString()]]);
     narrow_banner.show_empty_narrow_message();
     assert.equal(
         $(".empty_feed_notice_main").html(),
@@ -274,7 +275,7 @@ run_test("show_empty_narrow_message", ({mock_template}) => {
     );
 
     set_filter([
-        ["stream", "Rome"],
+        ["stream", rome_id.toString()],
         ["topic", "foo"],
     ]);
     narrow_banner.show_empty_narrow_message();
@@ -287,9 +288,10 @@ run_test("show_empty_narrow_message", ({mock_template}) => {
     );
 
     // for web-public stream for spectator
-    stream_data.add_sub({name: "web-public-stream", stream_id: 1231, is_web_public: true});
+    const web_public_id = 1231;
+    stream_data.add_sub({name: "web-public-stream", stream_id: web_public_id, is_web_public: true});
     set_filter([
-        ["stream", "web-public-stream"],
+        ["stream", web_public_id.toString()],
         ["topic", "foo"],
     ]);
     narrow_banner.show_empty_narrow_message();
@@ -524,7 +526,7 @@ run_test("show_empty_narrow_message", ({mock_template}) => {
 
     set_filter([
         ["sender", "alice@example.com"],
-        ["stream", "Rome"],
+        ["stream", rome_id.toString()],
     ]);
     narrow_banner.show_empty_narrow_message();
     assert.equal(
@@ -542,14 +544,15 @@ run_test("show_empty_narrow_message", ({mock_template}) => {
         ),
     );
 
+    const my_stream_id = 103;
     const my_stream = {
         name: "my stream",
-        stream_id: 103,
+        stream_id: my_stream_id,
     };
     stream_data.add_sub(my_stream);
     stream_data.subscribe_myself(my_stream);
 
-    set_filter([["stream", "my stream"]]);
+    set_filter([["stream", my_stream_id.toString()]]);
     narrow_banner.show_empty_narrow_message();
     assert.equal(
         $(".empty_feed_notice_main").html(),
@@ -617,6 +620,8 @@ run_test("show_search_stopwords", ({mock_template}) => {
         empty_narrow_html("translated: No search results.", undefined, expected_search_data),
     );
 
+    const streamA_id = 88;
+    stream_data.add_sub({name: "streamA", stream_id: streamA_id});
     const expected_stream_search_data = {
         has_stop_word: true,
         stream_query: "streamA",
@@ -627,7 +632,7 @@ run_test("show_search_stopwords", ({mock_template}) => {
         ],
     };
     set_filter([
-        ["stream", "streamA"],
+        ["stream", streamA_id.toString()],
         ["search", "what about grail"],
     ]);
     narrow_banner.show_empty_narrow_message();
@@ -647,7 +652,7 @@ run_test("show_search_stopwords", ({mock_template}) => {
         ],
     };
     set_filter([
-        ["stream", "streamA"],
+        ["stream", streamA_id.toString()],
         ["topic", "topicA"],
         ["search", "what about grail"],
     ]);
@@ -666,12 +671,14 @@ run_test("show_invalid_narrow_message", ({mock_template}) => {
     message_lists.set_current(undefined);
     mock_template("empty_feed_notice.hbs", true, (_data, html) => html);
 
-    stream_data.add_sub({name: "streamA", stream_id: 88});
-    stream_data.add_sub({name: "streamB", stream_id: 77});
+    const streamA_id = 88;
+    const streamB_id = 77;
+    stream_data.add_sub({name: "streamA", stream_id: streamA_id});
+    stream_data.add_sub({name: "streamB", stream_id: streamB_id});
 
     set_filter([
-        ["stream", "streamA"],
-        ["stream", "streamB"],
+        ["stream", streamA_id.toString()],
+        ["stream", streamB_id.toString()],
     ]);
     narrow_banner.show_empty_narrow_message();
     assert.equal(
@@ -734,7 +741,8 @@ run_test("narrow_to_compose_target streams", ({override_rewire}) => {
     });
 
     compose_state.set_message_type("stream");
-    stream_data.add_sub({name: "ROME", stream_id: 99});
+    const rome_id = 99;
+    stream_data.add_sub({name: "ROME", stream_id: rome_id});
     compose_state.set_stream_id(99);
 
     // Test with existing topic
@@ -744,7 +752,7 @@ run_test("narrow_to_compose_target streams", ({override_rewire}) => {
     assert.equal(args.called, true);
     assert.equal(args.opts.trigger, "narrow_to_compose_target");
     assert.deepEqual(args.terms, [
-        {operator: "channel", operand: "ROME"},
+        {operator: "channel", operand: rome_id.toString()},
         {operator: "topic", operand: "one"},
     ]);
 
@@ -754,7 +762,7 @@ run_test("narrow_to_compose_target streams", ({override_rewire}) => {
     message_view.to_compose_target();
     assert.equal(args.called, true);
     assert.deepEqual(args.terms, [
-        {operator: "channel", operand: "ROME"},
+        {operator: "channel", operand: rome_id.toString()},
         {operator: "topic", operand: "four"},
     ]);
 
@@ -763,14 +771,14 @@ run_test("narrow_to_compose_target streams", ({override_rewire}) => {
     args.called = false;
     message_view.to_compose_target();
     assert.equal(args.called, true);
-    assert.deepEqual(args.terms, [{operator: "channel", operand: "ROME"}]);
+    assert.deepEqual(args.terms, [{operator: "channel", operand: rome_id.toString()}]);
 
     // Test with no topic
     compose_state.topic(undefined);
     args.called = false;
     message_view.to_compose_target();
     assert.equal(args.called, true);
-    assert.deepEqual(args.terms, [{operator: "channel", operand: "ROME"}]);
+    assert.deepEqual(args.terms, [{operator: "channel", operand: rome_id.toString()}]);
 });
 
 run_test("narrow_to_compose_target direct messages", ({override, override_rewire}) => {
@@ -851,26 +859,24 @@ run_test("narrow_compute_title", () => {
     assert.equal(narrow_title.compute_narrow_title(filter), "translated: Messages sent by you");
 
     // Stream narrows
+    const foo_stream_id = 43;
     const sub = {
         name: "Foo",
-        stream_id: 43,
+        stream_id: foo_stream_id,
     };
     stream_data.add_sub(sub);
 
     filter = new Filter([
-        {operator: "stream", operand: "foo"},
+        {operator: "stream", operand: foo_stream_id.toString()},
         {operator: "topic", operand: "bar"},
     ]);
     assert.equal(narrow_title.compute_narrow_title(filter), "#Foo > bar");
 
-    filter = new Filter([{operator: "stream", operand: "foo"}]);
+    filter = new Filter([{operator: "stream", operand: foo_stream_id.toString()}]);
     assert.equal(narrow_title.compute_narrow_title(filter), "#Foo");
 
     filter = new Filter([{operator: "stream", operand: "Elephant"}]);
-    assert.equal(
-        narrow_title.compute_narrow_title(filter),
-        "translated: Unknown channel #Elephant",
-    );
+    assert.equal(narrow_title.compute_narrow_title(filter), "translated: Unknown channel");
 
     // Direct messages with narrows
     const joe = {
diff --git a/web/tests/narrow_activate.test.js b/web/tests/narrow_activate.test.js
index 3a26d262c9..06f7889734 100644
--- a/web/tests/narrow_activate.test.js
+++ b/web/tests/narrow_activate.test.js
@@ -171,7 +171,7 @@ run_test("basics", ({override, override_rewire}) => {
     override_rewire(message_view, "try_rendering_locally_for_same_narrow", noop);
 
     const helper = test_helper({override});
-    const terms = [{operator: "stream", operand: "Denmark"}];
+    const terms = [{operator: "stream", operand: denmark.stream_id.toString()}];
 
     const selected_id = 1000;
 
diff --git a/web/tests/narrow_state.test.js b/web/tests/narrow_state.test.js
index cab6a9f9c8..101b3591a5 100644
--- a/web/tests/narrow_state.test.js
+++ b/web/tests/narrow_state.test.js
@@ -40,26 +40,27 @@ test("stream", () => {
     assert.ok(!narrow_state.filter());
     assert.equal(narrow_state.stream_id(), undefined);
 
-    const test_stream = {name: "Test", stream_id: 15};
+    const test_stream_id = 15;
+    const test_stream = {name: "Test", stream_id: test_stream_id};
     stream_data.add_sub(test_stream);
 
     assert.ok(!narrow_state.is_for_stream_id(test_stream.stream_id));
 
     set_filter([
-        ["stream", "Test"],
+        ["stream", test_stream_id.toString()],
         ["topic", "Bar"],
         ["search", "yo"],
     ]);
     assert.ok(narrow_state.filter());
 
     assert.equal(narrow_state.stream_name(), "Test");
-    assert.equal(narrow_state.stream_id(), 15);
+    assert.equal(narrow_state.stream_id(), test_stream_id);
     assert.equal(narrow_state.stream_sub().stream_id, test_stream.stream_id);
     assert.equal(narrow_state.topic(), "Bar");
     assert.ok(narrow_state.is_for_stream_id(test_stream.stream_id));
 
     const expected_terms = [
-        {negated: false, operator: "channel", operand: "Test"},
+        {negated: false, operator: "channel", operand: test_stream_id.toString()},
         {negated: false, operator: "topic", operand: "Bar"},
         {negated: false, operator: "search", operand: "yo"},
     ];
@@ -68,6 +69,8 @@ test("stream", () => {
     assert.deepEqual(public_terms, expected_terms);
 });
 
+const foo_stream_id = 72;
+const foo_stream = {name: "Foo", stream_id: foo_stream_id};
 test("narrowed", () => {
     assert.ok(!narrow_state.narrowed_to_pms());
     assert.ok(!narrow_state.narrowed_by_reply());
@@ -76,6 +79,8 @@ test("narrowed", () => {
     assert.ok(!narrow_state.narrowed_by_stream_reply());
     assert.equal(narrow_state.stream_sub(), undefined);
 
+    stream_data.add_sub(foo_stream);
+
     set_filter([["stream", "Foo"]]);
     assert.ok(!narrow_state.narrowed_to_pms());
     assert.ok(!narrow_state.narrowed_by_reply());
@@ -91,7 +96,7 @@ test("narrowed", () => {
     assert.ok(!narrow_state.narrowed_by_stream_reply());
 
     set_filter([
-        ["stream", "Foo"],
+        ["stream", foo_stream_id.toString()],
         ["topic", "bar"],
     ]);
     assert.ok(!narrow_state.narrowed_to_pms());
@@ -117,14 +122,14 @@ test("narrowed", () => {
 
 test("terms", () => {
     set_filter([
-        ["stream", "Foo"],
+        ["stream", foo_stream_id.toString()],
         ["topic", "Bar"],
         ["search", "Yo"],
     ]);
     let result = narrow_state.search_terms();
     assert.equal(result.length, 3);
     assert.equal(result[0].operator, "channel");
-    assert.equal(result[0].operand, "Foo");
+    assert.equal(result[0].operand, foo_stream_id.toString());
 
     assert.equal(result[1].operator, "topic");
     assert.equal(result[1].operand, "Bar");
@@ -136,11 +141,11 @@ test("terms", () => {
     result = narrow_state.search_terms();
     assert.equal(result.length, 0);
 
-    page_params.narrow = [{operator: "stream", operand: "Foo"}];
+    page_params.narrow = [{operator: "stream", operand: foo_stream_id.toString()}];
     result = narrow_state.search_terms();
     assert.equal(result.length, 1);
     assert.equal(result[0].operator, "channel");
-    assert.equal(result[0].operand, "Foo");
+    assert.equal(result[0].operand, foo_stream_id.toString());
 });
 
 test("excludes_muted_topics", () => {
@@ -169,7 +174,7 @@ test("excludes_muted_topics", () => {
 
 test("set_compose_defaults", () => {
     set_filter([
-        ["stream", "Foo"],
+        ["stream", foo_stream_id.toString()],
         ["topic", "Bar"],
     ]);
 
@@ -178,10 +183,9 @@ test("set_compose_defaults", () => {
     assert.equal(stream_and_topic.stream_id, undefined);
     assert.equal(stream_and_topic.topic, "Bar");
 
-    const test_stream = {name: "Foo", stream_id: 72};
-    stream_data.add_sub(test_stream);
+    stream_data.add_sub(foo_stream);
     stream_and_topic = narrow_state.set_compose_defaults();
-    assert.equal(stream_and_topic.stream_id, 72);
+    assert.equal(stream_and_topic.stream_id, foo_stream_id);
     assert.equal(stream_and_topic.topic, "Bar");
 
     set_filter([["dm", "foo@bar.com"]]);
@@ -212,11 +216,12 @@ test("set_compose_defaults", () => {
     ]);
     assert.deepEqual(narrow_state.set_compose_defaults(), {});
 
-    stream_data.add_sub({name: "ROME", stream_id: 99});
-    set_filter([["stream", "rome"]]);
+    const rome_id = 99;
+    stream_data.add_sub({name: "ROME", stream_id: rome_id});
+    set_filter([["stream", rome_id.toString()]]);
 
     const stream_test = narrow_state.set_compose_defaults();
-    assert.equal(stream_test.stream_id, 99);
+    assert.equal(stream_test.stream_id, rome_id);
 });
 
 test("update_email", () => {
@@ -241,7 +246,7 @@ test("update_email", () => {
 
 test("topic", () => {
     set_filter([
-        ["stream", "Foo"],
+        ["stream", foo_stream.stream_id.toString()],
         ["topic", "Bar"],
     ]);
     assert.equal(narrow_state.topic(), "Bar");
@@ -271,14 +276,15 @@ test("stream_sub", () => {
     assert.equal(narrow_state.stream_sub(), undefined);
 
     set_filter([
-        ["stream", "Foo"],
+        ["stream", "55"],
         ["topic", "Bar"],
     ]);
-    assert.equal(narrow_state.stream_name(), "Foo");
+    assert.equal(narrow_state.stream_name(), undefined);
     assert.equal(narrow_state.stream_sub(), undefined);
 
     const sub = {name: "Foo", stream_id: 55};
     stream_data.add_sub(sub);
+    assert.equal(narrow_state.stream_name(), "Foo");
     assert.deepEqual(narrow_state.stream_sub(), sub);
 
     set_filter([
@@ -297,7 +303,7 @@ test("pm_ids_string", () => {
     assert.deepStrictEqual(narrow_state.pm_ids_set(), new Set());
 
     set_filter([
-        ["stream", "Foo"],
+        ["stream", foo_stream.stream_id.toString()],
         ["topic", "Bar"],
     ]);
     assert.equal(narrow_state.pm_ids_string(), undefined);
diff --git a/web/tests/narrow_unread.test.js b/web/tests/narrow_unread.test.js
index 98902d2365..7e1de5f8e2 100644
--- a/web/tests/narrow_unread.test.js
+++ b/web/tests/narrow_unread.test.js
@@ -25,6 +25,8 @@ const alice = {
     full_name: "Alice",
 };
 
+const bogus_stream_id = "999999";
+
 people.init();
 people.add_active_user(alice);
 
@@ -111,12 +113,12 @@ run_test("get_unread_ids", () => {
     assert.deepEqual(unread_ids, []);
     assert_unread_info({flavor: "not_found"});
 
-    terms = [{operator: "stream", operand: "bogus"}];
+    terms = [{operator: "stream", operand: bogus_stream_id}];
     set_filter(terms);
     unread_ids = candidate_ids();
     assert.deepEqual(unread_ids, []);
 
-    terms = [{operator: "stream", operand: sub.name}];
+    terms = [{operator: "stream", operand: sub.stream_id.toString()}];
     set_filter(terms);
     unread_ids = candidate_ids();
     assert.deepEqual(unread_ids, []);
@@ -131,7 +133,7 @@ run_test("get_unread_ids", () => {
     });
 
     terms = [
-        {operator: "stream", operand: "bogus"},
+        {operator: "stream", operand: bogus_stream_id},
         {operator: "topic", operand: "my topic"},
     ];
     set_filter(terms);
@@ -139,7 +141,7 @@ run_test("get_unread_ids", () => {
     assert.deepEqual(unread_ids, []);
 
     terms = [
-        {operator: "stream", operand: sub.name},
+        {operator: "stream", operand: sub.stream_id.toString()},
         {operator: "topic", operand: "my topic"},
     ];
     set_filter(terms);
@@ -226,7 +228,7 @@ run_test("get_unread_ids", () => {
     // destination topic.
     unread.process_loaded_messages([other_topic_message]);
     terms = [
-        {operator: "channel", operand: sub.name},
+        {operator: "channel", operand: sub.stream_id.toString()},
         {operator: "topic", operand: "another topic"},
     ];
     set_filter(terms);
@@ -234,7 +236,7 @@ run_test("get_unread_ids", () => {
     assert.deepEqual(unread_ids, [other_topic_message.id]);
 
     terms = [
-        {operator: "channel", operand: sub.name},
+        {operator: "channel", operand: sub.stream_id.toString()},
         {operator: "topic", operand: "another topic"},
         {operator: "with", operand: stream_msg.id},
     ];
@@ -243,7 +245,7 @@ run_test("get_unread_ids", () => {
     assert.deepEqual(unread_ids, [stream_msg.id]);
 
     terms = [
-        {operator: "channel", operand: sub.name},
+        {operator: "channel", operand: sub.stream_id.toString()},
         {operator: "topic", operand: "another topic"},
         {operator: "with", operand: private_msg.id},
     ];
diff --git a/web/tests/peer_data.test.js b/web/tests/peer_data.test.js
index 7324f4e965..dbf2034525 100644
--- a/web/tests/peer_data.test.js
+++ b/web/tests/peer_data.test.js
@@ -66,19 +66,19 @@ test("unsubscribe", () => {
     stream_data.add_sub(devel);
 
     // verify clean slate
-    assert.ok(!stream_data.is_subscribed_by_name("devel"));
+    assert.ok(!stream_data.is_subscribed(devel.stream_id));
 
     // set up our subscription
     devel.subscribed = true;
     peer_data.set_subscribers(devel.stream_id, [me.user_id]);
 
     // ensure our setup is accurate
-    assert.ok(stream_data.is_subscribed_by_name("devel"));
+    assert.ok(stream_data.is_subscribed(devel.stream_id));
 
     // DO THE UNSUBSCRIBE HERE
     stream_data.unsubscribe_myself(devel);
     assert.ok(!devel.subscribed);
-    assert.ok(!stream_data.is_subscribed_by_name("devel"));
+    assert.ok(!stream_data.is_subscribed(devel.stream_id));
     assert.ok(!contains_sub(stream_data.subscribed_subs(), devel));
     assert.ok(contains_sub(stream_data.unsubscribed_subs(), devel));
 
@@ -96,7 +96,7 @@ test("subscribers", () => {
     people.add_active_user(george);
 
     // verify setup
-    assert.ok(stream_data.is_subscribed_by_name(sub.name));
+    assert.ok(stream_data.is_subscribed(sub.stream_id));
 
     const stream_id = sub.stream_id;
 
diff --git a/web/tests/search.test.js b/web/tests/search.test.js
index d8b814eaa8..a5352b42a0 100644
--- a/web/tests/search.test.js
+++ b/web/tests/search.test.js
@@ -237,25 +237,26 @@ run_test("initialize", ({override, override_rewire, mock_template}) => {
             assert.equal(opts.updater("ver"), "ver");
             assert.ok(!input_pill_displayed);
 
+            const verona_stream_id = verona.stream_id.toString();
             terms = [
                 {
                     negated: false,
                     operator: "channel",
-                    operand: "Verona",
+                    operand: verona_stream_id,
                 },
             ];
             expected_pill_display_value = "channel: Verona";
             _setup(terms);
             input_pill_displayed = false;
             mock_pill_removes(search.search_pill_widget);
-            assert.equal(opts.updater("channel:Verona"), "");
+            assert.equal(opts.updater(`channel:${verona_stream_id}`), "");
             assert.ok(input_pill_displayed);
 
             search.__Rewire__("is_using_input_method", true);
             _setup(terms);
             input_pill_displayed = false;
             mock_pill_removes(search.search_pill_widget);
-            assert.equal(opts.updater("channel:Verona"), "");
+            assert.equal(opts.updater(`channel:${verona_stream_id}`), "");
             assert.ok(input_pill_displayed);
         }
         return {
diff --git a/web/tests/search_suggestion.test.js b/web/tests/search_suggestion.test.js
index ca53129239..5ff8f6bcf2 100644
--- a/web/tests/search_suggestion.test.js
+++ b/web/tests/search_suggestion.test.js
@@ -50,6 +50,12 @@ const jeff = {
 
 const example_avatar_url = "http://example.com/example.png";
 
+let _stream_id = 0;
+function new_stream_id() {
+    _stream_id += 1;
+    return _stream_id;
+}
+
 function init() {
     current_user.is_admin = true;
 
@@ -81,7 +87,7 @@ function test(label, f) {
 test("basic_get_suggestions", ({override}) => {
     const query = "fred";
 
-    override(narrow_state, "stream_name", () => "office");
+    override(narrow_state, "stream_id", noop);
 
     const suggestions = get_suggestions(query);
 
@@ -116,14 +122,17 @@ test("get_is_suggestions_for_spectator", () => {
 test("subset_suggestions", ({mock_template}) => {
     mock_template("search_description.hbs", true, (_data, html) => html);
 
-    const query = "channel:Denmark topic:Hamlet shakespeare";
+    const denmark_id = new_stream_id();
+    const sub = {name: "Denmark", stream_id: denmark_id};
+    stream_data.add_sub(sub);
+    const query = `channel:${denmark_id} topic:Hamlet shakespeare`;
 
     const suggestions = get_suggestions(query);
 
     const expected = [
-        "channel:Denmark topic:Hamlet shakespeare",
-        "channel:Denmark topic:Hamlet",
-        "channel:Denmark",
+        `channel:${denmark_id} topic:Hamlet shakespeare`,
+        `channel:${denmark_id} topic:Hamlet`,
+        `channel:${denmark_id}`,
     ];
 
     assert.deepEqual(suggestions.strings, expected);
@@ -249,7 +258,7 @@ test("dm_suggestions", ({override, mock_template}) => {
 
     // "pm-with" operator returns search result
     // and "dm" operator as a suggestions
-    override(narrow_state, "stream_name", () => undefined);
+    override(narrow_state, "stream_id", () => undefined);
     query = "pm-with";
     suggestions = get_suggestions(query);
     expected = ["pm-with", "dm:"];
@@ -330,10 +339,10 @@ test("group_suggestions", ({mock_template}) => {
     assert.deepEqual(suggestions.strings, expected);
 
     // Doesn't show dms because it's invalid in combination
-    // with a channel.
-    query = "channel:Denmark has:link dm:bob@zulip.com,Smit";
+    // with a channel. (Random channel id.)
+    query = "channel:66 has:link dm:bob@zulip.com,Smit";
     suggestions = get_suggestions(query);
-    expected = ["channel:Denmark has:link", "channel:Denmark"];
+    expected = ["channel:66 has:link", "channel:66"];
     assert.deepEqual(suggestions.strings, expected);
 
     // Invalid emails don't give suggestions
@@ -346,8 +355,10 @@ test("group_suggestions", ({mock_template}) => {
 test("empty_query_suggestions", () => {
     const query = "";
 
-    stream_data.add_sub({stream_id: 44, name: "devel", subscribed: true});
-    stream_data.add_sub({stream_id: 77, name: "office", subscribed: true});
+    const devel_id = new_stream_id();
+    const office_id = new_stream_id();
+    stream_data.add_sub({stream_id: devel_id, name: "devel", subscribed: true});
+    stream_data.add_sub({stream_id: office_id, name: "office", subscribed: true});
 
     const suggestions = get_suggestions(query);
 
@@ -361,8 +372,8 @@ test("empty_query_suggestions", () => {
         "is:unread",
         "is:resolved",
         "sender:myself@zulip.com",
-        "channel:devel",
-        "channel:office",
+        `channel:${devel_id}`,
+        `channel:${office_id}`,
         "has:link",
         "has:image",
         "has:attachment",
@@ -395,7 +406,7 @@ test("has_suggestions", ({override, mock_template}) => {
     let query = "h";
     stream_data.add_sub({stream_id: 44, name: "devel", subscribed: true});
     stream_data.add_sub({stream_id: 77, name: "office", subscribed: true});
-    override(narrow_state, "stream_name", noop);
+    override(narrow_state, "stream_id", noop);
 
     let suggestions = get_suggestions(query);
     let expected = ["h", "has:link", "has:image", "has:attachment", "has:reaction"];
@@ -439,13 +450,10 @@ test("has_suggestions", ({override, mock_template}) => {
     expected = ["att", "has:attachment"];
     assert.deepEqual(suggestions.strings, expected);
 
-    query = "channel:Denmark is:alerted has:lin";
+    // 66 is misc channel id.
+    query = "channel:66 is:alerted has:lin";
     suggestions = get_suggestions(query);
-    expected = [
-        "channel:Denmark is:alerted has:link",
-        "channel:Denmark is:alerted",
-        "channel:Denmark",
-    ];
+    expected = ["channel:66 is:alerted has:link", "channel:66 is:alerted", "channel:66"];
     assert.deepEqual(suggestions.strings, expected);
 });
 
@@ -453,9 +461,7 @@ test("check_is_suggestions", ({override, mock_template}) => {
     mock_template("search_description.hbs", true, (_data, html) => html);
     mock_template("user_pill.hbs", true, (_data, html) => html);
 
-    stream_data.add_sub({stream_id: 44, name: "devel", subscribed: true});
-    stream_data.add_sub({stream_id: 77, name: "office", subscribed: true});
-    override(narrow_state, "stream_name", noop);
+    override(narrow_state, "stream_id", noop);
 
     let query = "i";
     let suggestions = get_suggestions(query);
@@ -541,20 +547,16 @@ test("check_is_suggestions", ({override, mock_template}) => {
     expected = ["st", "streams:public", "is:starred", "channel:"];
     assert.deepEqual(suggestions.strings, expected);
 
-    query = "channel:Denmark has:link is:sta";
+    query = "channel:66 has:link is:sta";
     suggestions = get_suggestions(query);
-    expected = [
-        "channel:Denmark has:link is:starred",
-        "channel:Denmark has:link",
-        "channel:Denmark",
-    ];
+    expected = ["channel:66 has:link is:starred", "channel:66 has:link", "channel:66"];
     assert.deepEqual(suggestions.strings, expected);
 });
 
 test("sent_by_me_suggestions", ({override, mock_template}) => {
     mock_template("search_description.hbs", true, (_data, html) => html);
 
-    override(narrow_state, "stream_name", noop);
+    override(narrow_state, "stream_id", noop);
 
     let query = "";
     let suggestions = get_suggestions(query);
@@ -604,13 +606,16 @@ test("sent_by_me_suggestions", ({override, mock_template}) => {
     expected = ["-sent", "-sender:myself@zulip.com"];
     assert.deepEqual(suggestions.strings, expected);
 
-    query = "channel:Denmark topic:Denmark1 sent";
+    const denmark_id = new_stream_id();
+    const sub = {name: "Denmark", stream_id: denmark_id};
+    stream_data.add_sub(sub);
+    query = `channel:${denmark_id} topic:Denmark1 sent`;
     suggestions = get_suggestions(query);
     expected = [
-        "channel:Denmark topic:Denmark1 sent",
-        "channel:Denmark topic:Denmark1 sender:myself@zulip.com",
-        "channel:Denmark topic:Denmark1",
-        "channel:Denmark",
+        `channel:${denmark_id} topic:Denmark1 sent`,
+        `channel:${denmark_id} topic:Denmark1 sender:myself@zulip.com`,
+        `channel:${denmark_id} topic:Denmark1`,
+        `channel:${denmark_id}`,
     ];
     assert.deepEqual(suggestions.strings, expected);
 
@@ -627,11 +632,11 @@ test("topic_suggestions", ({override, mock_template}) => {
     let expected;
 
     override(stream_topic_history_util, "get_server_history", noop);
-    stream_data.add_sub({stream_id: 77, name: "office", subscribed: true});
-    override(narrow_state, "stream_name", () => "office");
+    const office_id = new_stream_id();
+    stream_data.add_sub({stream_id: office_id, name: "office", subscribed: true});
+    override(narrow_state, "stream_id", () => office_id);
 
-    const devel_id = 44;
-    const office_id = 77;
+    const devel_id = new_stream_id();
     stream_data.add_sub({stream_id: devel_id, name: "devel", subscribed: true});
     stream_data.add_sub({stream_id: office_id, name: "office", subscribed: true});
 
@@ -657,8 +662,8 @@ test("topic_suggestions", ({override, mock_template}) => {
         "dm:ted@zulip.com",
         "sender:ted@zulip.com",
         "dm-including:ted@zulip.com",
-        "channel:office topic:team",
-        "channel:office topic:test",
+        `channel:${office_id} topic:team`,
+        `channel:${office_id} topic:test`,
     ];
     assert.deepEqual(suggestions.strings, expected);
 
@@ -666,40 +671,56 @@ test("topic_suggestions", ({override, mock_template}) => {
         return suggestions.lookup_table.get(q).description_html;
     }
     assert.equal(describe("te"), "Search for te");
-    assert.equal(describe("channel:office topic:team"), "Channel office > team");
+    assert.equal(describe(`channel:${office_id} topic:team`), "Channel office > team");
 
-    suggestions = get_suggestions("topic:staplers channel:office");
-    expected = ["topic:staplers channel:office", "topic:staplers"];
+    suggestions = get_suggestions(`topic:staplers channel:${office_id}`);
+    expected = [`topic:staplers channel:${office_id}`, "topic:staplers"];
     assert.deepEqual(suggestions.strings, expected);
 
-    suggestions = get_suggestions("channel:devel topic:");
-    expected = ["channel:devel topic:", "channel:devel topic:REXX", "channel:devel"];
+    suggestions = get_suggestions(`channel:${devel_id} topic:`);
+    expected = [
+        `channel:${devel_id} topic:`,
+        `channel:${devel_id} topic:REXX`,
+        `channel:${devel_id}`,
+    ];
     assert.deepEqual(suggestions.strings, expected);
 
-    suggestions = get_suggestions("channel:devel -topic:");
-    expected = ["channel:devel -topic:", "channel:devel -topic:REXX", "channel:devel"];
+    suggestions = get_suggestions(`channel:${devel_id} -topic:`);
+    expected = [
+        `channel:${devel_id} -topic:`,
+        `channel:${devel_id} -topic:REXX`,
+        `channel:${devel_id}`,
+    ];
     assert.deepEqual(suggestions.strings, expected);
 
     suggestions = get_suggestions("-topic:te");
-    expected = ["-topic:te", "channel:office -topic:team", "channel:office -topic:test"];
+    expected = [
+        "-topic:te",
+        `channel:${office_id} -topic:team`,
+        `channel:${office_id} -topic:test`,
+    ];
     assert.deepEqual(suggestions.strings, expected);
 
-    suggestions = get_suggestions("is:alerted channel:devel is:starred topic:");
+    suggestions = get_suggestions(`is:alerted channel:${devel_id} is:starred topic:`);
     expected = [
-        "is:alerted channel:devel is:starred topic:",
-        "is:alerted channel:devel is:starred topic:REXX",
-        "is:alerted channel:devel is:starred",
-        "is:alerted channel:devel",
+        `is:alerted channel:${devel_id} is:starred topic:`,
+        `is:alerted channel:${devel_id} is:starred topic:REXX`,
+        `is:alerted channel:${devel_id} is:starred`,
+        `is:alerted channel:${devel_id}`,
         "is:alerted",
     ];
     assert.deepEqual(suggestions.strings, expected);
 
-    suggestions = get_suggestions("is:dm channel:devel topic:");
-    expected = ["is:dm channel:devel topic:", "is:dm channel:devel", "is:dm"];
+    suggestions = get_suggestions(`is:dm channel:${devel_id} topic:`);
+    expected = [`is:dm channel:${devel_id} topic:`, `is:dm channel:${devel_id}`, `is:dm`];
     assert.deepEqual(suggestions.strings, expected);
 
-    suggestions = get_suggestions("topic:REXX channel:devel topic:");
-    expected = ["topic:REXX channel:devel topic:", "topic:REXX channel:devel", "topic:REXX"];
+    suggestions = get_suggestions(`topic:REXX channel:${devel_id} topic:`);
+    expected = [
+        `topic:REXX channel:${devel_id} topic:`,
+        `topic:REXX channel:${devel_id}`,
+        "topic:REXX",
+    ];
     assert.deepEqual(suggestions.strings, expected);
 });
 
@@ -748,38 +769,41 @@ test("topic_suggestions (limits)", () => {
 
 test("whitespace_glitch", ({override, mock_template}) => {
     mock_template("search_description.hbs", true, (_data, html) => html);
+    const office_stream_id = new_stream_id();
 
     const query = "channel:office "; // note trailing space
 
     override(stream_topic_history_util, "get_server_history", noop);
-    stream_data.add_sub({stream_id: 77, name: "office", subscribed: true});
+    stream_data.add_sub({stream_id: office_stream_id, name: "office", subscribed: true});
 
     const suggestions = get_suggestions(query);
 
-    const expected = ["channel:office"];
+    const expected = [`channel:${office_stream_id}`];
 
     assert.deepEqual(suggestions.strings, expected);
 });
 
 test("channel_completion", ({override}) => {
-    stream_data.add_sub({stream_id: 77, name: "office", subscribed: true});
-    stream_data.add_sub({stream_id: 88, name: "dev help", subscribed: true});
+    const office_stream_id = new_stream_id();
+    stream_data.add_sub({stream_id: office_stream_id, name: "office", subscribed: true});
+    const dev_help_stream_id = new_stream_id();
+    stream_data.add_sub({stream_id: dev_help_stream_id, name: "dev help", subscribed: true});
 
-    override(narrow_state, "stream_name", noop);
+    override(narrow_state, "stream_id", noop);
 
     let query = "channel:of";
     let suggestions = get_suggestions(query);
-    let expected = ["channel:office"];
+    let expected = [`channel:${office_stream_id}`];
     assert.deepEqual(suggestions.strings, expected);
 
     query = "-channel:of";
     suggestions = get_suggestions(query);
-    expected = ["-channel:office"];
+    expected = [`-channel:${office_stream_id}`];
     assert.deepEqual(suggestions.strings, expected);
 
     query = "hel";
     suggestions = get_suggestions(query);
-    expected = ["hel", "channel:dev+help"];
+    expected = ["hel", `channel:${dev_help_stream_id}`];
     assert.deepEqual(suggestions.strings, expected);
 });
 
@@ -789,7 +813,7 @@ test("people_suggestions", ({override, mock_template}) => {
 
     let query = "te";
 
-    override(narrow_state, "stream_name", noop);
+    override(narrow_state, "stream_id", noop);
 
     const ted = {
         email: "ted@zulip.com",
@@ -959,7 +983,7 @@ test("people_suggestions", ({override, mock_template}) => {
 test("operator_suggestions", ({override, mock_template}) => {
     mock_template("search_description.hbs", true, (_data, html) => html);
 
-    override(narrow_state, "stream_name", () => undefined);
+    override(narrow_state, "stream_id", () => undefined);
 
     // Completed operator should return nothing
     let query = "channel:";
@@ -977,37 +1001,40 @@ test("operator_suggestions", ({override, mock_template}) => {
     expected = ["-s", "-sender:myself@zulip.com", "-sender:", "-channel:"];
     assert.deepEqual(suggestions.strings, expected);
 
-    query = "channel:Denmark is:alerted -f";
+    // 66 is a misc channel id.
+    query = "channel:66 is:alerted -f";
     suggestions = get_suggestions(query);
     expected = [
-        "channel:Denmark is:alerted -f",
-        "channel:Denmark is:alerted -sender:myself@zulip.com",
-        "channel:Denmark is:alerted -sender:",
-        "channel:Denmark is:alerted",
-        "channel:Denmark",
+        "channel:66 is:alerted -f",
+        "channel:66 is:alerted -sender:myself@zulip.com",
+        "channel:66 is:alerted -sender:",
+        "channel:66 is:alerted",
+        "channel:66",
     ];
     assert.deepEqual(suggestions.strings, expected);
 });
 
 test("queries_with_spaces", () => {
-    stream_data.add_sub({stream_id: 77, name: "office", subscribed: true});
-    stream_data.add_sub({stream_id: 88, name: "dev help", subscribed: true});
+    const office_id = new_stream_id();
+    stream_data.add_sub({stream_id: office_id, name: "office", subscribed: true});
+    const dev_help_id = new_stream_id();
+    stream_data.add_sub({stream_id: dev_help_id, name: "dev help", subscribed: true});
 
     // test allowing spaces with quotes surrounding operand
     let query = 'channel:"dev he"';
     let suggestions = get_suggestions(query);
-    let expected = ["channel:dev+help"];
+    let expected = [`channel:${dev_help_id}`];
     assert.deepEqual(suggestions.strings, expected);
 
     // test mismatched quote
     query = 'channel:"dev h';
     suggestions = get_suggestions(query);
-    expected = ["channel:dev+help"];
+    expected = [`channel:${dev_help_id}`];
     assert.deepEqual(suggestions.strings, expected);
 
     // test extra space after operator still works
     query = "channel: offi";
     suggestions = get_suggestions(query);
-    expected = ["channel:office"];
+    expected = [`channel:${office_id}`];
     assert.deepEqual(suggestions.strings, expected);
 });
diff --git a/web/tests/stream_data.test.js b/web/tests/stream_data.test.js
index 6d3ed05808..25176a0227 100644
--- a/web/tests/stream_data.test.js
+++ b/web/tests/stream_data.test.js
@@ -17,6 +17,7 @@ const people = zrequire("people");
 const settings_config = zrequire("settings_config");
 const sub_store = zrequire("sub_store");
 const stream_data = zrequire("stream_data");
+const hash_util = zrequire("hash_util");
 const stream_settings_data = zrequire("stream_settings_data");
 const user_groups = zrequire("user_groups");
 
@@ -135,10 +136,8 @@ test("basics", () => {
     assert.deepEqual(stream_data.get_colors(), ["red", "yellow"]);
     assert.deepEqual(stream_data.subscribed_stream_ids(), [social.stream_id, test.stream_id]);
 
-    assert.ok(stream_data.is_subscribed_by_name("social"));
-    assert.ok(stream_data.is_subscribed_by_name("Social"));
-    assert.ok(!stream_data.is_subscribed_by_name("Denmark"));
-    assert.ok(!stream_data.is_subscribed_by_name("Rome"));
+    assert.ok(stream_data.is_subscribed(social.stream_id));
+    assert.ok(!stream_data.is_subscribed(denmark.stream_id));
 
     assert.equal(stream_data.get_stream_privacy_policy(test.stream_id), "public");
     assert.equal(stream_data.get_stream_privacy_policy(social.stream_id), "invite-only");
@@ -147,9 +146,10 @@ test("basics", () => {
         "invite-only-public-history",
     );
     assert.equal(stream_data.get_stream_privacy_policy(web_public_stream.stream_id), "web-public");
-    assert.ok(stream_data.is_web_public_by_stream_name(web_public_stream.name));
-    assert.ok(!stream_data.is_web_public_by_stream_name(social.name));
-    assert.ok(!stream_data.is_web_public_by_stream_name("unknown"));
+    assert.ok(stream_data.is_web_public_by_stream_id(web_public_stream.stream_id));
+    assert.ok(!stream_data.is_web_public_by_stream_id(social.stream_id));
+    const unknown_stream_id = 9999;
+    assert.ok(!stream_data.is_web_public_by_stream_id(unknown_stream_id));
 
     assert.ok(stream_data.is_invite_only_by_stream_id(social.stream_id));
     // Unknown stream id
@@ -159,9 +159,6 @@ test("basics", () => {
     assert.equal(stream_data.get_color(undefined), "#c2c2c2");
     assert.equal(stream_data.get_color(1234567), "#c2c2c2");
 
-    assert.equal(stream_data.get_name("denMARK"), "Denmark");
-    assert.equal(stream_data.get_name("unknown Stream"), "unknown Stream");
-
     assert.ok(!stream_data.is_muted(social.stream_id));
     assert.ok(stream_data.is_muted(denmark.stream_id));
 
@@ -176,19 +173,37 @@ test("basics", () => {
 
     // "new" correct url formats
     assert.equal(stream_data.slug_to_stream_id("2-social"), 2);
+    assert.equal(hash_util.decode_operand("channel", "2-social"), "2");
+
     assert.equal(stream_data.slug_to_stream_id("2"), 2);
+    assert.equal(hash_util.decode_operand("channel", "2"), "2");
+
     // we still get 2 because it's a valid stream id
     assert.equal(stream_data.slug_to_stream_id("2-whatever"), 2);
-    // invalid stream id
-    assert.equal(stream_data.slug_to_stream_id("999-social"), undefined);
-    // legacy
-    assert.equal(stream_data.slug_to_stream_id("social"), 2);
+    assert.equal(stream_data.slug_to_stream_id("2-"), 2);
+
+    // legacy, we recognize "social" as a valid channel name
+    assert.equal(stream_data.slug_to_stream_id("social"), 2);
+    assert.equal(hash_util.decode_operand("channel", "social"), "2");
+
+    // These aren't prepended with valid ids nor valid channel names. We
+    // don't get any stream id from the slug, and the decoded operand (the
+    // only caller of `slug_to_stream_id`) returns an empty string (which we
+    // don't display anywhere, since the channel is invalid).
+    assert.equal(stream_data.slug_to_stream_id("999-social"), undefined);
+    assert.equal(hash_util.decode_operand("channel", "999-social"), "");
 
-    // invalid formats
-    assert.equal(stream_data.slug_to_stream_id("25-or-6-to-4"), undefined);
-    assert.equal(stream_data.slug_to_stream_id("2something"), undefined);
     assert.equal(stream_data.slug_to_stream_id("99-whatever"), undefined);
+    assert.equal(hash_util.decode_operand("channel", "99-whatever"), "");
+
+    assert.equal(stream_data.slug_to_stream_id("25-or-6-to-4"), undefined);
+    assert.equal(hash_util.decode_operand("channel", "25-or-6-to-4"), "");
+
+    assert.equal(stream_data.slug_to_stream_id("2something"), undefined);
+    assert.equal(hash_util.decode_operand("channel", "2something"), "");
+
     assert.equal(stream_data.slug_to_stream_id("99whatever"), undefined);
+    assert.equal(hash_util.decode_operand("channel", "99whatever"), "");
 
     // sub_store
     assert.equal(sub_store.get(-3), undefined);
@@ -528,12 +543,12 @@ test("delete_sub", () => {
 
     stream_data.add_sub(canada);
 
-    assert.ok(stream_data.is_subscribed_by_name("Canada"));
+    assert.ok(stream_data.is_subscribed(canada.stream_id));
     assert.equal(stream_data.get_sub("Canada").stream_id, canada.stream_id);
     assert.equal(sub_store.get(canada.stream_id).name, "Canada");
 
     stream_data.delete_sub(canada.stream_id);
-    assert.ok(!stream_data.is_subscribed_by_name("Canada"));
+    assert.ok(!stream_data.is_subscribed(canada.stream_id));
     assert.ok(!stream_data.get_sub("Canada"));
     assert.ok(!sub_store.get(canada.stream_id));
 
diff --git a/web/tests/stream_events.test.js b/web/tests/stream_events.test.js
index 24fbec632b..2ceba6c010 100644
--- a/web/tests/stream_events.test.js
+++ b/web/tests/stream_events.test.js
@@ -83,7 +83,7 @@ const frontend = {
 };
 
 function narrow_to_frontend() {
-    const filter = new Filter([{operator: "stream", operand: "frontend"}]);
+    const filter = new Filter([{operator: "stream", operand: frontend.stream_id.toString()}]);
     message_lists.current = {
         data: {
             filter,
@@ -382,12 +382,12 @@ test("marked_subscribed (emails)", ({override}) => {
 
     $("#channels_overlay_container .stream-row:not(.notdisplayed)").length = 0;
 
-    assert.ok(!stream_data.is_subscribed_by_name(sub.name));
+    assert.ok(!stream_data.is_subscribed(sub.stream_id));
 
     const user_ids = [15, 20, 25, me.user_id];
     stream_events.mark_subscribed(sub, user_ids, "");
     assert.deepEqual(new Set(peer_data.get_subscribers(sub.stream_id)), new Set(user_ids));
-    assert.ok(stream_data.is_subscribed_by_name(sub.name));
+    assert.ok(stream_data.is_subscribed(sub.stream_id));
 
     const args = subs_stub.get_args("sub");
     assert.deepEqual(sub, args.sub);
diff --git a/web/tests/stream_list.test.js b/web/tests/stream_list.test.js
index b6203cc1d1..9810c74f98 100644
--- a/web/tests/stream_list.test.js
+++ b/web/tests/stream_list.test.js
@@ -429,19 +429,19 @@ test_ui("narrowing", ({mock_template}) => {
 
     let filter;
 
-    filter = new Filter([{operator: "stream", operand: "devel"}]);
+    filter = new Filter([{operator: "stream", operand: develSub.stream_id.toString()}]);
     stream_list.handle_narrow_activated(filter);
     assert.ok($("").hasClass("active-filter"));
 
     filter = new Filter([
-        {operator: "stream", operand: "cars"},
+        {operator: "stream", operand: carSub.stream_id.toString()},
         {operator: "topic", operand: "sedans"},
     ]);
     stream_list.handle_narrow_activated(filter);
     assert.ok(!$("ul.filters li").hasClass("active-filter"));
     assert.ok(!$("").hasClass("active-filter")); // false because of topic
 
-    filter = new Filter([{operator: "stream", operand: "cars"}]);
+    filter = new Filter([{operator: "stream", operand: carSub.stream_id.toString()}]);
     stream_list.handle_narrow_activated(filter);
     assert.ok(!$("ul.filters li").hasClass("active-filter"));
     assert.ok($("").hasClass("active-filter"));