Skip to main content

Overview

Projects are the organizational structure of Metaculus. They group related questions together and control access, visibility, and governance. Every question belongs to exactly one primary project and can be tagged to multiple additional projects.

Project Types

Metaculus supports multiple project types for different use cases (from projects/models.py:213-223):
class ProjectTypes(models.TextChoices):
    SITE_MAIN = "site_main"              # Main Metaculus site
    INDEX = "index"                      # Aggregate indices (e.g., democracy index)
    TOURNAMENT = "tournament"            # Time-bound competitions with prizes
    QUESTION_SERIES = "question_series" # Ongoing question collections
    PERSONAL_PROJECT = "personal_project" # User-created collections
    NEWS_CATEGORY = "news_category"     # News and current events
    CATEGORY = "category"               # General topic categories
    LEADERBOARD_TAG = "leaderboard_tag" # Tags for leaderboard filtering
    TOPIC = "topic"                     # Subject matter tags
    COMMUNITY = "community"             # Community-run projects

Tournaments

Competitions with prizes and leaderboards

Communities

Independent forecasting communities

Categories

Organize by topic (AI, climate, politics)

Question Series

Ongoing question collections on specific topics

Indices

Aggregate metrics from multiple questions

Personal Projects

Custom question collections

Project Structure

From projects/models.py:212-402, the core project model:
class Project(TimeStampedModel, TranslatedModel):
    type = models.CharField(max_length=32, choices=ProjectTypes.choices)
    
    # Identification
    name = models.CharField(max_length=200)
    slug = models.CharField(max_length=200, validators=[validate_alpha_slug])
    
    # Description and branding
    subtitle = models.CharField(max_length=255, blank=True, default="")
    description = models.TextField(blank=True, default="")
    header_image = models.ImageField(null=True, blank=True)
    header_logo = models.ImageField(null=True, blank=True)
    emoji = models.CharField(max_length=10, default="", blank=True)
    
    # Tournament/Competition fields
    prize_pool = models.DecimalField(decimal_places=2, max_digits=15, null=True)
    start_date = models.DateTimeField(null=True, blank=True)
    close_date = models.DateTimeField(null=True, blank=True)
    forecasting_end_date = models.DateTimeField(null=True, blank=True)
    
    # Leaderboards
    primary_leaderboard = models.ForeignKey("scoring.Leaderboard", null=True)
    
    # Permissions
    default_permission = models.CharField(choices=ObjectPermission.choices)
    override_permissions = models.ManyToManyField(User, through="ProjectUserPermission")
    created_by = models.ForeignKey(User, on_delete=models.CASCADE)
    
    # Visibility
    visibility = models.CharField(choices=Visibility.choices)
    show_on_homepage = models.BooleanField(default=False)
    
    # Metrics
    followers_count = models.PositiveIntegerField(default=0)
    forecasts_count = models.PositiveIntegerField(default=0)
    forecasters_count = models.PositiveIntegerField(default=0)

Question-Project Relationship

Questions are associated with projects through posts:

Primary Project

Every question belongs to exactly one primary project through its post (from questions/models.py:249-259):
post = models.ForeignKey(
    "posts.Post",
    on_delete=models.CASCADE,
    related_name="questions",
)

# Post has:
default_project = models.ForeignKey(Project, on_delete=models.CASCADE)

Additional Projects (Tags)

Posts can be tagged to multiple additional projects:
projects = models.ManyToManyField(Project, related_name="posts")
The primary project (default_project) controls governance, while additional projects (projects) provide categorization and discovery.

Permissions

Projects use a hierarchical permission system to control access.

Permission Levels

From projects/permissions.py, the available permissions are:
  • null: Private project - no access
  • VIEWER: Can view questions and forecasts
  • FORECASTER: Can view and make predictions
  • CURATOR: Can view, forecast, and help curate content
  • ADMIN: Full control over project

Default Permission

