Skip to content

Instantly share code, notes, and snippets.

@pmgreen
Last active January 20, 2026 20:38
Show Gist options
  • Select an option

  • Save pmgreen/7139e67b154a28bb8140704b18c4af9c to your computer and use it in GitHub Desktop.

Select an option

Save pmgreen/7139e67b154a28bb8140704b18c4af9c to your computer and use it in GitHub Desktop.
Script to get a tabular view of Trello JSON exports. This example gets road work from a DONE column. Thanks to Real Orange One.
#!/usr/bin/env python3
"""
Convert a Trello board export (JSON) into filtered JSON that can be more easily converted to CSV.
The filter_road_done function is an example of how to report on keywords and columns (e.g. give me all cards containing "road" in the "DONE!" column).
Adapted from https://gist.github.com/RealOrangeOne/c35751ee794e90df512bdfba6f22574d
Lumo was used for formatting (and code suggestions).
Usage:
Export JSON from Trello (three dots > print, export and share > export as JSON), run this script:
python trello_to_json.py <input_file.json> <output_file.json>
The output can then be converted to CSV using https://json-csv.com/
"""
import argparse
import json
import os
import sys
def parse_args() -> argparse.Namespace:
"""Define and parse command‑line arguments."""
parser = argparse.ArgumentParser(
description="Extract road‑related DONE cards from a Trello JSON export."
)
parser.add_argument(
"input",
help="Path to the Trello JSON export file.",
type=str,
)
parser.add_argument(
"output",
help="Path where the filtered JSON will be written.",
type=str,
)
return parser.parse_args()
def load_trello_json(path: str) -> dict:
"""Read the Trello JSON file and return the parsed dictionary."""
abs_path = os.path.abspath(path)
print(f"Reading data from {abs_path}...")
with open(abs_path, "r", encoding="utf-8") as f:
return json.load(f)
def build_lookup_tables(data: dict) -> tuple[dict, dict, dict]:
"""
Create convenient lookup dictionaries for lists, members, and labels.
Returns:
(lists, users, labels)
"""
lists = {lst["id"]: lst["name"] for lst in data.get("lists", [])}
users = {mem["id"]: mem["fullName"] for mem in data.get("members", [])}
labels = {lbl["id"]: lbl["name"] for lbl in data.get("labels", [])}
return lists, users, labels
def parse_cards(data: dict, lists: dict, users: dict, labels: dict) -> list[dict]:
"""Transform raw Trello cards into a friendlier structure."""
parsed = []
for card in data.get("cards", []):
parsed.append(
{
"name": card["name"],
"list": lists.get(card["idList"], ""),
"description": card["desc"],
"url": card["shortUrl"],
"members": [
users[mid] for mid in card.get("idMembers", []) if mid in users
],
"labels": [
labels[lid] for lid in card.get("idLabels", []) if lid in labels
],
"lastactivity": card["dateLastActivity"][:10],
}
)
return parsed
def filter_road_done(cards: list[dict]) -> list[dict]:
"""
Keep only cards that:
* are on the list named "DONE!"
* contain the word “road” in the title, description, or any label
"""
keyword = "road"
filtered = [
c
for c in cards
if c["list"] == "DONE!"
and (
keyword in c["name"].lower()
or keyword in c["description"].lower()
or any(keyword in lbl.lower() for lbl in c["labels"])
)
]
return filtered
def write_output(output_path: str, board: dict, cards: list[dict]) -> None:
"""Write the final JSON payload to disk."""
payload = {"board_data": board, "cards": cards}
abs_path = os.path.abspath(output_path)
with open(abs_path, "w", encoding="utf-8") as f:
json.dump(payload, f, indent=4, ensure_ascii=False)
print(f"Output written to {abs_path}!")
print("Visit https://json-csv.com/ to convert the output to CSV.")
def main() -> None:
args = parse_args()
# Load and inspect the raw Trello export
data = load_trello_json(args.input)
print(
f"Found {len(data.get('cards', []))} cards in {len(data.get('lists', []))} lists."
)
print("Parsing...")
# Build lookup tables and transform cards
lists, users, labels = build_lookup_tables(data)
parsed_cards = parse_cards(data, lists, users, labels)
# Filter for the specific “road” + DONE! criteria
filtered_cards = filter_road_done(parsed_cards)
print(f"Filtered down to {len(filtered_cards)} road‑related DONE cards.")
# (Optional) pretty‑print the filtered list for quick sanity‑checking
# print(json.dumps(filtered_cards, indent=2))
# Assemble board‑level metadata
board_meta = {
"name": data.get("name", ""),
"url": data.get("shortUrl", ""),
}
# Write the final JSON file
write_output(args.output, board_meta, filtered_cards)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
sys.exit("\nInterrupted by user.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment