Thanks for your interest in contributing! This guide explains how to get Marble running locally and how to submit high-quality contributions.
Before you start, make sure you have the following installed or available:
- Node.js ≥ 20.x
- pnpm ≥ 10.x (install with
npm i -g pnpm) - PostgreSQL database (we use Neon in examples)
- Redis database (we use Upstash)
- Google and GitHub OAuth apps (for authentication)
- Cloudflare account with R2 enabled (for media uploads)
- Optional: Polar sandbox account if you want to test payments
- Optional: QStash for reliable webhook delivery
This repository is a monorepo and is structured as follows:
/
├── apps/
│ ├── api/ → Hono REST API
│ ├── cms/ → Next.js dashboard
│ ├── docs/ → Mintlify documentation
│ └── web/ → Astro marketing site
├── packages/
│ ├── db/ → Prisma schema + client (shared by api & cms)
│ ├── editor/ → Tiptap-based rich text editor
│ ├── email/ → Email templates
│ ├── parser/ → Content parsing utilities
│ ├── tsconfig/ → Shared TypeScript configurations
│ ├── ui/ → shadcn/ui components (shared UI library)
│ └── utils/ → Shared utilities
├── .npmrc
├── package.json
├── pnpm-workspace.yaml
├── README.md
└── turbo.json
This directory contains the source code for all related applications:
- api: Hono REST API for content delivery
- cms: Next.js app for the dashboard
- docs: Mintlify documentation site
- web: Astro app for the marketing website
Packages contain internal shared modules used across different applications:
- db: Prisma schema and client shared between the
apiandcmsapps - editor: Tiptap-based rich text editor used in the CMS
- email: Email templates for notifications and transactional emails
- parser: Content parsing utilities
- tsconfig: TypeScript configurations shared across the monorepo
- ui: shadcn/ui components used in the
cmsapp - utils: Shared utilities used across apps and packages
-
Fork this repository to your own account
-
Visit Marble repository
-
Click the "Fork" button in the top right
-
Clone the fork to your local device.
git clone https://github.com/YOUR-USERNAME/marble.git cd marble- add the original repo as upstream
git remote add upstream https://github.com/usemarble/marble.git
-
-
Install Dependencies
pnpm install
-
Configure Environment Variables
Each app/package that uses environment variables has an example env file. You’ll need to copy and fill those out:
cp apps/api/.dev.vars.example apps/api/.dev.vars cp apps/cms/.env.example apps/cms/.env cp apps/web/.env.example apps/web/.env cp packages/db/.env.example packages/db/.env
You'll need:
-
A Postgres connection string (either neon or use docker to self host)
-
Redis credentials from Upstash (see below)
-
Google and GitHub OAuth credentials (how to get these)
-
A BetterAuth secret
-
Cloudflare R2 credentials for file uploads (see below)
-
Optional: If you want to test payments, set up a Polar sandbox account and fill in the POLAR_* variables.
-
Optional: If you want to test webhook delivery, set up QStash and fill in the QSTASH_TOKEN variable.
-
-
Database Setup
We use Neon for the database. Create a Neon project and copy your connection string for Prisma (ensure it includes
sslmode=require).- Paste it into the relevant env files: Example:
DATABASE_URL="postgresql://<user>:<password>@<host>/<db>?sslmode=require"-
Paste it into the relevant env files:
-
apps/api/.dev.vars→DATABASE_URL=<YOUR_STRING_HERE> -
apps/cms/.env→DATABASE_URL=<YOUR_STRING_HERE> -
packages/db/.env→DATABASE_URL=<YOUR_STRING_HERE> -
Run migrations:
pnpm db:migrate
Prerequisites: Docker Desktop (macOS/Windows) or Docker Engine + Docker Compose v2 (Linux).
Start a local Postgres and run migrations:
# from repo root pnpm docker:up # wait for the DB to be healthy (one of): # pnpm docker:logs # watch for "database system is ready to accept connections" # docker compose ps # ensure STATUS is "healthy" ## Alternatively, if your Docker Compose version supports it: # docker compose up -d --wait pnpm db:migrate
If you’re using the local Docker DB, set
DATABASE_URLin these env files:apps/api/.dev.varsapps/cms/.envpackages/db/.env
Example:
DATABASE_URL=postgresql://usemarble:justusemarble@localhost:5432/marble
Note: These credentials are for local development only. Do not use them in production.
This will:
-
Build (if needed) and start the Postgres container defined in
docker-compose.yml. -
Expose Postgres on port
5432using the credentials from the compose file. -
Persist data in the
marble_pgdataDocker volume. -
Note: If you already have a local Postgres on port 5432, stop it or adjust the port mapping in
docker-compose.yml.Useful commands:
pnpm docker:logs # follow DB logs pnpm docker:down # stop containers pnpm docker:clean # stop and remove volumes (DESTROYS local data)
Create a project in the Google Cloud Console.
Follow the first step in the Better Auth documentation to set up Google OAuth and set the values for GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET.
If you would rather use github you can follow the first step in the better auth docs to setup Github oAuth and set the environment values for GITHUB_ID and GITHUB_SECRET
To use media uploads in Marble, you’ll need to set up a Cloudflare R2 bucket. Here's a step-by-step guide to help you configure everything properly:
-
Go to your Cloudflare dashboard
-
Select your account and navigate to R2 from the sidebar
-
Click "Create Bucket"
-
Name your bucket (e.g. marble-media)
-
Hit 'Create"
-
switch to the settings tab and enable "public development url"
-
copy the value to
CLOUDFLARE_PUBLIC_URL -
Set your bucket name to
CLOUDFLARE_BUCKET_NAME -
Go back to your R2 buckets overview and click "API"
-
from the dropdown select "Use r2 with apis"
-
then copy the api url and set to
CLOUDFLARE_S3_ENDPOINT -
Below the url click "Create api Tokens"
-
Select "Create user API Token"
-
For permissions select "admin read and write"
-
Leave everything else as default and click "Create user API Token"
-
Copy the values to
CLOUDFLARE_SECRET_ACCESS_KEYandCLOUDFLARE_ACCESS_KEY_IDrespectively
Marble uses Redis for rate limiting, session caching, and analytics. We use Upstash for serverless Redis. Here's how to set it up:
-
Go to Upstash Console and sign in or create an account
-
Click "Create Database"
-
Give your database a name (e.g. marble-redis)
-
Select a region close to your primary deployment region
-
Leave the type as "Regional" (free tier)
-
Click "Create"
-
Once created, scroll down to the "REST API" section
-
Copy the "UPSTASH_REDIS_REST_URL" value and set it to
REDIS_URLin your environment files -
Copy the "UPSTASH_REDIS_REST_TOKEN" value and set it to
REDIS_TOKENin your environment filesYou'll need to add these to:
apps/api/.dev.vars→REDIS_URL=<YOUR_URL_HERE>andREDIS_TOKEN=<YOUR_TOKEN_HERE>apps/cms/.env→REDIS_URL=<YOUR_URL_HERE>andREDIS_TOKEN=<YOUR_TOKEN_HERE>
Use the repository's Docker Compose services to run Redis locally (native Redis + Upstash-compatible HTTP bridge):
# from repo root
pnpm docker:up
pnpm docker:psExpected Redis services:
redisonlocalhost:6379serverless-redis-httponlocalhost:8079
Set these in your env files:
apps/api/.dev.vars→REDIS_URL=http://localhost:8079andREDIS_TOKEN=justusemarbleapps/cms/.env→REDIS_URL=http://localhost:8079andREDIS_TOKEN=justusemarble
These values match the local defaults in:
apps/api/.dev.vars.exampleapps/cms/.env.exampledocker-compose.yml(SRH_TOKEN=justusemarble,8079:80,6379:6379)
Stop services when done:
pnpm docker:downQStash provides reliable webhook delivery with automatic retries. This is optional but recommended for production-like webhook testing. QStash is also from Upstash.
-
Go to Upstash Console (same account as Redis)
-
Click on "QStash" in the sidebar
-
If you haven't enabled QStash yet, click "Enable QStash"
-
Once enabled, you'll see your QStash dashboard
-
Scroll down to the "Request Builder" section and find "Current Signing Keys"
-
Copy the signing key token and set it to
QSTASH_TOKENin your environment filesYou'll need to add this to:
apps/cms/.env→QSTASH_TOKEN=<YOUR_TOKEN_HERE>
Note: QStash is used for reliable webhook delivery with automatic retries. Without it, webhooks will still work but won't have retry functionality.
From the root you can run all apps
pnpm devor just one
pnpm cms:dev
pnpm api:dev
pnpm web:dev
pnpm docs:devThe documentation lives in apps/docs and is built with Mintlify. To contribute:
- Node.js v20.17.0+ (same as the main project)
- No database, Redis, or OAuth setup required
After pnpm install, run:
pnpm docs:devThis uses the Mintlify CLI from the workspace—no global install needed. Docs preview at http://localhost:3000.
If you prefer the CLI globally:
pnpm add -g mint
cd apps/docs
mint devIf port 3000 is in use (e.g. by another app), use a different port:
mint dev --port 3333From apps/docs:
mint broken-links— Find broken internal linksmint a11y— Check accessibility (contrast, alt text)mint validate— Validate the build (useful for CI)
See apps/docs/README.md for the docs file layout.
The repo root has a skills-lock.json file that pins Agent Skills (for example Cloudflare, Tiptap, Vercel labs, and Resend-related packages). Installed skill files live under gitignored paths (for example .agents/skills/, .cursor/skills/, and .claude/skills/) so they are not committed.
After cloning, you can restore the same skills from the lockfile from the repository root:
npx skills experimental_installexperimental_install is the current CLI command that reads skills-lock.json. Adding a package updates the lockfile — for example:
npx skills add https://github.com/cloudflare/skills
npx skills add ueberdosis/tiptap
npx skills add vercel-labs/agent-skills
npx skills add resend/email-best-practicesIf you add or change skills for the team, commit the updated skills-lock.json.
-
Create a new branch for your changes
git checkout -b feature/your-feature
-
Before committing your changes make sure to run the lint and format commands (via ultracite/Biome) to catch any issues
pnpm format
or if you would rather fix issues yourself, run the following to list problems without fixing
pnpm lint
-
Test your changes and make sure they work and run a build
pnpm build
-
If your build succeeds you can go ahead and make a commit using conventional commit messages
git commit -m "fix(cms): fix sidebar overflow issue"- Your PR should reference an issue (if applicable) or clearly describe its impact on the project. see how to Link a pull request to an issue
- Include a clear description of the changes
- Keep PRs small and focused. Large PRs are harder to review and may be rejected or delayed.
- Ensure consistency with the existing codebase. Use ultracite (Biome) for linting and formatting.
- Include tests if applicable
- Update documentation if your changes affect usage or API behavior.
- Follow the existing code formatting in the project (use ultracite/Biome for consistency).
- Write clear, self-documenting code
- Add comments only when necessary to explain complex logic
- Use meaningful variable and function names
- Use the GitHub issue tracker
- Check if the issue already exists before creating a new one
- Provide a clear description of the issue
- Include steps to reproduce the issue
Feel free to open an issue for questions or join our discord.
Thank you for contributing!