DEV Community

CBT Tools
CBT Tools

Posted on

How to Build a CBT Thought Record Journal in Python (Mental Health Tooling)

If you've ever struggled with anxiety, negative self-talk, or emotional spirals, you've probably heard of Cognitive Behavioral Therapy (CBT). It's one of the most evidence-backed therapeutic approaches for anxiety, depression, and dozens of other conditions.

But here's the thing: the core CBT technique — the thought record — is essentially a structured way to debug your own thinking. And if you're a developer, you already know how to debug.

In this tutorial, I'll show you how to build a CBT Thought Record Journal in Python — a CLI tool that guides you through the 7-column thought record, stores entries as JSON, and lets you review patterns over time.

What Is a Thought Record?

A thought record (also called a "dysfunctional thought record" or "ABCDE worksheet") is a structured table where you:

  1. Situation — Describe what triggered the emotional response
  2. Emotions — Rate intensity (0-100)
  3. Automatic Thoughts — What went through your mind?
  4. Cognitive Distortion — Which thinking trap did you fall into?
  5. Evidence For — What supports the thought?
  6. Evidence Against — What contradicts it?
  7. Balanced Thought — A fairer, more accurate reframe

This structure comes from Aaron Beck's cognitive therapy model. Research shows that regular practice reduces anxiety and depression symptoms by 40-60% over 12-20 sessions.

The 10 Common Cognitive Distortions

Before we code, here are the distortions our tool will detect:

DISTORTIONS = {
    'all_or_nothing': 'All-or-Nothing Thinking (splitting)',
    'overgeneralization': 'Overgeneralization',
    'mental_filter': 'Mental Filter (focusing only on negatives)',
    'disqualifying_positive': 'Disqualifying the Positive',
    'mind_reading': 'Jumping to Conclusions (Mind Reading)',
    'fortune_telling': 'Jumping to Conclusions (Fortune Telling)',
    'magnification': 'Magnification (Catastrophizing) or Minimization',
    'emotional_reasoning': 'Emotional Reasoning',
    'should_statements': 'Should Statements',
    'labeling': 'Labeling and Mislabeling',
    'personalization': 'Personalization and Blame',
}
Enter fullscreen mode Exit fullscreen mode

Building the Tool

Step 1: Data Model

from dataclasses import dataclass, field
from datetime import datetime
from typing import List
import json

@dataclass
class ThoughtRecord:
    date: str = field(default_factory=lambda: datetime.now().isoformat())
    situation: str = ''
    emotions: dict = field(default_factory=dict)
    automatic_thoughts: str = ''
    distortions: List[str] = field(default_factory=list)
    evidence_for: str = ''
    evidence_against: str = ''
    balanced_thought: str = ''
    new_emotion_rating: dict = field(default_factory=dict)

    def to_dict(self):
        return {
            'date': self.date,
            'situation': self.situation,
            'emotions': self.emotions,
            'automatic_thoughts': self.automatic_thoughts,
            'distortions': self.distortions,
            'evidence_for': self.evidence_for,
            'evidence_against': self.evidence_against,
            'balanced_thought': self.balanced_thought,
            'new_emotion_rating': self.new_emotion_rating,
        }
Enter fullscreen mode Exit fullscreen mode

Step 2: Distortion Detector

This is where it gets interesting. We can use simple pattern matching to suggest which distortions might be present:

import re

def detect_distortions(thought: str) -> list:
    thought_lower = thought.lower()
    detected = []

    if re.search(r'\b(always|never|completely|totally|ruined|perfect|failure|success)\b', thought_lower):
        detected.append('all_or_nothing')

    if re.search(r'\b(every time|nothing ever)\b', thought_lower):
        detected.append('overgeneralization')

    if re.search(r'\b(disaster|end of|over for|can.?t handle|unbearable|worst)\b', thought_lower):
        detected.append('magnification')

    if re.search(r'\b(should|must|have to|ought to)\b', thought_lower):
        detected.append('should_statements')

    if re.search(r'\b(i feel|it feels|feels like)\b', thought_lower):
        detected.append('emotional_reasoning')

    if re.search(r'\b(i am a |i.m a |they are a )\b', thought_lower):
        detected.append('labeling')

    if re.search(r'\b(they think|they know|everyone thinks|people will)\b', thought_lower):
        detected.append('mind_reading')

    if re.search(r'\b(will go wrong|going to fail|this will)\b', thought_lower):
        detected.append('fortune_telling')

    return detected
Enter fullscreen mode Exit fullscreen mode

This isn't therapy — it's a prompting tool. The user confirms which distortions actually apply. But the pattern matching helps you learn to recognize them.

Step 3: Interactive CLI

