# Topic Voting Auto Self-Vote

**URL:** https://meta.discourse.org/t/topic-voting-auto-self-vote/394770
**Category:** Theme component
**Tags:** topic-voting
**Created:** [January 30, 2026, 3:42pm UTC](https://meta.discourse.org/t/topic-voting-auto-self-vote/394770 "2026-01-30T15:42:54Z")
**Posts on this page:** 1
**Showing post:** 1

<div class="post-metadata">

### Author: ![putty](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/putty/32/370902_2.png) [@putty](https://meta.discourse.org/u/putty)
#### Post date: [January 30, 2026, 3:42pm UTC](https://meta.discourse.org/t/topic-voting-auto-self-vote/394770/1 "2026-01-30T15:42:54Z")

</div>

| | | |
| --- | --- | --- |
| ℹ | **Summary** | Enable automatic topic votes when a user creates a topic |
| 🛠 | **Repository** | [GitHub - dereklputnam/discourse-topic-voting-auto-self-vote · GitHub](https://github.com/dereklputnam/discourse-topic-voting-auto-self-vote) |
| ❓ | **Install Guide** | [How to install a theme or theme component](https://meta.discourse.org/t/how-do-i-install-a-theme-or-theme-component/63682) |
| 📖 | **New to Discourse Themes?** | [Beginner’s guide to using Discourse Themes](https://meta.discourse.org/t/beginners-guide-to-using-discourse-themes/91966) |

Install this theme component

If your community has limited votes to cast, such as here on Meta, it makes sense for the OP’s vote not automatically be cast (see discussion [here](https://meta.discourse.org/t/self-voting-on-topics/127280) and [here](https://meta.discourse.org/t/auto-vote-on-topic-posting/109366)). But for communities with unlimited voting, it is a pain point, and users may not even vote on their own topic! Hence, the need for this component.

# Settings

 ![CleanShot 2026-01-30 at 10.18.32](https://global.discourse-cdn.com/meta/original/4X/6/1/3/6132506c408d177416786e999e4b05e054b6fdb1.png)

- **Auto vote categories:** if you do not want this enabled site-wite, specify which categories it should apply to.
- **Excluded groups:** if you don’t want this to apply to specific groups (perhaps internal teams), add them here.
- **Auto vote on visit:** a gentle way to clean up any existing topics where OP didn’t vote.

## Backfilling topics

### Identify the topics

Use this [data explorer](https://meta.discourse.org/t/32566?silent=true) query to identify topics where the OP did not vote:

```plaintext
-- [params]
-- null category_id :category_id
-- null string :category_slug
-- boolean :exclude_about = true

SELECT
  t.id AS topic_id,
  u.username AS "username",
  t.title AS "Topic Title",
  t.user_id AS "Author",
  t.created_at AS "Created At",
  c.name AS "Category Name",
  c.slug AS "Category Slug"
FROM topics t
JOIN users u ON u.id = t.user_id
JOIN categories c ON c.id = t.category_id
LEFT JOIN topic_voting_topic_vote_count tvvc ON tvvc.topic_id = t.id
LEFT JOIN topic_voting_votes tvv ON tvv.topic_id = t.id AND tvv.user_id = t.user_id
WHERE t.deleted_at IS NULL
  AND (
    tvvc.votes_count IS NULL OR tvv.id IS NULL
  )
  AND (
    :exclude_about = false
    OR t.title ILIKE 'about the % category' = false
  )
  AND (
    :category_id IS NULL OR c.id = :category_id
  )
  AND (
    :category_slug IS NULL OR c.slug = :category_slug
  )

```

> **Tip:** Rerun this query after your update topics to verify the votes were cast 🗳

### Backfilling via API

From there, use that list and run an API script to backfill the votes! **To use my script below, you’ll need to trim off columns beyond the first two.** I left them in the [data explorer](https://meta.discourse.org/t/32566?silent=true) query so it’s easier to identify the topics across your community.

> **Note:** your API key should be scoped to all users, since it’s impersonating each one.

I don’t have permission to attach the script that I used, but here it is in text:

> **Backfill script**
>
> ```plaintext
> #!/usr/bin/env python3
> """
> Backfill Self-Votes via Discourse API
> 
> This script casts votes for topic authors who haven't voted on their own topics.
> It uses the Discourse API to impersonate users and cast votes on their behalf.
> 
> Requirements:
> - Python 3.7+
> - requests library (pip install requests)
> - An admin API key with impersonation permissions
> 
> Usage:
> 1. Update the configuration section below
> 2. Prepare a CSV file with topic_id and username columns
> 3. Run: python backfill_votes_api.py
> 
> CSV Format:
> topic_id,username
> 12345,john_doe
> 12346,jane_smith
> """
> 
> import csv
> import time
> import requests
> from datetime import datetime
> 
> #==============================================================================
> # CONFIGURATION
> #==============================================================================
> 
> # Discourse instance URL (no trailing slash)
> DISCOURSE_URL = 'https://yourcommunity.com'
> 
> # Admin API key (must have impersonation permissions)
> API_KEY = 'YOUR_API_KEY_HERE'
> 
> # Admin username that owns the API key
> API_USERNAME = 'system'
> 
> # Path to CSV file with topic_id and username columns
> CSV_FILE = 'topics_to_vote.csv'
> 
> # Dry run mode - set to False to actually cast votes
> DRY_RUN = True
> 
> # Delay between API calls (seconds) to avoid rate limiting
> DELAY_BETWEEN_REQUESTS = 0.5
> 
> #==============================================================================
> # SCRIPT
> #==============================================================================
> 
> def cast_vote(topic_id: int, username: str) -> dict:
> """
> Cast a vote on a topic as a specific user.
> 
> Args:
> topic_id: The ID of the topic to vote on
> username: The username to impersonate when voting
> 
> Returns:
> dict with 'success' boolean and 'message' string
> """
> url = f"{DISCOURSE_URL}/voting/vote"
> 
> headers = {
> 'Api-Key': API_KEY,
> 'Api-Username': username, # Impersonate the user
> 'Content-Type': 'application/json'
> }
> 
> data = {
> 'topic_id': topic_id
> }
> 
> try:
> response = requests.post(url, headers=headers, json=data)
> 
> if response.status_code == 200:
> return {'success': True, 'message': 'Vote cast successfully'}
> elif response.status_code == 422:
> # Usually means already voted or voting not enabled
> return {'success': False, 'message': 'Already voted or voting not enabled'}
> elif response.status_code == 403:
> return {'success': False, 'message': 'Permission denied - check API key permissions'}
> elif response.status_code == 404:
> return {'success': False, 'message': 'Topic not found or voting not enabled on category'}
> else:
> return {'success': False, 'message': f'HTTP {response.status_code}: {response.text[:200]}'}
> 
> except requests.RequestException as e:
> return {'success': False, 'message': f'Request error: {str(e)}'}
> 
> def main():
> print("\n" + "=" * 60)
> print("Backfill Self-Votes via API")
> print("=" * 60)
> print(f"Mode: {'DRY RUN (no changes)' if DRY_RUN else 'LIVE (votes will be cast)'}")
> print(f"Target: {DISCOURSE_URL}")
> print(f"CSV File: {CSV_FILE}")
> print("=" * 60 + "\n")
> 
> # Read CSV file
> try:
> with open(CSV_FILE, 'r', newline='', encoding='utf-8') as f:
> reader = csv.DictReader(f)
> rows = list(reader)
> except FileNotFoundError:
> print(f"ERROR: CSV file not found: {CSV_FILE}")
> print("\nCreate a CSV file with the following format:")
> print("topic_id,username")
> print("12345,john_doe")
> print("12346,jane_smith")
> return
> except Exception as e:
> print(f"ERROR: Failed to read CSV file: {e}")
> return
> 
> if not rows:
> print("ERROR: CSV file is empty")
> return
> 
> # Validate CSV columns
> columns = set(rows[0].keys())
> 
> if 'topic_id' not in columns or 'username' not in columns:
> print(f"ERROR: CSV must have columns: topic_id and username")
> print(f"Found columns: {columns}")
> return
> 
> print(f"Found {len(rows)} topics to process\n")
> 
> # Process each row
> success_count = 0
> skip_count = 0
> error_count = 0
> 
> for i, row in enumerate(rows, 1):
> topic_id = row['topic_id'].strip()
> username = row['username'].strip()
> 
> print(f"[{i}/{len(rows)}] Topic #{topic_id} by @{username}", end=" ")
> 
> if DRY_RUN:
> print("-> would vote")
> success_count += 1
> else:
> result = cast_vote(int(topic_id), username)
> 
> if result['success']:
> print("-> voted!")
> success_count += 1
> elif 'Already voted' in result['message']:
> print("-> already voted (skipped)")
> skip_count += 1
> else:
> print(f"-> ERROR: {result['message']}")
> error_count += 1
> 
> # Rate limiting
> if i < len(rows):
> time.sleep(DELAY_BETWEEN_REQUESTS)
> 
> # Summary
> print("\n" + "=" * 60)
> print("SUMMARY")
> print("=" * 60)
> print(f"Total topics: {len(rows)}")
> print(f"Votes {'to cast' if DRY_RUN else 'cast'}: {success_count}")
> print(f"Already voted (skipped): {skip_count}")
> print(f"Errors: {error_count}")
> 
> if DRY_RUN:
> print("\n **DRY RUN COMPLETE**")
> print("To cast votes, set DRY_RUN = False and run again.")
> 
> print("")
> 
> if __name__ == ' __main__':
> main()
> 
> ```

---

_[View the full topic](https://meta.discourse.org/t/topic-voting-auto-self-vote/394770)._
