DEV Community

Cover image for Solved: Syncing Figma Comments to Slack for Better Design Ops
Darian Vance
Darian Vance

Posted on • Originally published at wp.me

Solved: Syncing Figma Comments to Slack for Better Design Ops

🚀 Executive Summary

TL;DR: This article provides a step-by-step guide to automate syncing Figma comments directly into a dedicated Slack channel using a Python script. This solution eliminates manual polling and context-switching, significantly improving design operations velocity and fostering near real-time feedback loops.

🎯 Key Takeaways

  • Figma API access requires a Personal Access Token from account settings and the specific File Key extracted from the design file’s URL.
  • A Slack App must be created with the chat:write bot token scope and installed to the workspace to enable the bot to post messages.
  • Environment variables, managed via python-dotenv and a config.env file, are crucial for securely storing sensitive API tokens and IDs.
  • A state file (last\_checked\_timestamp.txt) is implemented to track the last successful check time, preventing duplicate comment notifications on subsequent script runs.
  • Handling Figma API timestamps involves converting them to timezone-aware datetime objects (UTC) to ensure accurate comparison and filtering of new comments.
  • The Python script can be scheduled for periodic execution using traditional cron jobs or deployed as a serverless function (e.g., AWS Lambda) for robust automation.

Syncing Figma Comments to Slack for Better Design Ops

Hey team, Darian here. I want to talk about a small but mighty automation I set up that’s saved me—and our design team—a ton of context-switching. I used to manually poll our main Figma file for new feedback before every stand-up. It felt like I was wasting at least an hour or two a week just refreshing a browser tab. No more. By piping Figma comments directly into a dedicated Slack channel, we’ve closed the feedback loop and can spot critical design issues in near real-time. It’s a classic Design Ops win, and I want to show you how to build it.

This isn’t just about convenience; it’s about velocity. Faster feedback means faster iterations and a better product. Let’s get this done.

Prerequisites

  • Figma Account: You’ll need a Figma account with access to the file you want to monitor.
  • Slack Workspace: You need permissions to create a new Slack App or have an admin do it for you.
  • Python 3 Environment: A place to run our script. I trust you know how to set this up.
  • Scheduling Mechanism: A way to run the script periodically. We’ll discuss using cron, but this could easily be a serverless function (AWS Lambda, Google Cloud Functions, etc.).

The Guide: Step-by-Step

Step 1: Get Your Figma Credentials

First, we need to tell our script how to access Figma. We need two things: a Personal Access Token and a File Key.

  1. Personal Access Token: In Figma, go to your main menu > Help and account > Account settings. Scroll down to the “Personal access tokens” section. Create a new token, give it a descriptive name like “SlackCommentSync,” and copy it somewhere safe. This is your password to the API.
  2. File Key: Open the Figma file you want to monitor. Look at the URL in your browser. It will look something like figma.com/file/{file_key}/Your-Project-Name. That long alphanumeric string in the middle is your File Key. Grab that.

Step 2: Create a Slack App

Next, we need a bot to post messages in Slack for us.

  1. Navigate to api.slack.com/apps and click “Create New App.” Choose “From scratch.”
  2. Give your app a name (e.g., “Figma Comment Bot”) and select your workspace.
  3. In the sidebar, go to “OAuth & Permissions.” Scroll down to “Scopes” and under “Bot Token Scopes,” click “Add an OAuth Scope.” Add the chat:write permission. This allows your bot to post messages.
  4. Scroll back up and click “Install to Workspace.” Authorize it.
  5. After authorizing, you’ll see a “Bot User OAuth Token” that starts with xoxb-. Copy this. This is your bot’s password.
  6. Finally, create a new Slack channel (e.g., #figma-feedback) and invite your newly created bot to it. You can find the bot by its app name.

Step 3: The Python Script

Alright, let’s get to the code. I’ll skip the standard virtualenv setup since you likely have your own workflow for that. Just make sure you’re in an isolated environment. You’ll need to install a couple of libraries; you can do this with pip for requests to handle our API calls and python-dotenv to manage our secrets securely.

Create a file named config.env in your project directory. This is where we’ll store our secrets so they aren’t hardcoded in the script. It’s much safer.

FIGMA_TOKEN="your_figma_personal_access_token_here"
SLACK_TOKEN="your_slack_bot_token_starting_with_xoxb"
FIGMA_FILE_KEY="your_figma_file_key_from_url"
SLACK_CHANNEL_ID="YOUR_SLACK_CHANNEL_ID"
Enter fullscreen mode Exit fullscreen mode

To get the SLACK_CHANNEL_ID, right-click the channel name in Slack and select “Copy link.” The last part of the URL, starting with a ‘C’, is the ID.

Now, here’s the main script, let’s call it sync_comments.py. I’ve added comments to explain the logic as we go.

import os
import requests
from datetime import datetime, timezone, timedelta
from dotenv import load_dotenv

# --- Configuration & Setup ---
# Load environment variables from config.env file
load_dotenv('config.env')

FIGMA_TOKEN = os.getenv('FIGMA_TOKEN')
SLACK_TOKEN = os.getenv('SLACK_TOKEN')
FIGMA_FILE_KEY = os.getenv('FIGMA_FILE_KEY')
SLACK_CHANNEL = os.getenv('SLACK_CHANNEL_ID')

# State file to remember the last time we checked for comments
STATE_FILE = 'last_checked_timestamp.txt'

# --- Helper Functions ---
def get_last_check_time():
    """Reads the last successful check time from the state file."""
    try:
        with open(STATE_FILE, 'r') as f:
            return f.read().strip()
    except FileNotFoundError:
        # If the file doesn't exist, check comments from the last hour on first run.
        # This prevents spamming the channel with all historical comments.
        return (datetime.now(timezone.utc) - timedelta(hours=1)).isoformat()

def update_last_check_time(timestamp_str):
    """Writes the current check time to the state file."""
    with open(STATE_FILE, 'w') as f:
        f.write(timestamp_str)

# --- Core Logic ---
def fetch_figma_comments():
    """Fetches all comments from the specified Figma file."""
    url = f"https://api.figma.com/v1/files/{FIGMA_FILE_KEY}/comments"
    headers = {'X-FIGMA-TOKEN': FIGMA_TOKEN}
    try:
        response = requests.get(url, headers=headers)
        response.raise_for_status() # Raises an exception for bad status codes (4xx or 5xx)
        return response.json().get('comments', [])
    except requests.exceptions.RequestException as e:
        print(f"Error fetching Figma comments: {e}")
        return []

def post_to_slack(message):
    """Posts a formatted message to the configured Slack channel."""
    url = "https://slack.com/api/chat.postMessage"
    headers = {'Authorization': f"Bearer {SLACK_TOKEN}"}
    payload = {
        'channel': SLACK_CHANNEL,
        'text': message, # Fallback text for notifications
        'blocks': [
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": message
                }
            }
        ]
    }
    try:
        response = requests.post(url, headers=headers, json=payload)
        response.raise_for_status()
    except requests.exceptions.RequestException as e:
        print(f"Error posting to Slack: {e}")