def run_thought_record():
    print('=' * 60)
    print('  CBT Thought Record Journal')
    print('=' * 60)

    record = ThoughtRecord()
    record.situation = input('What happened? (Describe the situation):\n> ')

    print('\nWhat emotions did you feel? (rate 0-100)')
    print("Type emotion + rating, e.g. 'anxiety 80'. Empty line to finish.")
    while True:
        entry = input('> ')
        if not entry.strip():
            break
        parts = entry.rsplit(' ', 1)
        if len(parts) == 2 and parts[1].isdigit():
            record.emotions[parts[0]] = int(parts[1])

    record.automatic_thoughts = input('\nWhat went through your mind?\n> ')

    detected = detect_distortions(record.automatic_thoughts)
    if detected:
        print('\nPossible cognitive distortions detected:')
        for d in detected:
            print(f'  - {DISTORTIONS[d]}')
        confirm = input('\nWhich apply? (comma-separate, or all):\n> ')
        record.distortions = detected if confirm.strip() == 'all' else \
            [d.strip() for d in confirm.split(',') if d.strip() in DISTORTIONS]

    record.evidence_for = input('\nEvidence that supports the thought:\n> ')
    record.evidence_against = input('\nEvidence against the thought:\n> ')
    record.balanced_thought = input('\nA fairer, more balanced thought:\n> ')

    print('\nRe-rate your emotions now (0-100):')
    for emotion in record.emotions:
        new_rating = input(f'  {emotion} (was {record.emotions[emotion]}): ')
        if new_rating.isdigit():
            record.new_emotion_rating[emotion] = int(new_rating)

    return record
Enter fullscreen mode Exit fullscreen mode

Step 4: Storage + Pattern Review

from pathlib import Path
from collections import Counter

class ThoughtRecordJournal:
    def __init__(self, filepath='thought_records.json'):
        self.filepath = Path(filepath)
        self.records = self._load()

    def _load(self):
        if self.filepath.exists():
            with open(self.filepath, 'r') as f:
                return json.load(f)
        return []

    def add(self, record: ThoughtRecord):
        self.records.append(record.to_dict())
        self._save()

    def _save(self):
        with open(self.filepath, 'w') as f:
            json.dump(self.records, f, indent=2)

    def review_patterns(self):
        all_distortions = []
        for r in self.records:
            all_distortions.extend(r.get('distortions', []))

        counts = Counter(all_distortions)
        print('\n--- Your Most Common Distortions ---')
        for dist, count in counts.most_common():
            print(f'  {count}x  {DISTORTIONS.get(dist, dist)}')

        print('\n--- Emotion Intensity Trend ---')
        for r in self.records[-10:]:
            for emo, rating in r.get('emotions', {}).items():
                new = r.get('new_emotion_rating', {}).get(emo, '?')
                print(f"  {r['date'][:10]}  {emo}: {rating} -> {new}")
Enter fullscreen mode Exit fullscreen mode

Step 5: Putting It Together

def main():
    journal = ThoughtRecordJournal()
    print('CBT Thought Record Journal')
    print('1. New thought record')
    print('2. Review patterns')
    print('3. Export to CSV')
    choice = input('\nChoice: ')

    if choice == '1':
        record = run_thought_record()
        journal.add(record)
        print('\nThought record saved.')
    elif choice == '2':
        journal.review_patterns()

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

Why This Matters

I built a full CBT toolkit with 200+ free tools covering specific phobias, chronic illness, burnout, and more. The thought record is the foundation — once you can identify and reframe a cognitive distortion, you have a portable skill that works across every mental health challenge.

The toolkit is free and open source: CBT Toolkit on GitHub

The Science

CBT has one of the strongest evidence bases in mental health:

  • Anxiety disorders: 50-60% symptom reduction (Hofmann et al., 2012 meta-analysis of 269 studies)
  • Depression: equivalent to SSRI medication in mild-moderate cases (Cuijpers et al., 2013)
  • Insomnia: 70-80% improvement (CBT-I specifically)
  • PTSD: recommended first-line treatment by APA, NICE, and WHO

The thought record targets cognitive restructuring — the process of identifying, evaluating, and modifying distorted thinking patterns. It's the CBT equivalent of git bisect for your mind: systematically narrowing down where the bug (distortion) is.

Beyond the CLI

From here, you could extend this tool to:

  • Web app (Flask/FastAPI) with a nice UI for the 7-column table
  • Mobile app (React Native) with push reminders
  • NLP-powered distortion detection using a fine-tuned model
  • Pattern visualization with matplotlib showing emotion trends
  • Export to PDF for sharing with a therapist

The core data model and distortion detector stay the same — only the interface changes.

Takeaway

Building mental health tools is deeply rewarding. You're not just writing code — you're creating something that could help someone understand their own mind a little better. And the thought record is the perfect starting point: it's structured, evidence-based, and translates naturally into a data model.

If you found this helpful, check out the CBT Toolkit — 200+ free CBT tools for specific conditions, all open source.


Disclaimer: This tool is for self-help and education, not a replacement for professional therapy. If you're struggling, please reach out to a licensed therapist or a crisis line (988 in the US).

Top comments (0)