Skip to main content

Getting Started

Thank you for contributing to Gumroad! This guide will help you write high-quality code and create effective pull requests.
All contributors must follow these guidelines. Pull requests that don’t meet these standards may be rejected or require significant revisions.

Communication Standards

Use native-sounding English in all communication:
  • Bad: “HOW IS THIS GOING???”, “how’s dis going”, “thnx fr update”
  • Good: “Is this actively being worked on? I’ve started work on it here…”
Avoid:
  • Excessive capitalization
  • Multiple question marks
  • Grammatical errors
  • Typos
  • Informal abbreviations

Pull Request Guidelines

Required Elements

Every non-trivial pull request must include:
1

Clear description

Follow the PR description structure (see below)
2

Self-review comments

Comment on your own code to guide reviewers
3

Test updates

Include updates to tests, especially end-to-end tests
4

Visual documentation

For UI changes: video of before/after with light/dark mode and mobile/desktop
5

AI disclosure

Declare AI usage (see disclosure format below)

PR Description Structure

Non-trivial PRs should follow this format:
## What

Concrete changes made in this PR (not just a list of files).

## Why

Why this change exists and why this approach was chosen over alternatives.

## Before/After

[Screenshots or video for UI/CSS changes only]
- Include desktop and mobile
- Include light and dark mode

## Test Results

[Screenshot of tests passing locally]

---

## AI Disclosure

Model: Claude Opus 4.6
Prompts:
- "Implement user dashboard with React"
- "Add error handling for payment processing"

PR Size Guidelines

Break up large PRs (1000+ lines) into smaller PRs (~100 lines each). Large PRs are harder to review and more likely to introduce bugs.

Testing Requirements

Your PR must:
  • Include tests for all new features
  • Update existing tests for modified features
  • Include end-to-end tests for user-facing changes
  • Show test results passing locally
Tests must fail when the fix is reverted. If the test passes without the application code change, it is invalid.

Code Standards

General Principles

Don’t leave TODOs or placeholders. Complete the implementation in your PR.
Code should be self-documenting. Don’t leave explanatory comments. Use clear variable and method names instead.
Just fix them. Don’t add comments saying “sorry” or “I know this is bad”.
Pricing, calculations, and discount application belong in Rails, not the frontend. The frontend renders state provided by the backend. Enforce all constraints on the server.

Language Versions

Always use the latest stable versions:
  • Ruby 3.4.3
  • Rails 7.1.6
  • TypeScript 5.5.0
  • React 18.1.0
  • Node 20.17.0

Code Style

# Use latest Ruby features
user = User.find_by(email: params[:email])

# Avoid unless
if condition  # Good
  # code
end

unless condition  # Avoid
  # code
end

# Use modern naming
buyer = purchase.buyer  # Good
customer = purchase.customer  # Avoid

seller = product.seller  # Good
creator = product.creator  # Avoid

Text Formatting

Use sentence case, not title case:
  • ✅ “Save changes”
  • ❌ “Save Changes”

Constants

Assign raw numbers to named constants:
# Good
MAX_CHARACTER_LIMIT = 500
if description.length > MAX_CHARACTER_LIMIT
  # handle error
end

# Bad
if description.length > 500
  # What does 500 mean?
end

Testing Guidelines

Test Structure

Don’t start RSpec test names with “should”. See Better Specs.
it "displays the user's balance" do
  expect(page).to have_text("Balance $100")
end

it "redirects to login when not authenticated" do
  visit dashboard_path
  expect(current_path).to eq(login_path)
end

Test Data

  • Use factories for test data instead of creating objects directly
  • Use @example.com for emails in tests
  • Use example.com, example.org, example.net for custom domains
  • Write descriptive test names that explain the behavior
  • Keep tests independent and isolated
  • Group related tests together

VCR Cassettes

Scope VCR cassettes to specific test files. Sharing cassettes across tests causes collisions where tests read incorrect cached responses.

Sidekiq Assertions

# Good
expect { SomeJob.perform_async }.to change { SomeJob.jobs.size }.by(1)

# Bad - prone to false positives
expect { SomeJob.perform_async }.to_not have_enqueued_sidekiq_job

Sidekiq Jobs

Naming Convention

New Sidekiq job class names must end with “Job”:
class ProcessBacklogJob < ApplicationJob
class CalculateProfitJob < ApplicationJob

Queue Selection