def main():
    """Main execution function."""
    print("Starting Figma comment sync...")
    last_check_iso = get_last_check_time()
    last_check_dt = datetime.fromisoformat(last_check_iso.replace('Z', '+00:00'))

    current_check_time = datetime.now(timezone.utc)

    all_comments = fetch_figma_comments()
    if not all_comments:
        print("No comments found or API error.")
        return

    new_comments_found = False
    for comment in sorted(all_comments, key=lambda c: c['created_at']):
        comment_dt = datetime.fromisoformat(comment['created_at'].replace('Z', '+00:00'))

        # We only care about comments created after our last check
        if comment_dt > last_check_dt:
            new_comments_found = True
            user_name = comment['user']['handle']
            comment_text = comment['message']

            # Figma's API doesn't give a direct link, but we can construct one
            node_id = comment.get('client_meta', {}).get('node_id')
            link = f"https://www.figma.com/file/{FIGMA_FILE_KEY}?node-id={node_id}" if node_id else "Link not available"

            message = (
                f":figma: *New Figma Comment from {user_name}*\n"
                f"> {comment_text}\n"
                f"<{link}|View in Figma>"
            )

            print(f"Found new comment from {user_name}. Posting to Slack.")
            post_to_slack(message)

    if not new_comments_found:
        print("No new comments since last check.")

    # Update the state file with the time this script started running
    update_last_check_time(current_check_time.isoformat())
    print("Sync complete.")

if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Pro Tip: In my production setups, I replace the simple text file for state management with something more robust, like a Redis key or a small database entry. This avoids file permission issues and race conditions if the script ever runs in parallel, especially in a serverless environment. But for getting started, a text file is perfectly fine.

Step 4: Schedule the Script

You don’t want to run this manually. The goal is automation. The classic way is using cron on a server.

You can set up a cron job to run this script, say, every 15 minutes. The command would look something like this, assuming your script is in your user’s project folder.

*/15 * * * * python3 /path/to/your/project/sync_comments.py
Enter fullscreen mode Exit fullscreen mode

Alternatively, a more modern approach is to deploy this as a serverless function (like AWS Lambda) and trigger it on a schedule. This is often more reliable and cost-effective than managing a dedicated server for a small script.

Common Pitfalls

Here’s where I’ve stumbled in the past, so you don’t have to:

  • Slack Scopes are Wrong: The most common mistake is forgetting the OAuth scopes. You’ll create the bot, get the token, run the script, and… nothing. It’s almost always because you forgot to give your bot the chat:write permission in the Slack app settings and then reinstalling the app to your workspace.
  • Timezone Hell: Figma’s API returns timestamps in UTC (ISO 8601 format). My script handles this using Python’s timezone-aware datetime objects, but it’s a critical detail. If you modify the script, make sure all your time comparisons are timezone-aware to avoid missing comments or sending duplicates.
  • Initial Run Spam: The first time you run the script, the state file won’t exist. If you don’t handle this case, you could end up posting every single comment ever made on the file. My script defaults to only grabbing comments from the last hour on its first run to prevent this.
  • Rate Limiting: Both Figma and Slack have API rate limits. For a simple check every 5-15 minutes, you’ll be fine. But if you try to run it every second or monitor dozens of files with one token, you might get temporarily blocked. Keep the schedule reasonable.

Conclusion

And that’s it. A simple Python script that bridges a critical gap between design and development. This isn’t just about notifications; it’s about creating a culture of responsive feedback and reducing the friction of communication. You’ve now got a solid foundation you can build on—maybe add filtering for comments that @-mention a specific team, or route feedback to different channels based on the page name in Figma. Go make your designers happy.

Cheers,

Darian Vance


Darian Vance

👉 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

Top comments (0)