From projects/models.py:332-339:
default_permission = models.CharField(
    choices=ObjectPermission.choices,
    null=True,  # null -> private
    blank=True,
    default=ObjectPermission.FORECASTER,
    db_index=True,
)
Setting default_permission = null makes the project completely private - only users with explicit permission overrides can access it.

Permission Overrides

Individual users can have custom permissions (from projects/models.py:498-513):
class ProjectUserPermission(TimeStampedModel):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    project = models.ForeignKey(Project, on_delete=models.CASCADE)
    permission = models.CharField(choices=ObjectPermission.choices)
    
    class Meta:
        constraints = [
            models.UniqueConstraint(
                name="projectuserpermission_unique_user_id_project_id",
                fields=["user_id", "project_id"],
            ),
        ]

Permission Resolution

From projects/models.py:162-191, permissions are resolved with priority:
  1. Superuser: Automatically get ADMIN permission
  2. Creator: Project creator gets ADMIN permission
  3. Override: Explicit ProjectUserPermission if set
  4. Default: Falls back to project’s default_permission
def annotate_user_permission(self, user: User = None):
    if user and user.is_superuser:
        return self.annotate(user_permission=models.Value(ObjectPermission.ADMIN))
    
    return self.annotate(
        user_permission=models.Case(
            models.When(
                Q(created_by_id__isnull=False, created_by_id=user_id),
                then=models.Value(ObjectPermission.ADMIN),
            ),
            default=Coalesce(
                F("_user_permission_override__permission"),
                F("default_permission"),
            ),
        ),
    )

Visibility

Projects control how they appear across the platform.

Visibility Options

From projects/models.py:229-362:
class Visibility(models.TextChoices):
    NORMAL = "normal"
    NOT_IN_MAIN_FEED = "not_in_main_feed"
    UNLISTED = "unlisted"
  • Visible on the main feed
  • Questions contribute to global leaderboards and medals
  • Listed on tournaments/question series page
Use for: Public competitions and major question collections

Homepage Display

From projects/models.py:364-368:
show_on_homepage = models.BooleanField(default=False, db_index=True)
show_on_services_page = models.BooleanField(
    default=False, db_index=True, help_text="Show project on the Services page."
)
Featured projects can be promoted to the homepage for maximum visibility.

Communities

Communities are projects run by external organizations or user groups.

Community Features

  • Independent governance (custom admins)
  • Own leaderboards and competitions
  • Custom branding (header images, logos)
  • Separate permission management
  • Can host tournaments within the community

Creating Communities

Communities are created with type=COMMUNITY and automatically get a primary leaderboard (from projects/models.py:422-439).

Indices

Indices aggregate multiple questions into a single metric.

Index Structure

From projects/models.py:533-563:
class ProjectIndex(TimeStampedModel):
    class IndexType(models.TextChoices):
        DEFAULT = "default"
        MULTI_YEAR = "multi_year"
    
    type = models.CharField(max_length=32, choices=IndexType.choices)
    min = models.SmallIntegerField(default=-100, help_text="Y-axis min")
    min_label = models.CharField(
        max_length=200,
        help_text='Label at minimum end. Example: "Less democratic"',
    )
    max = models.SmallIntegerField(default=100, help_text="Y-axis max")
    max_label = models.CharField(
        max_length=200,
        help_text='Label at maximum end. Example: "More democratic"',
    )
    increasing_is_good = models.BooleanField(
        default=True,
        help_text="Color polarity: if on, higher values are good (green → right)",
    )

Index Post Weights

Questions contribute to indices with configurable weights (from projects/models.py:566-614):
class ProjectIndexPost(TimeStampedModel):
    index = models.ForeignKey(ProjectIndex, on_delete=models.CASCADE)
    post = models.ForeignKey("posts.Post", on_delete=models.CASCADE)
    weight = models.FloatField(
        help_text=(
            "Weight of the post within the index. "
            "If the post includes a group of questions, "
            "the same weight will be applied to all subquestions."
        ),
        default=1.0,
    )
    order = models.IntegerField(default=0)
Example: A democracy index might weight “free elections” at 2.0 and “press freedom” at 1.5.