Queue priorities (highest to lowest):
  1. critical - Receipt/purchase emails only
  2. default - Time-sensitive background jobs
  3. low - Most background jobs (use this by default)
  4. mongo - Legacy queue for one-time scripts
Select the lowest priority queue that meets your requirements. Most jobs should use low unless time-sensitive.

Job Deduplication

For deduplication using sidekiq-unique-jobs:
class ProcessUserJob < ApplicationJob
  sidekiq_options lock: :until_executed
  
  def perform(user_id)
    # Job logic
  end
end
Do NOT use on_conflict: :replace - it’s CPU expensive and slow because it scrolls through the Scheduled Set to find existing jobs.

Feature Development

Feature Flags

Use feature flags (Flipper) for new features:
if Flipper.enabled?(:new_dashboard, current_user)
  # New feature code
else
  # Old code path
end

Database Patterns

Do not use database-level foreign key constraints (add_foreign_key). This simplifies data migration and sharding operations at scale.
When creating financial records (receipts, sales), copy specific values (amount, currency, percentage) at purchase time instead of referencing mutable data like a DiscountCode ID. This ensures historical records remain accurate if the original object is edited or deleted.
Prefer re-using deprecated boolean flags instead of creating new ones. Deprecated flags are named DEPRECATED_<something>. Reset values on staging and production first, then rename.
# Reset deprecated flag
Link.where(Link.DEPRECATED_stream_only_condition).find_in_batches do |batch|
  ReplicaLagWatcher.watch
  puts batch.first.id
  Link.where(id: batch.map(&:id)).update_all(Link.set_flag_sql(:DEPRECATED_stream_only, false))
end

Backfilling Data

Do NOT perform backfilling operations via ActiveRecord callbacks. Create scripts in app/services/onetime folder instead.
Why:
  • Gumroad has millions of users and products
  • Callback-based backfilling can enqueue millions of uncontrollable jobs
  • This can crash Sidekiq (Redis out of memory)
  • Creates massive replica lag

Code Organization

File Locations

Concerns

Don’t create new files in app/modules/ (legacy). Use concerns in the right directory: app/models/concerns/, app/controllers/concerns/

Path/URL methods

Don’t create methods ending in _path or _url - they might collide with Rails route helpers. Use a module like CustomDomainRouteBuilder

Public IDs

Use Nano IDs to generate external/public IDs for new models

Modern naming

Use product instead of link, buyer instead of customer, seller instead of creator in new code

Writing Issues

Issue Structure

Issues for enhancements, features, or refactors:
### What

What needs to change. Be concrete:
- Describe current behavior and desired behavior
- Who is affected (buyers, sellers, internal team)
- Quantify impact with data (error rates, support tickets, revenue)
- Use checkbox task list for multiple deliverables

### Why

Why this change matters:
- What user or business problem does this solve?
- Link to related issues, support tickets, or discussions
Keep it short. The title should carry most of the weight — the body adds context the title can’t.

Writing Bug Reports

A great bug report includes:
1

Quick summary

Brief description of the issue
2

Steps to reproduce

Be specific! Include:
  • Exact steps taken
  • Sample code if applicable
  • Environment details
3

Expected behavior

What you expected would happen
4

Actual behavior

What actually happens
5

Notes

Why you think this might be happening, or things you tried that didn’t work

Code Patterns to Avoid

  • ❌ Dynamic Tailwind class interpolation: `text-${color}`
  • ❌ Using unless conditionals
  • ❌ Creating files in app/modules/
  • ❌ Methods ending in _path or _url
  • ❌ Using $.ajax (use request instead)
  • ❌ Leaving TODO comments
  • ❌ Explanatory comments
  • ❌ Starting tests with “should”
  • ❌ Foreign key constraints
  • ❌ Backfilling via callbacks

Reasoning Over Changes

Explain the reasoning behind your changes, not just the change itself:
  • Describe the architectural decision
  • Identify the specific problem being solved
  • For bug fixes, identify the root cause
  • Don’t apply a fix without explaining how the invalid state occurred
Reviewers need to understand WHY you made these changes, not just WHAT changed.

Help Wanted

Looking for issues to contribute to? Any issue with label help wanted is open for contributions: View open issues

License

By contributing, you agree that your contributions will be licensed under the MIT License.

Next Steps

Architecture

Understand the system architecture

Testing

Write comprehensive tests

Authentication

Learn authentication patterns

Deployment

Deploy your changes

Build docs developers (and LLMs) love