What it is
A PHP-based visual web editor delivered as a single-page application. The frontend lives in _studio/index.html — vanilla JavaScript and CSS, no framework. It talks to a PHP API backend that reads and writes real files on disk and stores its own state in SQLite.
The defining constraint: nothing to build and nothing to install. There are no bundlers, no package managers, and no test runner. You drop the _studio folder onto any host that runs PHP and it works.
- Frontend — one HTML file, vanilla JS + CSS, SPA navigation.
- Backend — a PHP API router (
api.php) plus per-feature handler files. - Storage — SQLite for app state; edited sites are plain files on disk.
Directory map
Everything ships inside _studio/. The router dispatches by an action parameter — roughly forty handlers live inline in api.php, and the rest are split into per-feature files under handlers/.
| Path | Purpose |
|---|---|
api.php | API router — dispatches handlers by action; also contains ~40 inline action handlers. |
bootstrap.php | Session, auth helpers, site/user CRUD, safePath(), peRemoteCall(). |
database.php | SQLite ORM — sites, users, activity_log, settings, extension_types, intake_submissions; WAL journal. |
config.php | Master credentials, timeout, allowed extensions. Some endpoints rewrite this file via regex. |
totp.php | Pure-PHP TOTP (RFC 6238) and QR generator — no dependencies. |
handlers/ | Action implementations: auth, sites, users_handler, files, backups, extensions, git, ga, intake, workflows. |
data/ | pe_app.sqlite (auto-created) and workflows.json. |
setup.html + setup.php | First-run setup wizard — SPA mode or classic PHP fallback. |
library.json | Extension catalog of installable entries (some flagged coming_soon). |
extensions/ | Installed PHP extension plugins, each with a manifest docblock. |
_canvas/ · _workspaces/ | Empty stubs reserved for future modules. |
Data model
State lives in a SQLite database at data/pe_app.sqlite, created automatically on first use. The older sites.json and users.json files are legacy and no longer authoritative.
Tables
| Table | Purpose |
|---|---|
sites | Workspaces with environments[] JSON, extensions[], API keys. |
users | User accounts, 4-tier roles, TOTP secrets, site assignments, owner field. |
activity_log | Audit trail of all user actions. |
settings | Key-value config store (allowed_extensions, session_timeout, webhooks). |
extension_types | Registered file-type extensions from plugins. |
intake_submissions | Public client intake form submissions. |
client_requests | Connect feature — tickets and projects. |
request_messages | Messages on Connect requests. |
nexus_servers | Server connections for DirectAdmin management. |
sessions | DB-backed PHP sessions (id, data, username, ip, user_agent, updated). |
roles | Custom role definitions with fine-grained permission grants. |
Sites & environments
Each site carries an environments[] array. An environment has a name, a type of local or remote, a root path (local) or url + api_key (remote), and a branch. New sites start with three: live, staging, and local.
{
"name": "staging",
"type": "remote", // local | remote
"url": "https://staging.example.com",
"api_key": "sk-...", // remote only
"branch": "staging"
}
Users & the role hierarchy
Users sit in a four-tier hierarchy. Each user has an owner field linking them to a parent, so resellers manage account owners, who in turn manage sub-users. The top-level admin is the PE_MASTER_USERNAME.
Permissions (RBAC)
Fine-grained role-based access control with ancestor matching. Default roles:
- admin:
*wildcard — full access to everything. - reseller: files, backups, git, users, extensions, intake, connect.
- account_owner: files, backups, git-read, users-create, connect-basic.
- sub_user: files-read-write, backups, git-read, seo.
Permissions support * wildcards and ancestor matching — files.* automatically grants files.read, files.write, and any future file permission. Custom roles can be created, edited, and deleted by admins; system roles cannot be deleted and the admin role must retain the * wildcard.
Authentication
Login is password-first, then TOTP when required, then a session. TOTP becomes mandatory if the Google Authenticator extension is installed or a TOTP secret is configured.
- Flow: password → TOTP (if enabled) → session. Session regeneration at both steps prevents fixation attacks.
- Two-factor: the
google-authenticator.phpextension makes TOTP mandatory for every user. TOTP enrollment is a two-step process — secret generation with QR code, then code verification before saving. - Brute force: five failed attempts trigger a 15-minute lockout (file-based in temp dir).
- Sessions: custom DB session handler stored in the
sessionstable. Two-hour timeout (PE_SESSION_TIMEOUT), refreshed on each request.SameSite=Lax,HttpOnly,Secure(proxy-aware viaX-Forwarded-Proto/X-Forwarded-SSL). - Impersonation: admins and resellers can impersonate other users via the
X-PE-Impersonateheader. State-changing actions are blocked while impersonating. - IP whitelist: per-user IP whitelist — comma-separated with
*suffix for subnet matching. Enforced on every authenticated request. - Account statuses: users can be
active,disabled,locked, orpending. Non-active accounts are blocked and sessions destroyed. - Rate limiting: sliding window — 60 requests/minute per IP+action (except
checkanddownload_agent). Returns 429 withRetry-Afterheader.
Guards live in bootstrap.php:
requireAuth() // any signed-in user requireAdmin() // master account only requireResellerOrAdmin() // reseller tier and up requireActiveSite() // a site must be selected
API reference
Every call is a POST to api.php?action=<name> with a JSON body. Inline handlers in api.php — including submit_intake, workflows, site_summary, site_activity, diff_backup, and the config actions — are routed before the handler-file includes.
Auth
Sites
Users
Files
Backups
Git
Workflows
Extensions
Google Authenticator
Intake
PublicRate-limitedConnect
Staff+ClientSystem
Adminsubmit_intake is the only public action — it's rate-limited and protected by a honeypot. download_agent injects the active site's API key into pe_api.php via regex replace and serves the result as a download.
File editing constraints
- Allowed types:
html,htm,css,phpby default (PE_ALLOWED_EXTENSIONS), plus any types registered by an extension. - Size limit: 2 MB per file (
PE_MAX_FILE_SIZE). - Path safety:
safePath()blocks traversal — a path must resolve inside the active environment's root. - Backups: every
savewrites a.bakinto/.pe_backups/inside the site root (keeps last 5 per file, skipped forliveenv). - File locking: concurrent editing is prevented with per-file locks (5-minute timeout, scoped to site + file + username). Only the lock owner can unlock.
- Atomic writes: temp file + rename for every save — no partial writes.
- Traversal:
walkFiles()skips dot-files,_studio, and.pe_backups.
Config self-modification
Actions like save_system_config and the setup wizard rewrite config.php in place — preg_replace over the define(...) lines, with temporary *.pe_tmp.* files for atomic writes.
The regex rewrite of config.php is by design. Don't refactor it into a different config mechanism — other endpoints depend on the file staying a set of define() statements.
Remote agent
The remote agent is a single file, pe_api.php, deployed at the root of each remote client site. It authenticates with an X-PE-API-Key header and lets the studio edit that site as if it were local.
curl -X POST https://client-site.com/pe_api.php?action=tree \ -H "Content-Type: application/json" \ -H "X-PE-API-Key: sk-your-secret-key" \ -d '{}'
Supported actions: ping, tree, read, save, linked_css, find_in_files, replace_in_files, images, seo_audit, and git_log.
Deployment
Deploys run over git-ftp via .deploy.sh.
bash .deploy.sh prod # git ftp push bash .deploy.sh staging # git ftp push -s staging bash .deploy.sh init # git ftp init bash .deploy.sh init-staging # first-time staging init
Security
Access is locked down at the web-server level through .htaccess rules.
| Location | Rule |
|---|---|
_studio/.htaccess | Denies .json, .bak, and .pe_tmp.* files. |
_studio/.htaccess | Blocks direct access to config.php and setup.php. |
_studio/.htaccess | Allows api.php, users.php, and setup.php only with an action param. |
data/ · handlers/ | Require all denied — no direct web access. |
Root .htaccess | Blocks _partials/ and all .json / .log / .sh / .md files. |
Server rules are the outer layer; safePath(), the allow-listed extensions, and the auth guards are the inner ones. Never expose data/, handlers/, or raw config to the web.
Connect — ticketing & projects
Connect is the built-in ticketing and project management system. It serves as a knowledge base, support desk, and project tracker — all inside the studio, no third-party tools required.
Tickets & projects
- Two types:
ticketfor support requests andprojectfor tracked work. - Statuses:
new,in_progress,waiting_on_client,resolved,closed— with SLA tracking timestamps (first_response_at,resolved_at). - Priority:
low,medium,high,urgent— auto-prioritization via configurable rules. - Categories: bug, broken_feature, functionality, feature_request, design_ui, performance, seo_content, security, billing, general, tier_2_3, urgent_critical, integration, training.
- Tags & due dates: JSON tag array per request plus optional due date for deadline tracking.
Conversation threads
Each request carries a thread of messages. When a staff member replies the client is notified by email, and vice versa. The first description becomes the first conversation message automatically.
Project stages & merging
- Stages: staff can set a JSON checklist of stages on any request to track multi-step work.
- Convert: tickets can be promoted to projects via
convert_to_project. - Merge: related tickets can be merged — messages and attachments move to the target, the source is closed with a system note.
- Bulk ops: batch-update status, priority, assigned_to, and type across multiple requests. Bulk-delete supported.
Canned responses
Pre-written reply templates that staff can insert into conversations. Full CRUD for canned responses — create, update, delete, and list.
Access control
Staff see all requests; clients see only their own. Public access tokens (reply_token and access_token) allow clients to view and reply without logging into the studio. Discord and email notifications fire on status changes and new messages.
Client intake
The public intake form (/intake) collects structured information from prospective clients and feeds it into the studio for review and account creation.
Submission flow
- Four-step wizard: business info → contact details → SEO & hosting → access credentials.
- 30+ fields: business name, URL, phone, email, address, SEO analysis, hosting provider, DirectAdmin credentials, access notes, and more.
- Rate limited: 3 submissions per 15 minutes per IP address.
- Honeypot: hidden form field catches bots; HTML sanitization and email validation on all inputs.
Intake management
- Statuses:
new→read→archived. Auto-marked read when opened by staff. - Convert to account: a single action creates a user account, a site workspace with a
liveenvironment, and optionally clones files from a template site. Auto-generates a username from the business name (with deduplication) and a random 12-character password.
Nexus — server management
Nexus manages DirectAdmin server connections from within the studio. It provides a unified view of your hosting infrastructure alongside your workspaces.
- Server connections: store and manage DirectAdmin server credentials in the
nexus_serverstable. - DNS management: view and edit DNS zones for domains hosted on connected servers.
- SSL certificates: monitor certificate expiry, issuer, and auto-renewal status across all connected servers.
- Uptime monitoring: health checks on sites and servers with status dashboards.
- Unified dashboard: switch between workspace file editing and server management in the same session.
Extensions & hooks
Extensions are PHP plugin files installed into _studio/extensions/. Each plugin declares a manifest docblock and can register custom file types, expose API handlers, and hook into system events.
Installation & security
- Upload & install: extensions are uploaded via the studio UI. The installer scans for blocklisted PHP functions (
eval,exec,system,passthru,shell_exec,popen,proc_open,pcntl_exec,assert,create_function,base64_decode) and rejects any file containing them. - Manifest: a docblock at the top of the extension file carries the plugin name and version.
- Extension library:
library.jsonis a catalog of available extension entries — browse and install directly from the studio. - Site filtering: extensions can be enabled or disabled per-site. The
*wildcard enables an extension on all sites.
File-type registration
Extensions can register custom file extensions via the extension_types table. Once registered, those file types appear in the file tree and can be edited like any built-in type.
Hook system
Extensions declare hooks with @hook <event> <function> in their docblock. When an event fires, the dispatcher invokes the registered function on each active extension. Hooks are cached for 5 minutes.
file_saved // after a file is saved to disk file_created // after a new file is created from template site_deployed // after git merge deployment completes
Installed extensions
- Google Authenticator: adds mandatory TOTP two-factor authentication for all users.
- TixCo Events & Calendar: event widget and calendar management.
- AI Page Builder: AI-assisted content generation and page building.
Extensions marked coming_soon in the library include SEO Toolkit Pro, Page Speed Optimizer, Form Builder, and AI Content Assistant.
Git & version control
Every site workspace comes with built-in Git — bare repos are auto-created, file saves are auto-committed, and branches map to environments for a natural push-to-deploy workflow.
Bare repo architecture
When a site is created, a bare Git repository is initialized at _workspaces/<siteId>.git and set as the origin for each environment. This bare repo acts as an internal hosting layer — you never push to an external Git host unless you choose to.
Auto-backup on save
Every file save automatically stages, commits, and pushes to the bare origin: Auto-backup: <path> [<username>]. This gives you a full revision history of every edit, tied to the user who made it.
Branch-per-environment
- Live:
masterorlivebranch. - Staging:
stagingbranch. - Local:
localbranch. - Git merge: push changes from staging to live by merging branches. The bare repo acts as the intermediary — for remote targets the operation is proxied through the remote agent.
Per-file history & restore
- File history: view every commit that touched a specific file, across all local environments.
- Restore: restore any file to its content at any commit hash. A
.bakbackup of current content is created before restoring.
Conflict handling
Pull operations return conflict information when branches diverge. The merge workflow keeps the bare repo as the single source of truth, minimizing conflicts between environments.
When a merge deployment completes (e.g. staging → live), the site_deployed hook fires — extensions can react with cache clearing, build steps, or notifications. A configurable deploy webhook URL can also be called.