Keep your best projects always visible on your GitHub profile
Live Demo | Documentation | Self-Hosting
GitHub sorts repositories by "last updated" date. This means your most important projects can get buried when you make a small fix to an old repo or create a new experimental project.
GitPins solves this by "touching" your chosen repositories (create/delete of a short-lived tag ref) in a controlled sequence, so GitHub's recency-based order matches your preferred top list.
- Drag & Drop Ordering - visually arrange your top list
- Manual Sync - "Sync now" from the dashboard (CSRF-protected)
- Scheduled Sync - trigger
/api/syncfrom GitHub Actions or any scheduler you control - Smart Sync - skips when already ordered + touches only the minimal prefix needed
- Single Strategy (Temporary Ref Touch) - no default-branch history noise, no file changes
- Commit Cleanup (Optional) - removes GitPins commits (history rewrite; explicit warning)
- Private Repos Support - include/exclude private repositories in the dashboard list
- Bilingual UI - English and Spanish
- Dark/Light Mode - theme toggle with system default
- Privacy Controls - export your data and delete your account (requires reauth / sudo)
- Admin Allowlist - admins are granted via
admin_accounts(revocable)
- Connect with GitHub (OAuth) - we create an app session cookie (JWT)
- Install the GitHub App - required so GitPins can create/delete temporary refs in selected repos
- Arrange your repos - save your desired top list and settings
- Sync
- Manual: click "Sync now" in the dashboard
- Scheduled: run GitHub Actions or any scheduler that calls
POST /api/sync
GitPins updates the "last updated" timestamp by creating a temporary tag ref that points to HEAD, then deleting that tag immediately.
This keeps your default branch history clean (no [GitPins] commits in main/master).
Conceptually:
git rev-parse HEAD
git push origin HEAD:refs/tags/gitpins-touch-<id>
git push origin :refs/tags/gitpins-touch-<id>See docs/ORDERING.md for the detailed algorithm, including the minimal-prefix optimization and what "single-pass" means in practice.
GitPins is designed with security in mind:
- Minimal GitHub App Permissions - only what is needed for temporary ref updates
- No File Changes - GitPins creates commits that point to the existing tree
- Encrypted Tokens - Access tokens are encrypted with AES-256-GCM
- Open Source - full code transparency
- Create/delete temporary refs in repos you installed the app on
- Read repository metadata (name, stars, etc.) via the GitHub API
- Optionally rewrite history to remove GitPins commits (cleanup feature; explicit confirmation)
- Delete repositories
- Modify tracked files (it uses the existing tree SHA)
- Access other GitHub data (issues, PRs, etc.) unless you grant additional permissions
Cleanup warning: cleanup rewrites git history (force-updates the default branch). This can impact collaborators and forks. Treat it as a dangerous, opt-in operation.
- Framework: Next.js 16 (App Router)
- Language: TypeScript 5
- Database: PostgreSQL (via Prisma 7)
- Auth: GitHub OAuth (GitHub Apps)
- Styling: Tailwind CSS 4
- Drag & Drop: dnd-kit
- Deployment: Vercel
Maintainer docs (recommended starting points):
docs/README.md- documentation index and suggested reading orderdocs/LOCAL_DEV.md- Docker Compose local dev + Neon clonedocs/ARCHITECTURE.md- system overview and flowsdocs/SECURITY.md- threat model, auth, CSRF, admin, sync secretdocs/PRIVACY.md- export + deletion modeldocs/ORDERING.md- ordering algorithm detailsdocs/DEPLOYMENT.md- Vercel/Neon deployment and rollback guidancedocs/ADMIN.md- allowlist, sudo, audit model, bootstrap CLIdocs/API.md- maintainer API overviewdocs/MIGRATIONS.md- Prisma migration notes for existing DBsdocs/TROUBLESHOOTING.md- common failure modes and operational fixes
- Node.js 20+ (recommended) or Docker
- PostgreSQL database
- GitHub App
Neon offers a generous free tier perfect for GitPins:
- Free tier: 0.5 GB storage, 190 compute hours/month
- Serverless: Scales to zero when not in use
- Fast: Optimized for serverless environments like Vercel
- Easy setup: Create a database in seconds
Other compatible options: Supabase, PlanetScale, Railway, or any PostgreSQL provider.
Go to GitHub Developer Settings and create a new app.
For Production:
| Field | Value |
|---|---|
| Homepage URL | https://your-domain.com |
| Callback URL | https://your-domain.com/api/auth/callback |
| Setup URL (optional) | https://your-domain.com/api/auth/setup |
| Webhook URL | Leave empty (not required) |
| Webhook Active | ❌ Unchecked |
For Local Development (Docker default):
| Field | Value |
|---|---|
| Homepage URL | http://localhost:3001 |
| Callback URL | http://localhost:3001/api/auth/callback |
| Setup URL (optional) | http://localhost:3001/api/auth/setup |
| Webhook URL | Leave empty |
| Webhook Active | ❌ Unchecked |
If you run npm run dev directly (no Docker), use http://localhost:3000 instead of 3001.
Configure these permissions in your GitHub App settings:
Repository Permissions:
| Permission | Access Level | Purpose |
|---|---|---|
| Contents | Read and write | Create and delete temporary refs |
| Metadata | Read-only | Read repository list and info |
Account Permissions:
| Permission | Access Level | Purpose |
|---|---|---|
| Email addresses | Read-only | Get user email for identification |
In the OAuth section of your GitHub App:
- Request user authorization (OAuth) during installation: Enabled
- Enable Device Flow: Optional
git clone https://github.com/686f6c61/gitpins.git
cd gitpins
npm installcp .env.example .envEdit .env with your values:
DATABASE_URL="postgresql://..."
DIRECT_URL="postgresql://..."
GITHUB_APP_ID="your_app_id"
GITHUB_APP_CLIENT_ID="your_client_id"
GITHUB_APP_CLIENT_SECRET="your_client_secret"
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----..."
NEXT_PUBLIC_APP_URL="https://your-domain.com"
JWT_SECRET="generate_with_openssl_rand_base64_32"
ENCRYPTION_SECRET="generate_with_openssl_rand_base64_32"npx prisma db push# Development
npm run dev
# Production
npm run build
npm startWhen running locally with a dedicated GitHub App (gitpins-local):
- Use localhost app URLs:
- Docker:
http://localhost:3001 - Non-Docker:
http://localhost:3000
- Docker:
- Set environment variable:
NEXT_PUBLIC_APP_URL=http://localhost:3001(Docker) orhttp://localhost:3000(non-Docker) - Re-authenticate after changing app settings to refresh OAuth grants
cp .env.docker.example .env.docker
docker compose up -dServices:
- App:
http://localhost:3001 - Postgres:
localhost:5432
By default, local Docker runs with:
GITPINS_DISABLE_GITHUB_MUTATIONS=true(safe mode: no GitHub write operations)
# 1) Start only the DB
docker compose up -d db
# 2) Clone production data to local
SOURCE_DB_URL='postgresql://...' ./scripts/clone-neon-to-local.shNotes:
- The script does not hardcode Neon credentials.
- It writes a temporary dump to
/tmpand removes it after restore. - Use this only in secure local environments because data is cloned with full fidelity.
When authenticating, GitPins requests these OAuth scopes:
repo- Access to private repositories (used by the dashboard repo list)user:email- Access to email address
If you're experiencing "Resource not accessible by integration" errors, the user needs to re-authenticate to get a token with the correct scopes.
GitPins provides the sync endpoint (POST /api/sync) but does not automatically create a gitpins-config repository for you.
A common setup is to create a private repo (many people name it gitpins-config) and add a scheduled GitHub Actions workflow that calls your GitPins instance. If you prefer, any scheduler that can send the same HTTP request will work.
name: GitPins - Maintain Repo Order
on:
schedule:
- cron: '0 */6 * * *'
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Sync
run: |
curl -s -X POST "${{ vars.GITPINS_APP_URL }}/api/sync" \
-H "X-GitPins-Sync-Secret: ${{ secrets.GITPINS_SYNC_SECRET }}"You must set:
GITPINS_SYNC_SECRET(repo secret): the per-user secret stored inrepo_orders.syncSecret.GITPINS_APP_URL(repo variable): your app URL (for examplehttps://your-domain.com).
| Issue | Solution |
|---|---|
| "Resource not accessible" | Re-authenticate (logout/login) to refresh OAuth token |
| "Bad credentials" | Check GITHUB_APP_PRIVATE_KEY format (include BEGIN/END lines) |
| Scheduled sync not running | Check the scheduler logs. If you use GitHub Actions, inspect the workflow logs in the repo that hosts it |
| Repos not syncing | Verify the GitHub App is installed on those repos |
- Click the button above
- Add a PostgreSQL database (Neon recommended)
- Configure environment variables
- Deploy!
GitPins includes an admin dashboard for managing users and viewing statistics.
- Grant admin access in the allowlist (
admin_accountstable). Usenpm run admin:access -- grant --github-id <id>or insert directly in DB. - Log in with the granted account.
- Access the dashboard:
Navigate to
/adminafter logging in with the admin account.
- User Statistics: Total users, active users, banned users, sync counts
- User Management: View all users with their config repos and activity
- Ban/Unban Users: Suspend users who violate terms of service
- Delete Users: Remove users from the application (they can re-register)
- Activity Charts: Visual graphs of registrations and syncs over 30 days
The admin panel is protected by:
- Session validation (must be logged in)
- Database allowlist verification (
admin_accounts, whererevokedAt IS NULL) - All admin API endpoints return 403 Forbidden for non-admin users
src/
├── app/ # Next.js App Router
│ ├── api/ # API Routes
│ │ ├── auth/ # OAuth endpoints
│ │ ├── repos/ # Repository management
│ │ └── sync/ # Sync endpoints (called manually or by an external scheduler)
│ ├── dashboard/ # Main dashboard
│ ├── admin/ # Admin panel
│ └── how-it-works/ # Documentation page
├── components/ # React components
├── lib/ # Utility modules
│ ├── crypto.ts # AES-256-GCM encryption
│ ├── session.ts # JWT session management
│ ├── security.ts # CSRF, rate limiting
│ ├── github.ts # GitHub OAuth
│ └── github-app.ts # GitHub App operations
├── i18n/ # Internationalization
└── types/ # TypeScript definitions
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
686f6c61 - @686f6c61
Made with code and mass energy by @686f6c61
If you find this useful, consider giving it a star!




