DevStack Directory — Documentation

DevStack Directory Docs

Complete documentation for installing, configuring, and running your DevStack Directory. No Composer, no Node, no build step — upload and run.

Overview

DevStack Directory is a self-hosted developer tool directory CMS. It supports two listing tiers — free (backlink required, auto-approved on verification) and paid (Polar.sh payment, priority manual review). Everything runs on PHP + SQLite with no external dependencies.

Out of the box you get: user registration with email verification, Google + GitHub OAuth, SMTP email, Polar.sh payments, reCAPTCHA, backlink verification, badge embed codes, OG image + favicon upload, and a full admin panel.

Requirements

✓ Works on

Any shared cPanel/Plesk host, VPS, or dedicated server. InfinityFree, Hostinger, SiteGround, DigitalOcean, Hetzner — all work. Minimum PHP 8.1.

Installation

1
Upload files
Upload all files from the cms_fixed/ folder to your web host's public folder (e.g. public_html/) via FTP or cPanel File Manager. Do not upload the data/ folder — it is created automatically.
2
Set permissions
Set data/ and data/uploads/ to 755 permission. On cPanel, right-click in File Manager → Change Permissions → 755.
3
Visit your domain
Open your domain in a browser. The SQLite database auto-initializes at data/directory.db with sample categories and default settings.
4
Log in to admin
Go to https://yourdomain.com/admin. Default password: admin. You will be prompted to change it on first login.

First setup

After logging in, go to Admin → Settings and configure at minimum:

⚠ Change the admin password

The default password is admin. Change it immediately in Admin → Settings → Admin Password before making the site public.

Admin panel

The admin panel is at /admin. It has five sections accessible from the left sidebar.

Tools

Full list of submitted tools with filters by status and tier. Actions per tool:

Paid listings are marked with a PAID badge so you can prioritise them in your review queue.

Categories

Create, edit, and delete categories. Each category has a name, slug, and optional icon (emoji or short text). Categories appear as filter pills on the homepage. Deleting a category does not delete its tools — they become uncategorised.

Users

List of all registered users with email, registration date, verification status, and listing count. You can suspend accounts from this panel — suspended users cannot log in.

Settings reference

SettingDescription
site_nameDisplayed in navbar, page titles, and emails
site_taglineShown on the homepage hero section
site_descriptionMeta description tag for SEO
site_meta_keywordsMeta keywords tag for SEO
badge_domainYour domain without https:// — used for badge URLs and webhook display
paid_priceDisplay price shown on submit page. Must match your Polar product price.
site_faviconUploaded favicon file (png, ico, svg, webp)
site_og_imageUploaded 1200×630 OG image for social sharing
mail_fromSender address for all outgoing email
smtp_hostSMTP server hostname
smtp_portSMTP port — 587 for TLS, 465 for SSL
smtp_userSMTP username (usually your email)
smtp_passSMTP password
smtp_encryptiontls (recommended), ssl, or none
polar_sandboxEnable when testing with Polar sandbox. Disable in production.
polar_project_idCheckout Link ID from Polar (starts with polar_cl_)
polar_webhook_secretWebhook secret from Polar → Settings → Webhooks
recaptcha_enabledToggle to enable/disable reCAPTCHA on forms
recaptcha_site_keyFrom Google reCAPTCHA admin console
recaptcha_secret_keyFrom Google reCAPTCHA admin console
google_oauth_enabledToggle to enable Google OAuth login button
google_client_idFrom Google Cloud Console → Credentials
google_client_secretFrom Google Cloud Console → Credentials
github_oauth_enabledToggle to enable GitHub OAuth login button
github_client_idFrom GitHub → Settings → Developer settings → OAuth Apps
github_client_secretFrom GitHub → Settings → Developer settings → OAuth Apps

Email / SMTP

All transactional emails (verification, password reset) go through SMTP. PHP's mail() is used as a fallback when SMTP is not configured, but most modern hosts require SMTP for reliable delivery.

Recommended: SendPulse

Free tier: 15,000 emails/month. No daily cap. No domain ownership verification required to start.

1
Sign up at sendpulse.com
Create a free account at sendpulse.com
2
Get SMTP credentials
Go to Account → SMTP and generate your SMTP username and password
3
Add a verified sender
Go to Senders → Add sender and verify your noreply@yourdomain.com address
4
Fill in Admin → Settings → Email / SMTP
Host: smtp.sendpulse.com · Port: 587 · Encryption: TLS · Username & Password from step 2 · From address: your verified sender

