# How to Integrate Replit with Trello

- Tool: Replit
- Difficulty: Intermediate
- Time required: 15 minutes
- Last updated: March 2026

## TL;DR

To integrate Replit with Trello, generate an API Key from trello.com/power-ups/admin and authorize a Token, store both in Replit Secrets (lock icon in sidebar), then call the Trello REST API to create cards, move them between lists, and manage boards. Register a webhook pointing to your deployed Replit app for real-time card change notifications.

## Why Connect Replit to Trello?

Trello's simplicity is its strength — boards, lists, and cards map naturally to any workflow. But when your project management needs go beyond what Trello's built-in automations can handle, the Trello REST API lets you build custom integrations that automate card creation, status updates, and reporting in ways that Power-Ups and Butler rules cannot.

Common integration scenarios include: automatically creating a Trello card when a new support ticket arrives, moving cards between lists based on webhook events from other systems, building a custom reporting dashboard that aggregates data across multiple boards, or syncing Trello card status to a database so you can query project progress. Any time you want to create cards from code (form submissions, scheduled jobs, external triggers) or react to card changes programmatically, the Trello API is the tool.

Trello's API uses simple query parameter authentication — your API Key and Token are appended to every request URL. This is less secure than OAuth but significantly simpler for personal integrations and server-side code where the credentials are stored in Replit Secrets and never exposed to browsers. This tutorial walks through the full setup: generating credentials, making API calls, and receiving webhook events on a deployed Replit app.

## Before you start

- A Trello account with at least one board created
- A Replit account with a Node.js or Python Repl ready
- Basic familiarity with REST APIs and HTTP requests
- A deployed Replit app URL (needed for webhook registration — use Autoscale deployment)

## Step-by-step guide

### 1. Generate Your Trello API Key and Token

Trello's authentication uses two credentials: an API Key (identifies your integration) and a Token (grants access to a specific Trello account's data). Both are required for every API call.

To get your API Key, log into Trello and navigate to trello.com/power-ups/admin. Click 'New Power-Up' or find an existing one — even if you are not building a Power-Up, this is where Trello manages developer apps. Fill in the required fields (Power-Up Name: 'Replit Integration', Workspace: choose any, Iframe connector URL: leave blank for now). Click 'Create' and then click on your new app. Under the 'API Key' tab, you will see your API Key displayed. Click 'Show API Key' and copy it.

Next, generate a Token. On the same API Key page, click the 'Token' link (shown in a note below the API Key). This opens an authorization page where Trello asks you to grant the app access to your account. Click 'Allow'. You will see a long Token string — copy it immediately as it is shown only once on this page.

The Token you just generated grants read/write access to all boards, lists, and cards the authorizing Trello account can access. It does not expire unless you manually revoke it in Trello Settings > Apps. For integrations where you want read-only access or time-limited access, append &expiration=1day or &expiration=never to the token authorization URL and set &scope=read instead of &scope=read,write.

**Expected result:** You have both a Trello API Key (32-character string) and a Token (64-character string) ready to store in Replit.

### 2. Store Credentials in Replit Secrets

Click the lock icon (🔒) in the Replit sidebar to open the Secrets panel. Add two secrets:

1. TRELLO_API_KEY — your 32-character API Key from the Power-Up admin page
2. TRELLO_TOKEN — your 64-character Token from the authorization page

Click 'Add Secret' after each one. These values are now encrypted with AES-256 and stored separately from your code. They will never appear in your file tree, version control history, or be visible to other users who fork your Repl.

If you need to work with multiple Trello workspaces or use different tokens for different environments, you can add additional secrets like TRELLO_TOKEN_PROD and TRELLO_TOKEN_DEV and select the appropriate one with an environment variable check.

You can also store frequently used IDs as secrets to avoid hardcoding them:
- TRELLO_BOARD_ID — the ID of your main board (find it by adding .json to your board URL: trello.com/b/{boardId}/board-name.json)
- TRELLO_LIST_ID_TODO — the ID of your 'To Do' list (find it from the board JSON or the Trello API)

Access in code:
- Node.js: process.env.TRELLO_API_KEY
- Python: os.environ['TRELLO_API_KEY']

**Expected result:** TRELLO_API_KEY and TRELLO_TOKEN appear in the Replit Secrets panel with values hidden.

