A complete multi-tenant Real Estate CRM SaaS system built with pure PHP and MySQL. No frameworks, lightweight, and beginner-friendly.
- Multi-tenant Architecture - Complete tenant isolation with subscription management
- Lead Management - Capture, track, and convert leads
- Client Management - Manage buyers, sellers, landlords, and tenants
- Property Listings - Residential, commercial, and land properties
- Deal Pipeline - Kanban-style deal tracking
- Task Management - Assign and track tasks with reminders
- Activity Tracking - Calls, meetings, emails, and notes
- File Attachments - Secure file uploads with validation
- Subscription System - Plans, quotas, and billing
- REST API - Public API for lead generation
- Role-based Access - Platform admin, tenant admin, and agents
- Responsive Design - Clean, mobile-friendly interface
- PHP 7.0 or higher
- MySQL 5.7 or higher
- Apache with mod_rewrite enabled
- PHP Extensions: PDO, pdo_mysql, mbstring
git clone https://github.com/ahmedsaadawi13/SplashEstateCRM.git
cd SplashEstateCRMCopy the example environment file:
cp .env.example .envEdit .env and update your database credentials:
DB_HOST=localhost
DB_NAME=splashestate_crm
DB_USER=root
DB_PASS=your_password
BASE_URL=http://localhost/SplashEstateCRM/publicCreate a new MySQL database:
CREATE DATABASE splashestate_crm CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;Import the database schema:
mysql -u root -p splashestate_crm < database.sqlMake the storage directory writable:
chmod -R 755 storage/
mkdir -p storage/uploads
chmod -R 777 storage/uploadsEnsure your Apache configuration allows .htaccess overrides:
<Directory "/var/www/html/SplashEstateCRM">
AllowOverride All
Require all granted
</Directory>Restart Apache:
sudo service apache2 restartOpen your browser and navigate to:
http://localhost/SplashEstateCRM/public
- Email: admin@splashestate.com
- Password: admin123
- Email: john.smith@sunsetrealty.com
- Password: agent123
- API Key:
sk_test_4eC39HqLyjWDarjtT1zdp7dc11111111111111111111111111111111
- Email: lisa.chen@metroproperties.com
- Password: agent123
- API Key:
sk_test_7uH92KfDjkPNxywqR6vbm3ef22222222222222222222222222222222
All API requests require an X-API-KEY header with your tenant's API key.
Endpoint: POST /api/leads/create
Headers:
Content-Type: application/json
X-API-KEY: your_api_key_here
Request Body:
{
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com",
"phone": "(555) 123-4567",
"interest_type": "buy",
"budget_min": 400000,
"budget_max": 500000,
"notes": "Looking for a 3-bedroom house"
}Success Response (201 Created):
{
"success": true,
"message": "Lead created successfully",
"data": {
"lead_id": 123,
"lead": {
"id": "123",
"tenant_id": "1",
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com",
"phone": "(555) 123-4567",
"source": "API",
"status": "new",
"interest_type": "buy",
"budget_min": "400000.00",
"budget_max": "500000.00",
"notes": "Looking for a 3-bedroom house",
"created_at": "2025-01-22 10:30:00"
}
}
}Error Response (401 Unauthorized):
{
"error": "Invalid API key"
}Error Response (422 Validation Error):
{
"error": "Validation failed",
"errors": {
"first_name": "First name is required",
"email": "Invalid email format"
}
}Error Response (429 Too Many Requests):
{
"error": "Lead limit reached. Please upgrade your plan."
}Endpoint: GET /api/leads/index
Headers:
X-API-KEY: your_api_key_here
Query Parameters:
page(optional) - Page number (default: 1)per_page(optional) - Records per page (default: 20, max: 100)status(optional) - Filter by status (new, contacted, qualified, etc.)
Example Request:
GET /api/leads/index?page=1&per_page=20&status=new
X-API-KEY: sk_test_4eC39HqLyjWDarjtT1zdp7dc11111111111111111111111111111111
Success Response (200 OK):
{
"success": true,
"data": {
"leads": [
{
"id": "1",
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com",
"status": "new",
"created_at": "2025-01-22 10:30:00"
}
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 45,
"total_pages": 3
}
}
}Create Lead:
curl -X POST http://localhost/SplashEstateCRM/public/api/leads/create \
-H "Content-Type: application/json" \
-H "X-API-KEY: sk_test_4eC39HqLyjWDarjtT1zdp7dc11111111111111111111111111111111" \
-d '{
"first_name": "Jane",
"last_name": "Smith",
"email": "jane@example.com",
"phone": "(555) 987-6543",
"interest_type": "sell",
"notes": "Wants to sell 4-bedroom house"
}'List Leads:
curl -X GET "http://localhost/SplashEstateCRM/public/api/leads/index?page=1&status=new" \
-H "X-API-KEY: sk_test_4eC39HqLyjWDarjtT1zdp7dc11111111111111111111111111111111"SplashEstateCRM/
├── app/
│ ├── controllers/ # Controllers for all modules
│ │ ├── api/ # API controllers
│ │ ├── AuthController.php
│ │ ├── DashboardController.php
│ │ ├── LeadsController.php
│ │ ├── ClientsController.php
│ │ ├── PropertiesController.php
│ │ ├── DealsController.php
│ │ └── TasksController.php
│ ├── models/ # Data models
│ │ ├── User.php
│ │ ├── Lead.php
│ │ ├── Client.php
│ │ └── ...
│ ├── views/ # View templates
│ │ ├── layouts/
│ │ ├── auth/
│ │ ├── dashboard/
│ │ └── ...
│ ├── core/ # Core MVC classes
│ │ ├── Router.php
│ │ ├── Controller.php
│ │ ├── Model.php
│ │ ├── View.php
│ │ └── Database.php
│ └── helpers/ # Helper functions
│ └── functions.php
├── config/ # Configuration files
│ └── config.php
├── public/ # Public web root
│ ├── index.php # Application entry point
│ ├── assets/
│ │ ├── css/
│ │ └── js/
│ └── .htaccess
├── storage/ # File uploads
│ └── uploads/
├── tests/ # Test files
├── database.sql # Database schema with seed data
├── .env.example # Environment configuration template
├── .htaccess # Root htaccess
└── README.md
Every table includes a tenant_id column for data isolation:
- All queries automatically filter by
tenant_id - API keys are tied to specific tenants
- File uploads are stored in tenant-specific directories
- Subscription quotas are enforced per tenant
- CSRF Protection - Token-based protection for all forms
- Password Hashing - bcrypt via
password_hash() - Input Validation - Server-side validation on all inputs
- SQL Injection Prevention - Prepared statements only
- File Upload Validation - Type, size, and extension checks
- XSS Prevention - Output escaping with
htmlspecialchars() - Session Security - Secure session management
- API Key Authentication - Secure API access
-
Free Trial
- $0/month
- 50 leads
- 10 properties
- 2 agents
-
Professional
- $49/month
- 500 leads
- 100 properties
- 10 agents
-
Enterprise
- $199/month
- Unlimited leads
- Unlimited properties
- Unlimited agents
The system automatically enforces quotas:
- Users cannot create new leads/properties when limits are reached
- API requests return 429 error when quota exceeded
- Upgrade prompts displayed in the UI
- Create model in
app/models/YourModel.php - Create controller in
app/controllers/YourController.php - Create views in
app/views/your_module/ - Add routes (router handles convention-based routing automatically)
Edit public/assets/css/style.css to customize:
- Primary color:
#3498db - Secondary color:
#95a5a6 - Success color:
#27ae60 - Danger color:
#e74c3c
Run the included test suite:
php tests/run_tests.phpTests include:
- Authentication tests
- CRUD operations
- Tenant isolation
- Subscription enforcement
- API endpoint tests
-
Environment Configuration
- Set
APP_ENV=productionin.env - Set
APP_DEBUG=false - Use strong database passwords
- Change all default passwords
- Set
-
Security
- Enable HTTPS (SSL certificate)
- Disable directory listing
- Set proper file permissions (644 for files, 755 for directories)
- Remove or restrict access to
database.sql
-
Database
- Remove seed data in production
- Set up regular backups
- Optimize tables and indexes
-
Performance
- Enable PHP opcode caching (OPcache)
- Configure MySQL query cache
- Set up CDN for static assets
- Enable GZIP compression
-
Monitoring
- Set up error logging
- Monitor disk space for uploads
- Track API usage
- Set up uptime monitoring
<VirtualHost *:80>
ServerName yourdomain.com
DocumentRoot /var/www/splashestate/public
<Directory /var/www/splashestate/public>
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/splashestate_error.log
CustomLog ${APACHE_LOG_DIR}/splashestate_access.log combined
</VirtualHost>server {
listen 80;
server_name yourdomain.com;
root /var/www/splashestate/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}- Verify database credentials in
.env - Ensure MySQL is running
- Check if database exists
- Verify Apache mod_rewrite is enabled
- Check
.htaccessfiles exist - Ensure
AllowOverride Allis set
- Check directory permissions on
storage/uploads - Verify
MAX_UPLOAD_SIZEin.env - Check PHP
upload_max_filesizeandpost_max_size
- Ensure session directory is writable
- Check PHP session configuration
- Clear browser cookies
For issues and questions:
- GitHub Issues: https://github.com/ahmedsaadawi13/SplashEstateCRM/issues
- Documentation: https://docs.splashestate.com
- Email: ahmed.sha3ban13@gmail.com
This project is licensed under the MIT License.
Developed by SplashEstate Team Built with PHP, MySQL, and vanilla JavaScript