Solved: Exporting Slack Conversation History to JSON before Plan Limits

Source: Dev.to | Jan 21, 2026 (UTC)

🚀 Executive Summary

TL;DR: Slack’s free and legacy plans limit access to older conversation history, posing significant challenges for compliance and data retention. This guide provides a Python-based solution leveraging the Slack API to proactively export all conversation history to a structured JSON format, ensuring valuable team communications remain accessible before plan limits are reached.

🎯 Key Takeaways

  • Creating a Slack App with specific Bot Token Scopes (e.g., “channels:history”, “users:read”, “im:history”) is fundamental for granting the necessary permissions to access various conversation types and user data via the Slack API.
  • The Python slack\_sdk library, combined with proper handling of API pagination (next\_cursor) and rate limiting (retry logic with delays), is crucial for reliably extracting complete message histories from all conversation types.
  • For security and maintainability, it’s best practice to use Python virtual environments for dependency isolation and to store the xoxb- Bot User OAuth Token as an environment variable rather than hardcoding it in the script.

Exporting Slack Conversation History to JSON before Plan Limits

Introduction

In the fast-paced world of digital collaboration, Slack has become an indispensable tool for teams of all sizes. It fosters real-time communication, knowledge sharing, and project coordination. However, with its immense utility comes a critical consideration: data retention and accessibility, especially concerning its free and older paid plans.

Many organizations rely on Slack’s free tier or legacy plans, which often come with significant limitations on message history access. Once you exceed a certain message count or time frame, older conversations become inaccessible, effectively disappearing behind a paywall. This isn’t just an inconvenience; it can pose serious challenges for compliance, auditing, historical context, and business intelligence.

At TechResolve, we understand the importance of owning your data. This comprehensive tutorial will guide SysAdmins, Developers, and DevOps Engineers through the process of proactively exporting your Slack conversation history to a structured JSON format. By leveraging the Slack API, you can safeguard your valuable team communications before they are restricted by plan limits, ensuring your data remains accessible, searchable, and analyzable, even if you decide to downgrade or leave Slack.

Let’s dive in and take control of your team’s communication archives.

Prerequisites

Before you begin, ensure you have the following:

  • Slack Workspace Administrator or Owner Permissions: You will need the necessary permissions to create a Slack app and install it to your workspace.
  • Slack API Token: This will be obtained during Step 1. We recommend using a Bot Token for broader access and stability.
  • Python 3.7+ Installed: This tutorial uses Python for scripting. You can download it from python.org.
  • Basic Understanding of Python and APIs: Familiarity with Python syntax and how APIs work will be beneficial.
  • A Text Editor or IDE: Such as VS Code, Sublime Text, or PyCharm.
  • Internet Connectivity: To access the Slack API.

Step-by-Step Guide

Step 1: Create a Slack App and Obtain an API Token

To interact with the Slack API, you need to create a Slack App within your workspace and grant it the necessary permissions (scopes).

  1. Navigate to the Slack API App Management page.
  2. Click on “Create New App”.
  3. Choose “From scratch”.
  4. Give your app a meaningful name (e.g., “History Exporter”) and select your workspace. Click “Create App”.
  5. In the left-hand navigation, under “Features”, click on “OAuth & Permissions”.
  6. Scroll down to the “Scopes” section. Under “Bot Token Scopes”, click “Add an OAuth Scope” and add the following:
    • channels:history (To read messages in public and private channels)
    • channels:read (To view basic information about channels)
    • groups:history (To read messages in private channels/groups)
    • groups:read (To view basic information about private channels/groups)
    • im:history (To read messages in direct messages)
    • im:read (To view basic information about direct messages)
    • mpim:history (To read messages in multi-person direct messages)
    • mpim:read (To view basic information about multi-person direct messages)
    • users:read (To read user profiles to map user IDs to names)
  7. Scroll back up to the “OAuth Tokens for Your Workspace” section and click “Install to Workspace”.
  8. Review the permissions and click “Allow”.
  9. You will now see a “Bot User OAuth Token” starting with xoxb-. Copy this token immediately and store it securely. Treat it like a password. We recommend setting it as an environment variable rather than hardcoding it in your script.

Logic Explanation: By creating an app and assigning these scopes, you grant your script the necessary permissions to fetch channel lists, read message histories, and resolve user IDs to display names, all without needing to log in as a specific user.