### 3. Read Boards, Lists, and Cards from the Trello API

The Trello REST API base URL is https://api.trello.com/1/. All requests append the API key and token as query parameters. This is simpler than bearer token authentication but means both values appear in request URLs — ensure you are always making these calls server-side (from your Replit backend), never from browser JavaScript where they would be visible in network logs.

The code below provides both Node.js and Python examples for reading board structure and card data. Install the Python requests library if using Python: in the Replit Shell, run pip install requests.

```
// trello-client.js — Node.js Trello API client
const BASE_URL = 'https://api.trello.com/1';

function getAuthParams() {
  const key = process.env.TRELLO_API_KEY;
  const token = process.env.TRELLO_TOKEN;
  if (!key || !token) throw new Error('TRELLO_API_KEY and TRELLO_TOKEN secrets required');
  return `key=${key}&token=${token}`;
}

async function trelloGet(endpoint) {
  const url = `${BASE_URL}${endpoint}${endpoint.includes('?') ? '&' : '?'}${getAuthParams()}`;
  const response = await fetch(url);
  if (!response.ok) {
    const error = await response.text();
    throw new Error(`Trello API ${response.status}: ${error}`);
  }
  return response.json();
}

async function trelloPost(endpoint, body) {
  const url = `${BASE_URL}${endpoint}?${getAuthParams()}`;
  const response = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body)
  });
  if (!response.ok) {
    const error = await response.text();
    throw new Error(`Trello API ${response.status}: ${error}`);
  }
  return response.json();
}

async function trelloPut(endpoint, body) {
  const url = `${BASE_URL}${endpoint}?${getAuthParams()}`;
  const response = await fetch(url, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body)
  });
  if (!response.ok) {
    const error = await response.text();
    throw new Error(`Trello API ${response.status}: ${error}`);
  }
  return response.json();
}

// Get all boards for the authenticated user
async function getBoards() {
  return trelloGet('/members/me/boards?fields=id,name,url');
}

// Get all lists on a board
async function getLists(boardId) {
  return trelloGet(`/boards/${boardId}/lists?fields=id,name,pos`);
}

// Get all cards in a list
async function getCards(listId) {
  return trelloGet(`/lists/${listId}/cards?fields=id,name,desc,due,labels,url`);
}

module.exports = { trelloGet, trelloPost, trelloPut, getBoards, getLists, getCards };
```

**Expected result:** Calling getBoards() returns a JSON array of your Trello boards with IDs and names. getLists() returns the lists on a specific board.

### 4. Create and Manage Cards via the API

With the client module ready, build an Express server that exposes endpoints for creating, updating, and moving cards. Card creation requires at minimum a name and the ID of the list to add it to. Optionally, you can set a description, due date, labels, and members.

The moveCard function is the most commonly used operation in automation workflows — it changes the list a card belongs to (e.g., moving from 'In Progress' to 'Done') without any other changes.

Install Express if not already present: npm install express

```
// server.js — Trello card management Express server
const express = require('express');
const { trelloPost, trelloPut, trelloGet, getBoards, getLists } = require('./trello-client');

const app = express();
app.use(express.json());

// GET /boards — list all accessible boards
app.get('/boards', async (req, res) => {
  try {
    const boards = await getBoards();
    res.json({ boards });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// GET /boards/:boardId/lists — get lists on a board
app.get('/boards/:boardId/lists', async (req, res) => {
  try {
    const lists = await getLists(req.params.boardId);
    res.json({ lists });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// POST /cards — create a new card
app.post('/cards', async (req, res) => {
  try {
    const { listId, name, description, dueDate, labelIds } = req.body;
    if (!listId || !name) {
      return res.status(400).json({ error: 'listId and name are required' });
    }

    const cardData = {
      idList: listId,
      name,
      desc: description || '',
      pos: 'bottom'
    };

    if (dueDate) cardData.due = dueDate; // ISO 8601 string
    if (labelIds && labelIds.length > 0) {
      cardData.idLabels = labelIds.join(',');
    }

    const card = await trelloPost('/cards', cardData);
    res.json({
      id: card.id,
      name: card.name,
      url: card.url,
      shortLink: card.shortLink
    });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// PUT /cards/:cardId/move — move card to a different list
app.put('/cards/:cardId/move', async (req, res) => {
  try {
    const { targetListId } = req.body;
    if (!targetListId) {
      return res.status(400).json({ error: 'targetListId required' });
    }

    const updated = await trelloPut(`/cards/${req.params.cardId}`, {
      idList: targetListId,
      pos: 'bottom'
    });
    res.json({ success: true, cardId: updated.id, newList: updated.idList });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// PUT /cards/:cardId — update card details
app.put('/cards/:cardId', async (req, res) => {
  try {
    const { name, description, dueComplete } = req.body;
    const updates = {};
    if (name) updates.name = name;
    if (description !== undefined) updates.desc = description;
    if (dueComplete !== undefined) updates.dueComplete = dueComplete;

    const updated = await trelloPut(`/cards/${req.params.cardId}`, updates);
    res.json({ success: true, card: { id: updated.id, name: updated.name } });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.listen(3000, '0.0.0.0', () => {
  console.log('Trello integration server running on port 3000');
});
```