Project Metrics

Projects track engagement metrics (from projects/models.py:375-379):
followers_count = models.PositiveIntegerField(default=0, editable=False)
forecasts_count = models.PositiveIntegerField(default=0, editable=False)
forecasters_count = models.PositiveIntegerField(default=0, editable=False)
These are updated via (from projects/models.py:470-495):
def update_followers_count(self):
    self.followers_count = self.subscriptions.count()

def update_forecasts_count(self):
    result = Post.objects.filter_projects(self).aggregate(
        total_forecasts=Sum("forecasts_count")
    )
    self.forecasts_count = result["total_forecasts"] or 0

def update_forecasters_count(self):
    self.forecasters_count = (
        PostUserSnapshot.objects.filter(
            last_forecast_date__isnull=False,
            user__is_staff=False,
        )
        .filter(Q(post__default_project=self) | Q(post__projects=self))
        .values("user_id")
        .distinct()
        .count()
    )

Subscriptions

Users can follow projects to receive updates (from projects/models.py:516-530):
class ProjectSubscription(TimeStampedModel):
    user = models.ForeignKey(
        User, on_delete=models.CASCADE, related_name="project_subscriptions"
    )
    project = models.ForeignKey(
        Project, on_delete=models.CASCADE, related_name="subscriptions"
    )
    
    class Meta:
        constraints = [
            models.UniqueConstraint(
                name="projectsubscription_unique_user_project",
                fields=["user_id", "project_id"],
            )
        ]
Subscribe to projects you’re interested in to get notifications when new questions are added or important updates occur.

Querying Projects

Metaculus provides powerful querysets for filtering projects.

By Type

From projects/models.py:24-47:
Project.objects.filter_tournament()     # Tournaments and question series
Project.objects.filter_communities()    # Communities
Project.objects.filter_topic()          # Topic tags
Project.objects.filter_category()       # Category tags
Project.objects.filter_leaderboard_tags()  # Leaderboard filtering tags

By Permission

From projects/models.py:193-209:
# Get all projects user can access
Project.objects.filter_permission(user=user)

# Get projects where user can forecast
Project.objects.filter_permission(user=user, permission=ObjectPermission.FORECASTER)

# Get projects where user is admin
Project.objects.filter_permission(user=user, permission=ObjectPermission.ADMIN)

With Annotations

From projects/models.py:49-159:
# Annotate with post count
Project.objects.annotate_posts_count()

# Annotate with question count
Project.objects.annotate_questions_count()

# Annotate with user's subscription status
Project.objects.annotate_is_subscribed(user=user)

# Annotate with user's permission level
Project.objects.annotate_user_permission(user=user)

Use Cases

Create a TOURNAMENT project with prize pool, set start/close dates, configure leaderboard, add curated questions.
Create a COMMUNITY project, set custom permissions, add admins via ProjectUserPermission, enable subscriptions.
Create a TOPIC or CATEGORY project, tag relevant questions via post.projects, users can filter by topic.
Create an INDEX project, configure scale and labels, add weighted questions via ProjectIndexPost.
Create a PERSONAL_PROJECT, set default_permission=null for privacy, organize questions you’re tracking.

Best Practices

1

Choose the Right Type

Select the project type that matches your use case. Don’t use TOURNAMENT for simple question collections.
2

Set Appropriate Permissions

Consider who should access your project. Most public projects use FORECASTER as default.
3

Configure Visibility

Use UNLISTED for work-in-progress, NOT_IN_MAIN_FEED for specialized content, NORMAL for featured content.
4

Add Clear Descriptions

Write comprehensive descriptions and resolution criteria so participants understand the project goals.
5

Enable Subscriptions

Make your project subscribable so interested users can follow updates and new questions.

API Reference

Projects API

Explore the full Projects API documentation

Tournaments

Learn about competitive forecasting

Leaderboards

Understand project-specific rankings

Questions

Learn about organizing questions

Permissions

Deep dive into permission management

Build docs developers (and LLMs) love