Testing: Mailtrap

For staging and testing, Mailtrap catches all outgoing emails in a test inbox — nothing reaches real users. Sign up, go to Email Testing → My Inbox → SMTP Settings, copy the credentials, and paste into admin settings. When going live, swap for SendPulse.

Email triggers

EventRecipientDescription
RegistrationNew userVerification link, expires in 24 hours
Login (unverified)UserFresh verification link automatically resent
Resend verificationUserManual resend from profile dashboard (rate limited: once per 5 min)
Password resetUserReset link, expires in 1 hour

Polar.sh payments

Polar.sh handles checkout for Pro listings. The integration supports both sandbox (testing) and production modes via a toggle in admin settings.

Setup steps

1
Create an account
Sign up at sandbox.polar.sh for testing or polar.sh for production. Create an Organization.
2
Create a product
Go to Products → Catalogue → New Product. Set as one-time payment. Price must match the Paid listing display price in your admin settings.
3
Create a checkout link
Go to Products → Checkout Links → New Checkout Link. Assign your product. Set Success URL to: https://yourdomain.com/profile/index.php?paid=1. Copy the checkout link ID (the part after /checkout/ — starts with polar_cl_).
4
Add a webhook
Go to Settings → Webhooks → Add Webhook. URL: https://yourdomain.com/webhook/polar.php. Format: Raw. Events: check order.created and order.paid. Copy the webhook secret.
5
Configure in Admin → Settings → Polar.sh
Check Sandbox mode if testing. Paste Checkout Link ID and Webhook secret. Set paid listing display price.

Payment flow

  1. User selects Pro listing and submits the tool form
  2. They are redirected to Polar checkout
  3. After successful payment, Polar redirects them back to their profile with a success message
  4. Listing shows as PAID PENDING REVIEW in their dashboard
  5. Admin reviews and approves — paid listings are priority
ℹ Going live

Uncheck Sandbox mode in Admin → Settings and update the webhook URL in Polar to your production domain when you're ready to accept real payments.

Social login

Google OAuth

1
Google Cloud Console
Go to console.cloud.google.com → APIs & Services → Credentials → Create Credentials → OAuth 2.0 Client ID. Application type: Web application.
2
Add authorized URIs
Authorized JavaScript origins: https://yourdomain.com
Authorized redirect URIs: https://yourdomain.com/profile/oauth_google.php
3
Copy credentials
Copy Client ID and Client Secret into Admin → Settings → Google OAuth. Enable the toggle.
⚠ Exact match required

The redirect URI in Google Console must match exactly — including .php extension. https://yourdomain.com/profile/oauth_google.php is correct. Propagation after adding/changing URIs can take a few minutes.

GitHub OAuth

1
Create OAuth App
Go to GitHub → Settings → Developer settings → OAuth Apps → New OAuth App. No developer program required — this is free for all accounts.
2
Fill in the form
Homepage URL: https://yourdomain.com
Authorization callback URL: https://yourdomain.com/profile/oauth_github.php
3
Copy credentials
Copy Client ID and generate a Client Secret. Paste into Admin → Settings → GitHub OAuth. Enable the toggle.

OAuth behavior

reCAPTCHA

Google reCAPTCHA v2 ("I'm not a robot" checkbox) on the submission and registration forms.

1
Register your site
Go to google.com/recaptcha/admin. Choose reCAPTCHA v2 → "I'm not a robot" checkbox. Add your domain to the allowed domains list.
2
Copy keys
Copy the Site key and Secret key into Admin → Settings → reCAPTCHA. Enable the toggle.
ℹ Testing note

reCAPTCHA displays on any domain but only validates on registered domains. For staging testing, either register the staging domain or add localhost to the allowed list in Google's console.

Free listings require a backlink from the submitter's site. The system verifies this automatically.

  1. User submits a free listing and provides the URL of the page where their badge/link is placed
  2. The system fetches that page using PHP's curl and searches for a link containing your domain
  3. If found → listing is AUTO-APPROVED and goes live immediately
  4. If not found → listing goes to PENDING for manual review

