See what sticks.
Gumroad is an e-commerce platform that enables creators to sell products directly to consumers. This repository contains the source code for the Gumroad web application.
We don't run an inbound review queue on this repo. To contribute a change: fork the repo, open the pull request on your own fork, then email support@gumroad.com with a link to it. We read every submission and merge the ones we want on our side, keeping your authorship — though we can't promise a reply to each one or a timeline.
Your PR still has to meet the same bar as our own work — visual evidence for anything user-facing, QA steps, test results, and an AI disclosure naming the model. See CONTRIBUTING.md for the full guidelines and the substitutions that apply when working from a fork.
💡 If you're on Windows, follow our Windows setup guide instead.
Before you begin, ensure you have the following installed:
We use Docker to setup the services for development environment.
sudo wget -qO- https://get.docker.com/ | sh
sudo usermod -aG docker $(whoami)
Install a local version of MySQL 8.0.x to match the version running in production.
The local version of MySQL is a dependency of the Ruby mysql2 gem. You do not need to start an instance of the MySQL service locally. The app will connect to a MySQL instance running in the Docker container.
brew install mysql@8.0 percona-toolkit
brew link --force mysql@8.0
# to use Homebrew's `openssl`:
brew install openssl
bundle config --global build.mysql2 --with-opt-dir="$(brew --prefix openssl)"
# ensure MySQL is not running as a service
brew services stop mysql@8.0
apt install libmysqlclient-devWe use imagemagick for preview editing.
brew install imagemagicksudo apt-get install imagemagickFor newer image formats we use libvips for image processing with ActiveStorage.
brew install libvipssudo apt-get install libvips-devWe use ffprobe that comes with FFmpeg package to fetch metadata from video files.
brew install ffmpegsudo apt-get install ffmpegWe use pdftk to stamp PDF files with the Gumroad logo and the buyers' emails.
sudo apt-get install pdftkWhile generating invoices, to convert HTML to PDF, PDFKit expects wkhtmltopdf to be installed on your system. Download and install the version 0.12.6 for your platform.
We use Bundler to install Ruby gems.
gem install bundler
Install gems:
bundle install
Also make sure to install dotenv as it is required for some console commands:
gem install dotenv
Make sure the correct version of npm is enabled:
corepack enable
Install dependencies:
npm install
App can be booted without any custom credentials. But if you would like to use services that require custom credentials (e.g. S3, Stripe, Resend, etc.), you can copy the .env.example file to .env and fill in the values.
If you installed Docker Desktop (on a Mac or Windows machine), you can run the following command to start the Docker services:
make local
If you are on Linux, or installed Docker via a package manager on a mac, you may need superuser access to expose container ports. To do that, use sudo make local instead.
This command will not terminate. You run this in one tab and start the application in another tab.
If you want to run Docker services in the background, use LOCAL_DETACHED=true make local instead.
bin/rails db:prepare
For Linux (Debian / Ubuntu) you might need the following:
apt install libxslt-dev libxml2-devbin/dev
This starts the Rails server, the JavaScript build system, and a Sidekiq worker.
You can now access the application at http://localhost:3000. Seller subdomains and the asset/api hosts use *.localhost (e.g. http://seller.localhost:3000, http://api.localhost:3000) — modern browsers auto-resolve these to 127.0.0.1, so no /etc/hosts edits are needed.
*.localhost over HTTP is treated as a secure context by browsers (so Stripe.js, Stripe Elements, and other secure-context APIs work), but a couple of features that need either HTTPS or a registrable cookie domain don't work in this setup:
ngrok http 3000) and register the resulting hostname in the Stripe Apple Pay dashboard._gumroad_guid, multi-account "switch seller", session) — browsers reject Domain=localhost, so cookies set on localhost:3000 aren't sent to seller.localhost:3000. Test these flows on a single host, or front the app with a local HTTPS reverse proxy on a registrable hostname (e.g. gumroad.test via mkcert) where Domain=.gumroad.test works.Run the full test suite:
bin/rspec
Run a single file or specific test:
bin/rspec spec/requests/dashboard_spec.rb
bin/rspec spec/requests/dashboard_spec.rb:75
Before running tests:
make local # start Docker services (db, Redis, etc.)
RAILS_ENV=test bin/rails db:setup # set up the test database
RAILS_ENV=test bin/rails js:export # generate JS constants for the test environment
Integration specs use Capybara with Selenium driving Chrome. Install Chrome from google.com/chrome.
See docs/testing.md for details on preventing flaky specs, VCR cassettes, debugging widgets, and purchase testing with Stripe/PayPal.
You can log in with the username seller@gumroad.com and the password password. The two-factor authentication code is 000000.
Read more about logging in as a user with a different team role at Users & authentication.
You will need to explicitly reindex Elasticsearch to populate the indices after setup, otherwise you will see index_not_found_exception errors when you visit the dev application. You can reset them using:
# Run this in a rails console:
DevTools.delete_all_indices_and_reindex_all
To send push notifications:
INITIALIZE_RPUSH_APPS=true bundle exec rpush start -e development -f
bin/rails c
bin/rake task_name
We use ESLint for JS, and Rubocop for Ruby. Your editor should support displaying and fixing issues reported by these inline, and CI checks these on every push.
If you'd like, you can run git config --local core.hooksPath .githooks to check for these locally when committing.
fork())objc[11912]: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called.
objc[11912]: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug.
This issue occurs on macOS due to how the fork() system call interacts with multithreaded Objective-C applications—commonly triggered when Spring is enabled during testing.
Temporarily disable Spring before running your tests to avoid this error.
export DISABLE_SPRING=1
bin/rspec spec/requests/balance_pages_spec.rb
This will disable Spring for the current session, allowing the tests to run without triggering the fork()-related crash.
(top 30 of 116)
Ruby
80.2%
TypeScript
14.6%
HTML
4.4%
See what sticks.
Gumroad is an e-commerce platform that enables creators to sell products directly to consumers. This repository contains the source code for the Gumroad web application.
We don't run an inbound review queue on this repo. To contribute a change: fork the repo, open the pull request on your own fork, then email support@gumroad.com with a link to it. We read every submission and merge the ones we want on our side, keeping your authorship — though we can't promise a reply to each one or a timeline.
Your PR still has to meet the same bar as our own work — visual evidence for anything user-facing, QA steps, test results, and an AI disclosure naming the model. See CONTRIBUTING.md for the full guidelines and the substitutions that apply when working from a fork.
💡 If you're on Windows, follow our Windows setup guide instead.
Before you begin, ensure you have the following installed:
We use Docker to setup the services for development environment.
sudo wget -qO- https://get.docker.com/ | sh
sudo usermod -aG docker $(whoami)
Install a local version of MySQL 8.0.x to match the version running in production.
The local version of MySQL is a dependency of the Ruby mysql2 gem. You do not need to start an instance of the MySQL service locally. The app will connect to a MySQL instance running in the Docker container.
brew install mysql@8.0 percona-toolkit
brew link --force mysql@8.0
# to use Homebrew's `openssl`:
brew install openssl
bundle config --global build.mysql2 --with-opt-dir="$(brew --prefix openssl)"
# ensure MySQL is not running as a service
brew services stop mysql@8.0
apt install libmysqlclient-devWe use imagemagick for preview editing.
brew install imagemagicksudo apt-get install imagemagickFor newer image formats we use libvips for image processing with ActiveStorage.
brew install libvipssudo apt-get install libvips-devWe use ffprobe that comes with FFmpeg package to fetch metadata from video files.
brew install ffmpegsudo apt-get install ffmpegWe use pdftk to stamp PDF files with the Gumroad logo and the buyers' emails.
sudo apt-get install pdftkWhile generating invoices, to convert HTML to PDF, PDFKit expects wkhtmltopdf to be installed on your system. Download and install the version 0.12.6 for your platform.
We use Bundler to install Ruby gems.
gem install bundler
Install gems:
bundle install
Also make sure to install dotenv as it is required for some console commands:
gem install dotenv
Make sure the correct version of npm is enabled:
corepack enable
Install dependencies:
npm install
App can be booted without any custom credentials. But if you would like to use services that require custom credentials (e.g. S3, Stripe, Resend, etc.), you can copy the .env.example file to .env and fill in the values.
If you installed Docker Desktop (on a Mac or Windows machine), you can run the following command to start the Docker services:
make local
If you are on Linux, or installed Docker via a package manager on a mac, you may need superuser access to expose container ports. To do that, use sudo make local instead.
This command will not terminate. You run this in one tab and start the application in another tab.
If you want to run Docker services in the background, use LOCAL_DETACHED=true make local instead.
bin/rails db:prepare
For Linux (Debian / Ubuntu) you might need the following:
apt install libxslt-dev libxml2-devbin/dev
This starts the Rails server, the JavaScript build system, and a Sidekiq worker.
You can now access the application at http://localhost:3000. Seller subdomains and the asset/api hosts use *.localhost (e.g. http://seller.localhost:3000, http://api.localhost:3000) — modern browsers auto-resolve these to 127.0.0.1, so no /etc/hosts edits are needed.
*.localhost over HTTP is treated as a secure context by browsers (so Stripe.js, Stripe Elements, and other secure-context APIs work), but a couple of features that need either HTTPS or a registrable cookie domain don't work in this setup:
ngrok http 3000) and register the resulting hostname in the Stripe Apple Pay dashboard._gumroad_guid, multi-account "switch seller", session) — browsers reject Domain=localhost, so cookies set on localhost:3000 aren't sent to seller.localhost:3000. Test these flows on a single host, or front the app with a local HTTPS reverse proxy on a registrable hostname (e.g. gumroad.test via mkcert) where Domain=.gumroad.test works.Run the full test suite:
bin/rspec
Run a single file or specific test:
bin/rspec spec/requests/dashboard_spec.rb
bin/rspec spec/requests/dashboard_spec.rb:75
Before running tests:
make local # start Docker services (db, Redis, etc.)
RAILS_ENV=test bin/rails db:setup # set up the test database
RAILS_ENV=test bin/rails js:export # generate JS constants for the test environment
Integration specs use Capybara with Selenium driving Chrome. Install Chrome from google.com/chrome.
See docs/testing.md for details on preventing flaky specs, VCR cassettes, debugging widgets, and purchase testing with Stripe/PayPal.
You can log in with the username seller@gumroad.com and the password password. The two-factor authentication code is 000000.
Read more about logging in as a user with a different team role at Users & authentication.
You will need to explicitly reindex Elasticsearch to populate the indices after setup, otherwise you will see index_not_found_exception errors when you visit the dev application. You can reset them using:
# Run this in a rails console:
DevTools.delete_all_indices_and_reindex_all
To send push notifications:
INITIALIZE_RPUSH_APPS=true bundle exec rpush start -e development -f
bin/rails c
bin/rake task_name
We use ESLint for JS, and Rubocop for Ruby. Your editor should support displaying and fixing issues reported by these inline, and CI checks these on every push.
If you'd like, you can run git config --local core.hooksPath .githooks to check for these locally when committing.
fork())objc[11912]: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called.
objc[11912]: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug.
This issue occurs on macOS due to how the fork() system call interacts with multithreaded Objective-C applications—commonly triggered when Spring is enabled during testing.
Temporarily disable Spring before running your tests to avoid this error.
export DISABLE_SPRING=1
bin/rspec spec/requests/balance_pages_spec.rb
This will disable Spring for the current session, allowing the tests to run without triggering the fork()-related crash.
(top 30 of 116)
Ruby
80.2%
TypeScript
14.6%
HTML
4.4%