Step 2: Set Up Your Python Environment

It’s good practice to use a virtual environment for your Python projects to manage dependencies.

  1. Open your terminal or command prompt.
  2. Create a new directory for your project and navigate into it:

mkdir slack_exporter cd slack_exporter

Enter fullscreen mode

Exit fullscreen mode

  1. Create a virtual environment:

python3 -m venv .venv

Enter fullscreen mode

Exit fullscreen mode

  1. Activate the virtual environment:

    source .venv/bin/activate

  1. Install the slack_sdk library:

pip install slack_sdk

Enter fullscreen mode

Exit fullscreen mode

  1. Set your Slack Bot Token as an environment variable. Replace YOUR_SLACK_BOT_TOKEN with the token you copied in Step 1.

    export SLACK_BOT_TOKEN='xoxb-YOUR_SLACK_BOT_TOKEN'

Logic Explanation: A virtual environment isolates your project dependencies, preventing conflicts with other Python projects. Installing slack_sdk provides a convenient, officially supported way to interact with the Slack API. Using an environment variable for the token enhances security by keeping sensitive information out of your script’s source code.

Step 3: Write the Python Script to Fetch Conversations

Now, let’s write the Python script that will orchestrate the data export.

  1. Create a new file named export_slack.py in your slack_exporter directory.
  2. Paste the following Python code into the file:

import os import json import time from slack_sdk import WebClient from slack_sdk.errors import SlackApiError # --- Configuration --- SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN") OUTPUT_DIR = "slack_exports" EXPORT_FILENAME = "slack_history.json" MAX_RETRIES = 3 RETRY_DELAY_SECONDS = 5 # Initial delay for rate limits # Initialize Slack WebClient if SLACK_BOT_TOKEN is None: raise ValueError("SLACK_BOT_TOKEN environment variable not set. Please set it before running the script.") client = WebClient(token=SLACK_BOT_TOKEN) # Ensure output directory exists os.makedirs(OUTPUT_DIR, exist_ok=True) print("Starting Slack conversation history export...") def get_all_users(): """Fetches all users in the workspace to map user IDs to names.""" users = {} cursor = None retries = 0 while retries < MAX_RETRIES: try: response = client.users_list(cursor=cursor) for user in response["members"]: users[user["id"]] = user.get("real_name", user["profile"].get("display_name", user["name"])) if response["response_metadata"] and "next_cursor" in response["response_metadata"]: cursor = response["response_metadata"]["next_cursor"] if not cursor: break else: break except SlackApiError as e: if e.response["error"] == "ratelimited": print(f"Rate limited during user fetch. Retrying in {RETRY_DELAY_SECONDS} seconds...") time.sleep(RETRY_DELAY_SECONDS) retries += 1 else: print(f"Error fetching users: {e.response['error']}") break except Exception as e: print(f"An unexpected error occurred while fetching users: {e}") break return users def get_all_conversations(channel_types="public_channel,private_channel,im,mpim"): """Fetches all specified types of conversations (channels, DMs, etc.).""" conversations = [] cursor = None retries = 0 print(f"Fetching conversations of types: {channel_types}...") while retries < MAX_RETRIES: try: response = client.conversations_list( types=channel_types, limit=200, # Max conversations per page cursor=cursor ) for conv in response["channels"]: # Filter out bots and deleted channels unless explicitly needed if not conv.get("is_bot") and not conv.get("is_archived") and not conv.get("is_org_shared"): conversations.append({ "id": conv["id"], "name": conv.get("name") or conv.get("user") or conv.get("id"), # Name for channels, user ID for DMs "is_channel": conv.get("is_channel", False), "is_group": conv.get("is_group", False), # Private channels "is_im": conv.get("is_im", False), # Direct messages "is_mpim": conv.get("is_mpim", False), # Multi-person direct messages "members": conv.get("members", []) # For IM/MPIM to resolve names later }) if response["response_metadata"] and "next_cursor" in response["response_metadata"]: cursor = response["response_metadata"]["next_cursor"] if not cursor: break else: break except SlackApiError as e: if e.response["error"] == "ratelimited": print(f"Rate limited during conversation list fetch. Retrying in {RETRY_DELAY_SECONDS} seconds...") time.sleep(RETRY_DELAY_SECONDS) retries += 1 else: print(f"Error fetching conversations: {e.response['error']}") break except Exception as e: print(f"An unexpected error occurred while fetching conversations: {e}") break print(f"Found {len(conversations)} conversations.") return conversations def get_conversation_history(channel_id, channel_name, users_map): """Fetches all messages from a given conversation ID.""" messages = [] cursor = None retries = 0 print(f" Exporting history for '{channel_name}' (ID: {channel_id})...") while retries < MAX_RETRIES: try: response = client.conversations_history( channel=channel_id, limit=1000, # Max messages per page cursor=cursor ) for msg in response["messages"]: # Replace user IDs with names for better readability if "user" in msg: msg["user_name"] = users_map.get(msg["user"], msg["user"]) messages.append(msg) if response["has_more"]: cursor = response["response_metadata"]["next_cursor"] # Slack API rate limits for conversations_history can be strict, add a small delay time.sleep(0.5) else: break except SlackApiError as e: if e.response["error"] == "ratelimited": print(f" Rate limited for '{channel_name}'. Retrying in {RETRY_DELAY_SECONDS} seconds...") time.sleep(RETRY_DELAY_SECONDS) retries += 1 else: print(f" Error fetching history for '{channel_name}': {e.response['error']}") break except Exception as e: print(f" An unexpected error occurred while fetching history for '{channel_name}': {e}") break print(f" Exported {len(messages)} messages from '{channel_name}'.") return messages def main(): all_users = get_all_users() all_conversations = get_all_conversations() exported_data = { "export_date": time.strftime("%Y-%m-%d %H:%M:%S"), "users": all_users, "conversations": [] } for conv in all_conversations: channel_id = conv["id"] channel_name = conv["name"] # For IMs and MPIMs, try to resolve names if conv["is_im"] or conv["is_mpim"]: resolved_members = [] for member_id in conv.get("members", []): resolved_members.append(all_users.get(member_id, member_id)) if conv["is_im"]: channel_name = f"DM with {resolved_members[0]}" if resolved_members else f"DM {channel_id}" elif conv["is_mpim"]: channel_name = f"Group DM with {', '.join(resolved_members)}" if resolved_members else f"Group DM {channel_id}" history = get_conversation_history(channel_id, channel_name, all_users) exported_data["conversations"].append({ "id": channel_id, "name": channel_name, "is_channel": conv["is_channel"], "is_group": conv["is_group"], "is_im": conv["is_im"], "is_mpim": conv["is_mpim"], "messages": history }) output_path = os.path.join(OUTPUT_DIR, EXPORT_FILENAME) with open(output_path, 'w', encoding='utf-8') as f: json.dump(exported_data, f, indent=4, ensure_ascii=False) print(f"\nExport complete! Data saved to '{output_path}'") if __name__ == "__main__": main()