From Admin → Tools, click Recheck on any free listing to re-run verification. There is also a bulk recheck option in Admin → Settings that processes all pending free listings at once.

Badge system

The badge lets submitters display a "Listed on [YourDirectory]" badge on their site, satisfying the backlink requirement.

After submission, users get two embed options — dark and light variants. The embed code is a fully inline-styled <a> tag that works on any external site without needing CSS from your directory.

Badge endpoint

/badge.php generates an SVG badge image. Parameters: style=dark (default) or style=light

✓ Important

Set the Badge domain in Admin → Settings before launching. Badge URLs and embed codes use this value. If it's empty, the webhook URL and badge previews will break.

Listing flow

Free listing

Submit form
  → System fetches backlink URL
  → Link to your domain found?
      ✓ Yes → Auto-approved → Live immediately
      ✗ No  → Pending → Admin review → Approve / Reject

Pro listing

Submit form
  → Redirect to Polar checkout
  → Payment complete
  → Return to profile (/profile/index.php?paid=1)
  → Success message shown
  → Listing: PAID / PENDING REVIEW
  → Admin sees PAID badge → Priority review → Approve / Reject

Status meanings

StatusMeaning
pendingAwaiting admin review or backlink verification
approvedLive and visible on the directory
rejectedNot approved — user can edit and resubmit

User accounts

Registration methods

Email verification flow

Password reset

Available at /profile/forgot. Sends a reset link valid for 1 hour. Enter email, receive link, set new password.

File structure

/ ├── index.php # Homepage / directory listing ├── submit.php # Tool submission form ├── tool.php # Individual tool page ├── out.php # Click tracking redirect ├── badge.php # SVG badge generator ├── og-image.php # Fallback OG image endpoint ├── check.php # Backlink check endpoint ├── .htaccess # URL rewriting + security │ ├── admin/ │ ├── index.php # Dashboard │ ├── tools.php # Tool management │ ├── categories.php # Category management │ ├── users.php # User management │ ├── settings.php # All site settings │ ├── recheck.php # Bulk backlink recheck │ ├── login.php │ └── logout.php │ ├── profile/ │ ├── index.php # User dashboard │ ├── login.php # Login + registration │ ├── logout.php │ ├── forgot.php # Password reset request │ ├── reset.php # Password reset form │ ├── verify.php # Email verification handler │ ├── resend_verification.php │ ├── oauth_google.php # Google OAuth handler │ └── oauth_github.php # GitHub OAuth handler │ ├── webhook/ │ └── polar.php # Polar.sh payment webhook │ ├── includes/ │ ├── db.php # SQLite connection + schema + migrations │ ├── helpers.php # SMTP, OAuth helpers, backlink, utils │ ├── layout.php # Shared header/footer/nav templates │ └── recaptcha.php # reCAPTCHA widget + verification │ ├── assets/ │ ├── css/style.css # All styles │ ├── LiberationSans-Regular.ttf │ └── LiberationSans-Bold.ttf # Font assets │ └── data/ # Created automatically — keep this safe! ├── directory.db # SQLite database (all your data) └── uploads/ # Uploaded favicons, OG images

Moving to a new domain

The database carries all settings, listings, users, and categories — nothing needs reconfiguring except domain-specific fields.

  1. Upload all files to the new host
  2. Upload data/directory.db and data/uploads/ separately
  3. In Admin → Settings update: Badge domain, From address
  4. In Polar dashboard → Settings → Webhooks: update the webhook URL to the new domain
  5. In Google Cloud Console: add new domain to Authorized JavaScript origins and new callback URI to Authorized redirect URIs
  6. In GitHub OAuth App: update Homepage URL and Authorization callback URL
  7. In Google reCAPTCHA admin: add the new domain to allowed domains
✓ Backup tip

data/directory.db is your entire database — one file. Back it up regularly. All settings, users, listings, and categories are in this file.

Nginx configuration

If using Nginx instead of Apache, add the following server block:

server {
    listen 80;
    server_name yourdomain.com;
    root /var/www/html;
    index index.php;

    # PHP handling
    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    # Block direct access to database
    location /data/ { deny all; }

    # Allow webhook endpoint
    location /webhook/ {
        try_files $uri $uri/ =404;
    }

    location / {
        try_files $uri $uri/ =404;
    }
}

Security notes

Recommended before going live