ReadyEditor ReadyEditor
docs/platform-features.md

Platform Feature List

A reference of every feature the ReadyEditor SaaS platform provides, grouped by area.


Plans

Four active plans plus a placeholder for future custom contracts.

Plan key Label Price (monthly) Price (annual/mo)
dev Dev Free Free
basic Starter $19 $15
pro Pro $59 $47
agency Agency $129 $99

Per-plan limits

Dev Starter Pro Agency
Projects 1 3 10 Unlimited
Domains per project 3 Unlimited Unlimited Unlimited
Team members 1 2 5 15
Plugins 5 core 14 standard All 35 All 35
Hide "Powered by ReadyEditor" badge No Yes Yes Yes
Usage analytics dashboard No No Yes Yes

Limits are enforced server-side: creating a project, adding a domain, or inviting a team member beyond the plan limit returns a validation error.


Editor delivery

  • Versioned releases — editor assets (JS, CSS, plugins) are published to public/releases/{version}/ with a manifest.json that includes SRI integrity hashes for every file.
  • Loader-based install — a single <script> tag with data-api-key bootstraps the full editor: calls /api/v1/editor/init, fetches entitled asset URLs from the manifest, and initializes all enabled plugins.
  • JS API install — alternative to the loader; call ReadyEditorLoader.init({...}) manually from your own bundle after loading the loader script.
  • Legacy multi-tag snippet — supported for backwards compatibility.
  • SRI enforcement — loader validates integrity hashes before executing plugin scripts; disable per-project for local dev via data-require-sri="0" or the dashboard toggle.
  • CDN delivery — asset base URL is configurable via EDITOR_RELEASE_BASE_URL (e.g. a CDN origin); falls back to the app origin.
  • Per-workspace release pinning — workspace owners/admins can select a specific editor version from the last 3 published releases; stored as a tenant_overrides row, takes precedence over the global default.
  • Badge control — the "Powered by ReadyEditor" status-bar link is shown/hidden based on plan: Dev always shows it, all paid plans suppress it. Controlled via hideBadge in the init response, read by the editor UI at runtime.

Init endpoint (GET /api/v1/editor/init)

The central gating endpoint. Called by the loader on every page load.

Validates:

  • API key exists and is active
  • Project is active
  • Origin/domain is in the project's allowed domains list
  • Workspace subscription is not canceled, unpaid, or blocked

Returns on success:

{
  "ok": true,
  "plan": "pro",
  "features": ["clipboard", "formatting_basic", "..."],
  "hideBadge": true,
  "domain": "example.com",
  "language": "en",
  "releaseVersion": "0.0.45",
  "releaseBase": "https://cdn.example.com/releases/0.0.45",
  "assets": {
    "css": [{ "path": "...", "url": "...", "integrity": "sha384-..." }],
    "js": {
      "core": [...],
      "plugins": [{ "name": "clipboard", "path": "...", "url": "...", "integrity": "..." }]
    }
  },
  "sdkVersion": "...",
  "ts": 1234567890
}

Error codes: missing_api_key, invalid_api_key, project_inactive, domain_not_allowed, subscription_blocked.

Caching: s-maxage=60, stale-while-revalidate=300 — CDN-friendly. ETag / 304 support for conditional requests.


Plugin system

  • 35 registered plugins — see plugins.md for the full catalog.
  • Plan-based entitlementsplugin_entitlements table maps plan → plugin names; resolved by EntitlementsResolver.
  • Workspace-level overrides — admin can grant or revoke individual plugins for a workspace via entitlement_overrides (e.g. early access to a plugin not yet in a plan).
  • Project-level toggles — within entitled plugins, workspace owners/admins can disable individual plugins per project via the project settings UI. Stored in project_plugin_settings.
  • Toolbar presetsdata-toolbar-preset="full|basic|minimal" controls which toolbar groups render without changing the entitlement set. See toolbar-presets.md.

Projects

  • Create, list, and view projects scoped to a workspace.
  • Each project has a unique slug, an active/inactive flag, and its own API key(s), domain allowlist, and plugin settings.
  • Project count enforced against the workspace's plan limit on creation.
  • Per-project usage stats available on the project detail page (all plans).

API keys

  • Multiple API keys per project; each has a name, public_key, and is_active flag.
  • Keys are created and revoked from the project settings page.
  • last_used_at is updated on each successful init call.
  • Only workspace owners/admins (canManageProjects) can create or revoke keys.

Domains

  • Exact (example.com) and wildcard (*.example.com) patterns.
  • Domain count enforced against the plan limit on add (Dev: 3 max).
  • Matching performed server-side on every init call by DomainMatcher.
  • Only workspace owners/admins can add or remove domains.

Team management

  • Workspace members with roles: owner, admin, member.
  • Owners can assign/remove the owner role; only owners can demote the last owner (blocked).
  • Invitations — email-based, 7-day expiry, token-based accept URL.
    • Duplicate email check before creating invitation.
    • Audit log written before mail send (so the log is always present even on SMTP failure).
    • Accepting an invitation is atomic: WorkspaceMember insert + invitation update + audit log in one DB transaction.
  • Member count enforced against the plan limit on invitation send.

