Last active
January 20, 2026 20:38
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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