# Contributing to Marble Thanks for your interest in contributing! This guide explains how to get Marble running locally and how to submit high-quality contributions. ## Prerequisites Before you start, make sure you have the following installed or available: - **Node.js** ≥ 22.13.0 - **pnpm** 12.6.0 (install with `npm i -g pnpm@12.6.0`) - **PostgreSQL** database (we use [Neon](https://neon.tech) in examples) - **Redis** database (we use [Upstash](https://upstash.com)) - **Google** and **GitHub** OAuth apps (for authentication) - **Cloudflare** account with R2 enabled (for media uploads) - **Optional**: [Polar](https://sandbox.polar.sh) sandbox account if you want to test payments --- ## Structure This repository is a monorepo and is structured as follows: ```text / ├── 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 ``` ### Apps This directory contains the source code for all related applications: - **api**: [Hono](https://hono.dev) REST API for content delivery - **cms**: [Next.js](https://nextjs.org) app for the dashboard - **docs**: [Mintlify](https://mintlify.com) documentation site - **web**: [Astro](https://astro.build) app for the marketing website ### Packages Packages contain internal shared modules used across different applications: - **db**: Drizzle schema and database clients shared by the `cms`, `api`, and `jobs` apps - **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 `cms` app - **utils**: Shared utilities used across apps and packages ## Getting Started 1. [Fork](https://github.com/usemarble/marble/fork/) this repository to your own account - Visit Marble repository - Click the "Fork" button in the top right - [Clone](https://help.github.com/articles/cloning-a-repository/) the fork to your local device. ```bash git clone https://github.com/YOUR-USERNAME/marble.git cd marble ``` - add the original repo as upstream ```bash git remote add upstream https://github.com/usemarble/marble.git ``` 2. Install Dependencies ```bash pnpm install ``` 3. Configure Environment Variables Each app/package that uses environment variables has an example env file. You’ll need to copy and fill those out: ```bash cp apps/api/.env.example apps/api/.env 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](https://sandbox.polar.sh) sandbox account and fill in the POLAR_* variables. Set the Polar webhook endpoint API version to `2026-04`; webhook versions are configured independently from API requests. 4. Database Setup ### Option 1: Use Neon (Hosted) We use Neon for the database. Create a Neon project and copy your connection string for Drizzle (ensure it includes `sslmode=require`). - Paste it into the relevant env files: Example: ```bash DATABASE_URL="postgresql://:@/?sslmode=require" ``` - Paste it into the relevant env files: - `apps/api/.env` → `CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE=` - `packages/db/.env` → `DATABASE_URL=` - Run migrations: ```bash pnpm db:migrate ``` ### Option 2: Use Docker (Local) Prerequisites: Docker Desktop (macOS/Windows) or Docker Engine + Docker Compose v2 (Linux). Start a local Postgres and run migrations: ```bash # 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_URL` in `packages/db/.env`: Example: ```bash DATABASE_URL=postgresql://usemarble:justusemarble@localhost:5432/marble ``` `apps/api/.env.example` already points `CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE` at this database. 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 `5432` using the credentials from the compose file. - Persist data in the `marble_pgdata` Docker 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: ```bash pnpm docker:logs # follow DB logs pnpm docker:down # stop containers pnpm docker:clean # stop and remove volumes (DESTROYS local data) ``` ### Google OAuth Create a project in the Google Cloud Console. Follow [the first step](https://www.better-auth.com/docs/authentication/google) in the Better Auth documentation to set up Google OAuth and set the values for `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET`. ### GitHub OAuth If you would rather use github you can follow [the first step](https://www.better-auth.com/docs/authentication/github#get-your-github-credentials) in the better auth docs to setup Github oAuth and set the environment values for `GITHUB_ID` and `GITHUB_SECRET` ### Set up Cloudflare R2 for media uploads 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_KEY` and `CLOUDFLARE_ACCESS_KEY_ID` respectively ## Set up Redis ### Option 1: Use Upstash Redis Marble uses Redis for rate limiting, session caching, and analytics. We use [Upstash](https://upstash.com) for serverless Redis. Here's how to set it up: - Go to [Upstash Console](https://console.upstash.com) 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_URL` in your environment files - Copy the "UPSTASH_REDIS_REST_TOKEN" value and set it to `REDIS_TOKEN` in your environment files You'll need to add these to: - `apps/api/.env` → `REDIS_URL=` and `REDIS_TOKEN=` ### Option 2: Docker (Local) Use the repository's Docker Compose services to run Redis locally (native Redis + Upstash-compatible HTTP bridge): ```bash # from repo root pnpm docker:up pnpm docker:ps ``` Expected Redis services: - `redis` on `localhost:6379` - `serverless-redis-http` on `localhost:8079` Set these in your env files: - `apps/api/.env` → `REDIS_URL=http://localhost:8079` and `REDIS_TOKEN=justusemarble` These values match the local defaults in: - `apps/api/.env.example` - `docker-compose.yml` (`SRH_TOKEN=justusemarble`, `8079:80`, `6379:6379`) Stop services when done: ```bash pnpm docker:down ``` ## Running the Apps From the root you can run all apps ```bash pnpm dev ``` or just one ```bash pnpm cms:dev pnpm api:dev pnpm web:dev pnpm docs:dev ``` To test queued work (webhooks, exports), run the api and jobs Workers in one local session so messages the api sends reach the jobs consumers: ```bash pnpm workers:dev ``` ## Contributing to docs The documentation lives in `apps/docs` and is built with [Mintlify](https://mintlify.com). To contribute: ### Prerequisites - Node.js v22.13.0+ (same as the main project) - No database, Redis, or OAuth setup required ### Option A: From the monorepo root (recommended) After `pnpm install`, run: ```bash pnpm docs:dev ``` This uses the Mintlify CLI from the workspace—no global install needed. Docs preview at `http://localhost:3000`. ### Option B: Manual setup If you prefer the CLI globally: ```bash pnpm add -g mint cd apps/docs mint dev ``` ### Port conflicts If port 3000 is in use (e.g. by another app), use a different port: ```bash mint dev --port 3333 ``` ### Useful commands From `apps/docs`: - `mint broken-links` — Find broken internal links - `mint a11y` — Check accessibility (contrast, alt text) - `mint validate` — Validate the build (useful for CI) ### Project structure See [apps/docs/README.md](../apps/docs/README.md) for the docs file layout. ## Agent skills (optional) The repo root has a [`skills-lock.json`](../skills-lock.json) file that pins [Agent Skills](https://skills.sh/) (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: ```bash npx skills experimental_install ``` `experimental_install` is the current CLI command that reads `skills-lock.json`. Adding a package updates the lockfile — for example: ```bash 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-practices ``` If you add or change skills for the team, commit the updated `skills-lock.json`. ## Making changes 1. Create a new branch for your changes ```bash git checkout -b feature/your-feature ``` 2. Before committing your changes make sure to run the lint and format commands (via ultracite/Biome) to catch any issues ```bash pnpm format ``` or if you would rather fix issues yourself, run the following to list problems without fixing ```bash pnpm lint ``` 3. Test your changes and make sure they work and run a build ```bash pnpm build ``` 4. If your build succeeds you can go ahead and make a commit using conventional [commit messages](https://www.conventionalcommits.org/en/v1.0.0/) ```bash git commit -m "fix(cms): fix sidebar overflow issue" ``` ## Pull Request Guidelines - 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](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-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. ## Code Style - 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 ## Reporting Issues - 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 ## Need Help? Feel free to open an issue for questions or join our [discord](https://discord.gg/gU44Pmwqkx). Thank you for contributing!