Enter fullscreen mode

Exit fullscreen mode

Logic Explanation:

Step 4: Execute the Script and Verify Data

With the script written, you can now run it to perform the export.

  1. Ensure your virtual environment is active and the SLACK_BOT_TOKEN environment variable is set.
  2. Run the Python script from your terminal:

python export_slack.py

Enter fullscreen mode

Exit fullscreen mode

  1. The script will print its progress to the console. Depending on the size of your workspace and the number of messages, this process can take a significant amount of time (minutes to hours).
  2. Once the script completes, a new directory named slack_exports will be created in your project folder, containing slack_history.json.
  3. Open the slack_history.json file with your text editor or a JSON viewer to inspect the exported data. You should see a structured JSON object containing a list of conversations, each with its messages, and a mapping of user IDs to names.

Common Pitfalls

Conclusion

Congratulations! You have successfully exported your Slack conversation history to a structured JSON format. By proactively archiving your data, you’ve taken a significant step towards data ownership and ensured that valuable information remains accessible, regardless of Slack’s plan limitations or future changes.

This exported data is now a powerful asset. You can use it for:

At TechResolve, we empower you to control your digital infrastructure. This tutorial provides a solid foundation; consider expanding the script to include file exports, refine message parsing, or integrate with a data warehouse for long-term storage and analysis. The possibilities are limitless when you own your data.

👉 Read the original article on TechResolve.blog

☕ Support my work

If this article helped you, you can buy me a coffee:

👉 https://buymeacoffee.com/darianvance

For further actions, you may consider blocking this person and/or reporting abuse

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *

Читайте також