Subscriptions & billing

  • Stripe integration via Laravel Cashier webhooks.
  • Subscription statuses: dev (free), trialing, active, past_due, canceled, unpaid, blocked.
  • Grace period for past_due: configurable via STRIPE_GRACE_DAYS (default 7 days); editor init remains allowed until grace expires.
  • blocked status set by BlockExpiredGraceSubscriptions scheduled command.
  • Unknown Stripe statuses fail closed (treated as unpaid) — no accidental free access from new Stripe status values.
  • Stripe customer creation is race-condition safe: lockForUpdate() prevents duplicate customers under concurrent requests.
  • Billing page shows current plan, period, renewal date, cancel-at-period-end status, and plan comparison cards.
  • Invoice paid email — workspace owner receives a notification after invoice.paid Stripe webhook.
  • ProcessStripeEvent queued job: 8 retries with exponential backoff, 90-second timeout. Alert email on permanent failure (STRIPE_EVENT_PROCESSING_FAILED_ENABLED).
  • Plan upgrade restricted to dev → paid only (no free self-upgrade exploit between paid tiers).
  • Defensive pull resync — optional background job (STRIPE_PULL_RESYNC_ENABLED) re-fetches subscription state from Stripe as a backstop for missed webhooks.

Usage analytics

Available to Pro and Agency plans only.

Workspace-level (/app/usage)

  • Init events in the last 24h.
  • 30-day daily chart (OK vs error).
  • Top projects by init volume (30d).
  • Top domains (30d).
  • Top error codes (30d).
  • Paginated recent failures table, filterable by error code, domain, or project.

Project-level (project detail page, all plans)

  • 3 stat cards: init total, init last 24h, success rate (30d).
  • Last 5 init events (timestamp, status, domain).
  • "Show details" toggle reveals: 14-day daily rollup table + top domains (7d) + top errors (7d).

Data pipeline

  • Raw events stored in usage_events.
  • RollupUsageDaily command aggregates into usage_rollups_daily, usage_rollups_daily_domains, usage_rollups_daily_errors (runs nightly, re-processable with --since).
  • PruneUsageEvents command prunes old raw events.
  • All timestamps UTC; timezone-safe aggregation via convert_tz.

Auth & accounts

  • Registration with email verification (MustVerifyEmail).
  • Login with rate limiting (10 req/min).
  • Forgot password with rate limiting (5 req/min).
  • Verified middleware on all app routes that mutate data (invitations, profile).
  • Welcome email on registration.
  • Session securitySESSION_SECURE_COOKIE=true required in production.
  • is_admin flag on User model: gates Horizon dashboard and admin routes; hidden from Inertia browser props.

Admin panel (/admin/*)

Restricted to is_admin users via EnsureIsAdmin middleware.

  • Users — list, search, export CSV, toggle is_admin.
  • Workspaces — list, inspect members and subscriptions.
  • Projects — list across all workspaces.
  • Usage — cross-workspace analytics, export.
  • Ops — system health, DB backup status, scheduled command log.
  • Doctor — runs readyeditor:doctor --strict; hidden from regular users.

Editor release management

  • php artisan editor:publish-release {version} — publishes a release from the source tree (legacy/public/ by default) to public/releases/{version}/ with a manifest and integrity hashes.
  • ReleaseCatalog::listSelectable() — returns up to 3 recent stable releases for the workspace selector; filters out non-X.Y.Z versions (test, pre-release).
  • Global default release version set via EDITOR_RELEASE_VERSION env var.
  • Per-workspace override stored in tenant_overrides; previous override deleted before inserting new one (no unbounded growth).

Ops

  • DB backups — scheduled via OPS_DB_BACKUPS_* env vars; configurable disk, retention, prune schedule.
  • Restore drill — optional weekly restore test (OPS_DB_RESTORE_DRILL_ENABLED).
  • Queue — Laravel Horizon (Redis); gate restricted to is_admin.
  • Request IDs — every request gets a X-Request-Id header set by RequestId middleware; propagated into audit logs and usage events.
  • CORSEditorInitCors middleware handles preflight for the init endpoint; EditorUsageCors for usage reporting.

Emails

All emails use the branded Laravel mail theme (logo, brand blue #2563eb, Privacy/Terms links in footer).

Trigger Email
Registration Welcome email
Email change / new account Email verification
Forgot password Password reset link
Team invitation Workspace invitation with accept link
Stripe invoice.paid Invoice paid notification to workspace owner

Security

  • Rate limiting — login (10/min), registration (10/min), forgot-password (5/min).
  • RBAC — owner/admin/member roles enforced by Laravel Policies on all team and project actions.
  • Audit logaudit_logs table records all significant actions: invitation sent/accepted/deleted, member role changed, project domain add/remove, editor release selected, API key created/revoked.
  • SRI — integrity hashes on all editor assets, validated client-side by the loader.
  • Branded error pages — custom 404, 403, 500 pages (no Laravel default stack traces in production).
  • React error boundaryErrorBoundary wraps the Inertia app; catches JS crashes and shows a branded fallback instead of a blank page.
  • OG image — branded public/og-image.png (1200×630) used across all marketing pages.