**Expected result:** POST /cards creates a new Trello card in the specified list and returns the card ID and URL. PUT /cards/:cardId/move successfully moves the card to a different list.

### 5. Set Up Trello Webhooks for Real-Time Card Events

Trello webhooks fire whenever something changes on a board or card — card moves, new comments, checklist completions, and more. Unlike other services that have a webhook configuration UI, Trello webhooks are created programmatically via the API.

First, deploy your Replit app to get a stable URL. Click the Deploy button, choose 'Autoscale', and note your deployment URL (e.g., https://your-app.replit.app). Your webhook endpoint must be publicly accessible and must respond to Trello's initial HEAD request with a 200 status code.

To create a webhook, send a POST request to the Trello webhooks API specifying the callback URL and the model ID to watch (a board ID, list ID, or card ID). The Python code below handles both the webhook registration and the event receiver.

```
# trello_webhook.py — Python webhook registration and handler
import os
import requests
from flask import Flask, request, jsonify

app = Flask(__name__)

TRELLO_BASE = 'https://api.trello.com/1'

def get_auth_params():
    return {
        'key': os.environ['TRELLO_API_KEY'],
        'token': os.environ['TRELLO_TOKEN']
    }

def register_webhook(model_id, callback_url):
    """Register a Trello webhook for a board or card ID."""
    params = {**get_auth_params(),
              'idModel': model_id,
              'callbackURL': callback_url,
              'description': 'Replit Integration Webhook'}
    response = requests.post(f'{TRELLO_BASE}/webhooks', params=params)
    response.raise_for_status()
    return response.json()

def list_webhooks():
    """List all webhooks for the current token."""
    params = get_auth_params()
    response = requests.get(
        f'{TRELLO_BASE}/tokens/{params["token"]}/webhooks',
        params=params
    )
    return response.json()

# Trello sends HEAD request to verify webhook URL — must return 200
@app.route('/trello-webhook', methods=['HEAD'])
def webhook_verify():
    return '', 200

# Handle incoming webhook events
@app.route('/trello-webhook', methods=['POST'])
def trello_webhook():
    payload = request.json
    if not payload:
        return 'OK', 200

    action = payload.get('action', {})
    action_type = action.get('type', 'unknown')
    data = action.get('data', {})

    print(f'Trello event: {action_type}')

    if action_type == 'createCard':
        card_name = data.get('card', {}).get('name')
        list_name = data.get('list', {}).get('name')
        print(f'New card created: "{card_name}" in "{list_name}"')

    elif action_type == 'updateCard':
        card = data.get('card', {})
        old = data.get('old', {})
        if 'idList' in old:
            # Card was moved between lists
            print(f'Card moved: {card.get("name")} -> list {card.get("idList")}')
        elif 'dueComplete' in old:
            complete = card.get('dueComplete')
            print(f'Card completion changed: {card.get("name")} -> {complete}')

    elif action_type == 'commentCard':
        text = data.get('text', '')
        member = action.get('memberCreator', {}).get('fullName', 'Unknown')
        print(f'Comment by {member}: {text[:100]}')

    return 'OK', 200

@app.route('/register-webhook', methods=['POST'])
def create_webhook():
    """Endpoint to register a new Trello webhook."""
    data = request.json
    board_id = data.get('boardId')
    callback_url = data.get('callbackUrl')

    if not board_id or not callback_url:
        return jsonify({'error': 'boardId and callbackUrl required'}), 400

    webhook = register_webhook(board_id, callback_url)
    return jsonify({'webhook': webhook})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=3000)
```

**Expected result:** After calling /register-webhook with your board ID and deployment URL, Trello delivers events to /trello-webhook whenever cards are created or moved on that board.

## Best practices

- Always make Trello API calls from your Replit backend server, never from client-side JavaScript — the API Key and Token appear in request URLs, and exposing them in the browser allows anyone viewing your page source to access your Trello boards.
- Store your TRELLO_API_KEY and TRELLO_TOKEN in Replit Secrets (lock icon in sidebar) and also consider storing frequently-used board and list IDs as secrets or environment variables to avoid hardcoding them in your codebase.
- Use the ?fields= query parameter to request only the fields you need — by default Trello returns 30+ fields per card, but most use cases need only id, name, desc, and idList, which significantly reduces response size.
- Always include a HEAD request handler on your webhook endpoint — Trello sends an HTTP HEAD request to validate the URL before registering a webhook, and the registration will fail if your endpoint does not respond with 200.
- Deploy webhook receivers on Autoscale to ensure 24/7 availability — Trello will delete webhooks that receive too many consecutive failures, and the development URL is not available when your browser is closed.
- Cache board structure (board ID, list IDs, label IDs) in your application to avoid fetching it on every API call — board structure rarely changes and these values are safe to store in Replit Secrets or a configuration object.
- Implement idempotent card creation to prevent duplicate cards when your endpoint is called multiple times — check for an existing card with the same title before creating a new one, or use a unique identifier in the card description to deduplicate.

## Use cases

### Automated Card Creation from Form Submissions

When a user submits a contact form or support request on your website, automatically create a Trello card in the correct list with the user's details, priority label, and due date. The card appears in your team's Trello board instantly without any manual data entry.

Prompt example:

```
Build an Express server with a /submit-task POST endpoint. When called with title, description, priority, and email fields, create a new Trello card in the specified list ID, add a colored label based on priority, set a due date, and include the email in the card description. Store TRELLO_API_KEY and TRELLO_TOKEN in environment variables.
```

### Cross-System Status Sync

Build a webhook bridge that moves Trello cards between lists automatically when events happen in other systems — for example, move a card from 'In Progress' to 'Done' when a GitHub PR is merged, or move it back to 'Review' when a new comment is added.

Prompt example:

```
Create a Flask server that receives GitHub webhook events. When a PR is merged, it should look up the matching Trello card by matching the PR title or branch name, then use the Trello API to move the card to the 'Done' list ID on the specified board. Return 200 for all recognized events.
```

### Project Progress Dashboard

Build a read-only dashboard that queries your Trello boards and aggregates card counts by list, shows overdue cards, and calculates throughput — all without Trello Power-Ups or paid add-ons, just your own Replit backend serving the data.

Prompt example:

```
Create an Express API with a /board-stats/:boardId endpoint that fetches all lists and cards from a Trello board, counts cards per list, identifies cards with due dates in the past that are not marked complete, and returns the aggregated stats as JSON for a dashboard frontend.
```

## Troubleshooting

### API returns '401 Unauthorized' or 'invalid token'

Cause: The most common causes are: the API Key or Token was copied with extra whitespace or newline characters, the Token was revoked in Trello Settings, or you are using an expired time-limited Token (if you generated a Token with expiration=1day).

Solution: Verify your credentials by testing directly in the Replit Shell: curl 'https://api.trello.com/1/members/me?key=YOUR_KEY&token=YOUR_TOKEN'. A valid response returns your profile JSON. If it fails, regenerate your Token at trello.com/power-ups/admin and update the TRELLO_TOKEN secret in Replit. Check that you authorized a 'never' expiration Token.

### Webhook registration returns 'callbackURL is not valid' or 403 error

Cause: Trello validates the callback URL when you register a webhook by sending a HEAD request to it. If the URL is not accessible (development URL, not yet deployed), returns a non-200 status, or does not exist, the registration will fail.

Solution: Deploy your Replit app first using Autoscale deployment to get a stable URL. Ensure your webhook handler includes a route that responds to HEAD requests with 200. Test the URL manually in a browser or with curl before registering it with Trello.

```
// Express — handle Trello's HEAD verification request
app.head('/trello-webhook', (req, res) => res.sendStatus(200));
app.post('/trello-webhook', (req, res) => {
  // handle event...
  res.sendStatus(200);
});
```

### createCard returns 'invalid id' for the list ID

Cause: The list ID (idList) provided in the card creation request does not exist or is malformed. Trello list IDs are 24-character hexadecimal strings. A common mistake is using the list name instead of the ID, or copying an ID with extra characters.

Solution: Find valid list IDs by calling GET /boards/{boardId}/lists with your credentials and checking the 'id' field on each list object. List IDs look like 5f3d8e1a2b4c6d8e0a2b4c6d. You can also find them by adding .json to your board URL and searching for the list name in the JSON response.

```
// Find list IDs for a board
const lists = await trelloGet(`/boards/${boardId}/lists?fields=id,name`);
console.log(lists.map(l => ({ id: l.id, name: l.name })));
```

### Webhook events stop arriving after a period of time

Cause: Trello automatically deletes webhooks that return non-200 responses for too many consecutive deliveries (typically after 50 consecutive failures). If your Replit app was redeploying or experiencing downtime, webhook deliveries may have failed repeatedly.

Solution: Check your webhook status by calling GET /tokens/{token}/webhooks. If the webhook is missing, re-register it. To prevent this, use a Reserved VM deployment for critical webhook endpoints that need guaranteed uptime, or implement proper error handling in your webhook handler to always return 200 (even if internal processing fails).

```
// Always return 200, handle errors internally
app.post('/trello-webhook', async (req, res) => {
  res.sendStatus(200); // Acknowledge immediately
  try {
    await processWebhookEvent(req.body);
  } catch (err) {
    console.error('Webhook processing error:', err.message);
    // Don't let errors cause non-200 responses to Trello
  }
});
```

## Frequently asked questions

### How do I connect Replit to Trello?

Generate a Trello API Key at trello.com/power-ups/admin, then authorize a Token by clicking the Token link on the API Key page. Store both as TRELLO_API_KEY and TRELLO_TOKEN in Replit Secrets (lock icon in the sidebar). Access them in code as process.env.TRELLO_API_KEY (Node.js) or os.environ['TRELLO_API_KEY'] (Python), and append them as query parameters to all Trello API requests.

### Does Replit work with Trello webhooks?

Yes, but your Replit app must be deployed to use webhooks — the development URL only works while your browser is open. Deploy with Autoscale to get a stable https://your-app.replit.app URL. Your webhook endpoint must also respond to HEAD requests with 200, which Trello uses to verify the URL when registering the webhook.

### How do I find Trello list IDs and board IDs?

The quickest way is to add .json to your Trello board URL in the browser (e.g., trello.com/b/XXXX/my-board.json) and search for 'id' in the response. For list IDs, call GET https://api.trello.com/1/boards/{boardId}/lists?key=KEY&token=TOKEN and find the id field for each list. You can also use the Trello API Explorer at developer.atlassian.com/cloud/trello/rest/.

### Can I use the Trello API for free?

Yes, the Trello REST API is free for all Trello account types. There is a rate limit of 300 requests per 10 seconds per API key and 100 requests per 10 seconds per token. For typical integration use cases (creating cards, reading boards, moving cards), these limits are more than sufficient. Free Trello accounts have limits on Power-Ups and attachments but not on API access.

### How do I store Trello API credentials securely in Replit?

Open the Replit Secrets panel (lock icon 🔒 in the sidebar) and add TRELLO_API_KEY and TRELLO_TOKEN as separate secrets. Replit encrypts these with AES-256 and injects them as environment variables at runtime. Never paste them directly into your code — Replit's Secret Scanner monitors code files for API key patterns and will prompt you to move them to Secrets.

### Why is my Trello webhook not receiving events?

The two most common reasons are: you registered the webhook with the development URL instead of the deployment URL, or your webhook endpoint does not respond to HEAD requests. Deploy your app using Autoscale deployment to get a stable URL, add a HEAD handler that returns 200, then re-register the webhook. You can verify your webhook status by calling GET /tokens/{token}/webhooks with your Trello credentials.

---

Source: https://www.rapidevelopers.com/replit-integration/trello
© RapidDev — https://www.rapidevelopers.com/replit-integration/trello
