This app installs itself through a guided setup wizard in the browser — you don't need to run Artisan commands by hand (though you can if you prefer, see Manual installation below).
Before you start
Requirements the wizard checks for you:
- PHP 8.3 or newer
- PHP extensions:
mbstring,pdo_mysql,bcmath,openssl,gd,zip,curl,fileinfo,intl - Write permission on:
storage/framework/cache,storage/framework/sessions,storage/framework/views,storage/logs,bootstrap/cache - A MySQL or MariaDB database (create an empty database and a user with full privileges on it before you start)
Redis is not required. If it isn't available, the app runs its cache and queue on the database driver instead — this is intentional, since most shared hosting doesn't offer Redis. The requirements page shows Redis as informational only; it never blocks installation.
Steps
Upload the files to your host (or clone the repo) and visit your domain. You'll land on /install automatically — every page redirects there until installation finishes.
- Requirements — the checklist above, checked live. Fix anything red before continuing.
- License — enter the purchase code from your CodeCanyon order confirmation. This is verified against the license server and stored against your domain. A weekly background check (
license:verify— see Running scheduled tasks below) re-verifies it afterward; a transient license-server outage never affects anything. If it's ever definitively rejected (revoked/refunded), you'll see a warning banner immediately, but the app keeps working for a grace period (14 days by default,LICENSE_GRACE_PERIOD_DAYSin.env) before switching to read-only — never an immediate hard lockout. - Database — host, port, database name, username, password. The wizard tests the connection before continuing, then writes these into your
.envfile and runs migrations for you. Make sure the database user has privileges to create tables — a migration failure here is reported back on this page (with PHP's own execution limit raised for the request) so you can fix credentials/privileges and retry without losing progress; migrations that already succeeded are skipped on retry. On some shared hosts, a web server timeout (nginx/Apache proxying to PHP-FPM) can still cut the request short even though PHP itself would keep going — if this step reliably times out, runphp artisan migrate --forceonce by SSH/terminal instead, then continue the wizard from the Mode step. - Mode — choose Standalone (you're running this for your own business) or SaaS (you intend to resell access to your own customers). See Standalone vs. SaaS mode — this is worth reading before you pick, though it can be changed later from the landlord panel if you choose SaaS.
- Business & admin account — your business name, base currency, locale, and your own name/email/password. Finishing this step creates your workspace, seeds default data (currencies, roles, a starting chart of accounts, CRM pipeline stages, a default tax rate), creates your account with the Owner role, and logs you straight in.
That's it — you'll land on your dashboard, and then the onboarding wizard walks you through branding and inviting your team.
If you chose SaaS mode, the same email/password you just set also becomes your first landlord login (/landlord/login) — see The landlord panel.
Manual installation
If you'd rather set things up yourself before running the wizard (or you're scripting a deployment):
composer install --no-dev --optimize-autoloader
cp .env.example .env
php artisan key:generate
# Set DB_* in .env, then:
php artisan migrate
npm install && npm run build
You still need to go through the in-browser wizard once (starting at the Mode step, since requirements/license/database are already satisfied) to create your tenant and admin account — there's no separate artisan tenant:create command, by design; the wizard is the one supported path so that every tenant is bootstrapped identically (same seeders, same order) whether it's created this way, through the landlord panel, or through public self-registration.
Re-running the installer
Once installed, a lock file at storage/installed.lock prevents the wizard from running again — visiting /install just redirects to your dashboard. If you genuinely need to reinstall from scratch (a fresh demo, a staging reset), delete that file and drop/recreate your database; the wizard doesn't undo data, it only unlocks itself.
Running scheduled tasks
Three things in this app only happen if Laravel's scheduler is actually running: recurring invoices get generated daily, sent quotes past their expiry date get marked expired daily, and the license gets re-verified weekly. None of these fire on their own — add one crontab entry on your server:
* * * * * cd /path-to-your-app && php artisan schedule:run >> /dev/null 2>&1
That's the only line you need — Laravel's scheduler itself decides when each individual task actually runs (daily/weekly/etc.), the cron entry just needs to check in every minute. Most shared-hosting control panels (cPanel, Plesk) have a "Cron Jobs" section where you paste this in directly, no SSH required.
Running a queue worker (optional)
By default (QUEUE_CONNECTION=sync in .env.example), emails and notifications send inline, during the request that triggers them — no separate process to run, which is why this is the default for shared-hosting buyers who can't run a persistent background process.
If you're on a VPS or similar and want these to send in the background instead (so a customer isn't waiting on an email round-trip when, say, sending an invoice), switch to QUEUE_CONNECTION=database and keep a worker running continuously. A process supervisor (not shared hosting's cron, which only checks in periodically) is the right tool for this, since the worker itself needs to stay running between jobs. Supervisor is the common choice on Linux:
; /etc/supervisor/conf.d/erp-queue-worker.conf
[program:erp-queue-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /path-to-your-app/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
numprocs=1
user=www-data
redirect_stderr=true
stdout_logfile=/path-to-your-app/storage/logs/queue-worker.log
stopwaitsecs=3600
Then: supervisorctl reread && supervisorctl update && supervisorctl start erp-queue-worker:*. The --max-time=3600 flag restarts the worker hourly, which is cheap insurance against gradually-accumulated memory growth in long-running PHP processes — Supervisor's autorestart brings it straight back up.
Backing up your data
Nothing here is bundled or run automatically — back up on whatever schedule matches how much data you're comfortable losing. Two things matter:
- The database — everything business-critical (tenants, CRM/sales/inventory/accounting records, users) lives here. A daily
mysqldump, or your hosting provider's own automated MySQL backup if it offers one, covers this. storage/app— tenant logos and any uploaded attachments live on disk here, not in the database. Back this up alongside the database; a database-only backup will restore a business's records but lose their uploaded files and branding.
If you want this automated rather than a cron'd mysqldump script, spatie/laravel-backup is a well-maintained option that covers both the database and storage/app and can ship backups off-site (S3 and others). It's not bundled here, to keep the default install free of opinions about where you back up to — add it yourself if you want it (composer require spatie/laravel-backup, then follow its own setup docs).
← All Coravo – AI CRM & ERP Software for Growing Businesses documentation