code icon Code

List Slack Channels

Fetch all accessible channels with pagination

Source Code

import fs from "fs";
import path from "path";

const [outputPath = "", types = "public_channel", memberOnly = "true"] = process.argv.slice(2);
const memberOnlyFlag = memberOnly === "true";

console.log(`Fetching Slack channels (types: ${types})...`);

try {
  // Get auth info and channels in parallel
  const [authData, channels] = await Promise.all([
    (async () => {
      const res = await fetch("https://slack.com/api/auth.test", {
        headers: { Authorization: "Bearer PLACEHOLDER_TOKEN" },
      });
      const data = await res.json();
      if (!data.ok) throw new Error(`Auth failed: ${data.error}`);
      return data;
    })(),

    (async () => {
      const allChannels = [];
      let cursor = null;

      do {
        const params = new URLSearchParams({
          types,
          exclude_archived: "true",
          limit: "200",
        });
        if (cursor) params.set("cursor", cursor);

        const res = await fetch("https://slack.com/api/conversations.list?" + params, {
          headers: { Authorization: "Bearer PLACEHOLDER_TOKEN" },
        });
        const data = await res.json();

        if (!data.ok) throw new Error(`conversations.list failed: ${data.error}`);
        allChannels.push(...(data.channels || []));
        cursor = data.response_metadata?.next_cursor;
      } while (cursor);

      return allChannels;
    })(),
  ]);

  // Filter and transform
  const transformedChannels = channels
    .filter(ch => !memberOnlyFlag || ch.is_member)
    .map(ch => ({
      id: ch.id,
      name: ch.name,
      isPrivate: ch.is_private,
      isIm: ch.is_im,
      isMpim: ch.is_mpim,
      isMember: ch.is_member,
      numMembers: ch.num_members,
      topic: ch.topic?.value,
      purpose: ch.purpose?.value,
      created: ch.created ? new Date(ch.created * 1000).toISOString() : null,
    }))
    .sort((a, b) => (b.numMembers || 0) - (a.numMembers || 0));

  const result = {
    workspace: authData.team,
    fetchedAt: new Date().toISOString(),
    types,
    memberOnly: memberOnlyFlag,
    count: transformedChannels.length,
    channels: transformedChannels,
  };

  if (outputPath) {
    const dir = path.dirname(outputPath);
    if (dir && dir !== ".") fs.mkdirSync(dir, { recursive: true });
    fs.writeFileSync(outputPath, JSON.stringify(result, null, 2));
    console.log(`Written to: ${outputPath}`);
  }

  console.log(`✓ Workspace: ${authData.team}`);
  console.log(`  ${transformedChannels.length} channels found`);

  if (transformedChannels.length > 0) {
    console.log(`  Top channels:`);
    transformedChannels.slice(0, 5).forEach(ch => {
      console.log(`    #${ch.name}: ${ch.numMembers || "?"} members`);
    });
  }

  console.log(JSON.stringify({
    success: true,
    workspace: authData.team,
    count: transformedChannels.length,
  }));
} catch (error) {
  console.error("Error:", error.message);
  throw error;
}