Publishing plugins
Complete guide for authors who want to publish plugins on the Arqel Marketplace.
This page describes the publishing pipeline end-to-end: account setup, submission, auto-checks, security scan, manual review, follow-up releases, and stats tracking.
Prerequisites
Before submitting, you need:
- PHP package published on Packagist (
vendor/package) withtype: arqel-pluginincomposer.json. - (Optional) Companion npm package for the React side, published on the npm registry (
@vendor/package). - Public GitHub repository with a
LICENSE(preferably MIT — see allow-list in Security best practices). - At least one tagged release (
v0.1.0or higher, semver-compliant). - Convention compliant — run
arqel:plugin:list --validatelocally to confirm.
If anything is missing, start with the Development tutorial — it covers setup from zero.
Step 1 — Publisher account
Create your account at arqel.dev/marketplace/signup. The form asks for:
- Email (verification mandatory — link expires in 24h).
- Public display name that appears next to each of your plugins.
- GitHub username for automatic repo linking.
- Composer vendor namespace (e.g.,
acme) — you may only submit plugins under that namespace. - (Optional) Stripe Connect onboarding if you intend to publish paid plugins. Can be done later.
The account has three states: unverified → verified → publisher. Only publisher can submit — automatic escalation after email verification + GitHub ownership proof via OAuth.
Step 2 — Submission form
REST endpoint: POST /api/marketplace/plugins/submit (Sanctum auth required, controller PluginSubmissionController).
Minimum payload:
{
"composer_package": "acme/stripe-card",
"npm_package": "@acme/arqel-stripe-fields",
"github_url": "https://github.com/acme/arqel-stripe-card",
"type": "field-pack",
"name": "Stripe Card Field",
"description": "Renderiza o Stripe Elements Card como um Field Arqel pronto para PaymentMethod.",
"screenshots": [
"https://raw.githubusercontent.com/acme/arqel-stripe-card/main/docs/screen-1.png",
"https://raw.githubusercontent.com/acme/arqel-stripe-card/main/docs/screen-2.png"
]
}Validation enforced by SubmitPluginRequest:
| Field | Rule |
|---|---|
composer_package | regex vendor/package, unique in arqel_plugins |
npm_package | optional string |
github_url | valid URL, host github.com (warn if other) |
type | enum field-pack/widget-pack/integration/theme/language-pack/tool |
name | 3-100 chars |
description | 20-2000 chars (warn if < 50) |
screenshots[] | array of public URLs (warn if 0) |
slug | derived from name via Str::slug when absent; uniqueness check |
The 201 response returns {plugin: {...}, checks: {checks: [...], passed: bool}} — you immediately see which auto-checks passed. If passed: false, the plugin still enters with status=pending, but the review queue is alerted and approval time grows.
Step 3 — Auto-checks (no network)
PluginAutoChecker runs 5 defensive checks:
composer_package_format— fail if regex invalid.github_url_format— fail if host is notgithub.com.description_length— warn if < 50 chars.screenshots_count— warn if 0.name_uniqueness— warn if another published plugin already has a similar name.
These checks are instant — no HTTP requests. The intent is to fail fast on obvious errors without holding CI for minutes.
Step 4 — Convention validation
PluginConventionValidator (MKTPLC-003) is the second gatekeeper. It requires your package's composer.json to contain:
{
"name": "acme/stripe-card",
"type": "arqel-plugin",
"description": "Stripe Card Field for Arqel",
"license": "MIT",
"keywords": ["arqel", "plugin", "field", "stripe", "payments"],
"extra": {
"arqel": {
"plugin-type": "field-pack",
"category": "integrations",
"compat": {
"arqel": "^1.0"
},
"installation-instructions": "https://github.com/acme/arqel-stripe-card#installation"
}
}
}Errors (fail):
typeis notarqel-plugin.extra.arqel.plugin-typemissing or outside the enum.extra.arqel.compat.arqelis not a valid semver constraint.extra.arqel.categorymissing or empty.
Warnings (passes but flags):
extra.arqel.installation-instructionsmissing.keywordsdoes not includearqel+plugin.
And the companion npm package.json needs one of these two:
{
"arqel": { "plugin-type": "field-pack" }
}or
{
"peerDependencies": { "@arqel-dev/types": "^1.0" }
}Step 5 — Security scan
After validation, SecurityScanner (MKTPLC-009) creates an arqel_plugin_security_scans row in running and runs four stages:
- Vulnerability lookup — queries
VulnerabilityDatabase(defaultStaticVulnerabilityDatabasereturning empty; host apps can rebind to a real GitHub Advisory Database). Each composer + npm package is checked. - License check — compares
composer.json#licenseagainst the allow-list (MIT,Apache-2.0,BSD-2-Clause,BSD-3-Clause). Anything outside becomes alowwarning. - Suspicious patterns — current placeholder (TODO MKTPLC-009-static-analysis). In the future, static scan for
eval,exec,file_get_contentson user-input URLs, etc. - Severity rollup — takes the maximum across all findings.
Outcome:
| Max severity | Action |
|---|---|
critical | status=failed + auto-delist (status=archived) + dispatch PluginAutoDelistedEvent |
high or medium | status=flagged + alert for manual review |
low or none | status=passed |
If your plugin is flagged, do not panic — open the scan detail page in the admin dashboard, read the findings, and respond with remediation. The human reviewer decides case by case.
Step 6 — Manual review
Plugins with status=pending enter the moderation queue (GET /admin/plugins?status=pending, Gate marketplace.review). The human reviewer:
- Reads description + screenshots.
- Visits
github_urland skims the code (especially the service provider and anyHttp/Process/Storagecalls). - Confirms the plugin does not violate guidelines (no adversarial crypto, no opaque telemetry collection, no abandonware dependency).
- Approves or rejects via
POST /admin/plugins/{slug}/review.
Expected timeline:
| Scenario | Time |
|---|---|
| Auto-checks passed + scan passed + reviewer available | 1-2 days |
| Warnings in auto-checks or scan flagged | 3-5 days |
| Rejected and re-submitted after fix | 5-7 days |
| Large backlog (framework major releases) | up to 14 days |
Approved → status=published + dispatch PluginApproved event → plugin appears in /api/marketplace/plugins. Rejected → status=archived + rejection_reason populated + dispatch PluginRejected. You receive email with the reason (email integration is TBD; for now you check via GET /publisher/plugins).
Step 7 — Follow-up releases
Each new version of your plugin generates a row in arqel_plugin_versions:
POST /api/marketplace/plugins/{slug}/versions
{
"version": "1.2.0",
"changelog": "Adicionado suporte a Stripe Connect Express. Fix em currency=EUR.",
"released_at": "2026-05-15T14:00:00Z"
}Versions follow strict semver. The marketplace does not automatically re-run the security scan on every release (expensive) — but it runs daily via the scheduled arqel:marketplace:scan. You can force a scan from the dashboard when you ship a vulnerability fix.
When publishing a release with a breaking change, update extra.arqel.compat.arqel in the new tag's composer.json. Users on a framework version <compat.arqel will keep receiving the previous version through the Composer resolver — no extra action from the marketplace.
Step 8 — Statistics
The publisher dashboard (/marketplace/publisher/dashboard) consumes four endpoints:
GET /api/marketplace/publisher/plugins
GET /api/marketplace/publisher/plugins/{slug}/installations?days=30
GET /api/marketplace/publisher/plugins/{slug}/reviews
GET /api/marketplace/publisher/payoutsEach returns metrics filtered by publisher_user_id = auth()->id(). Full stats detail lives in MKTPLC-004 — analytics (future delivery).
For paid plugins you also see aggregated purchases + pending payouts:
GET /api/marketplace/publisher/payouts?per_page=20Details in Payments & licensing.
Visual pipeline
flowchart LR
A[Submit form] --> B[Auto-checks 5x]
B -->|fail| Z[422 erro]
B -->|passed/warnings| C[Convention validator]
C -->|fail| Z
C -->|passed| D[Security scan]
D -->|critical| F[failed + auto-delist]
D -->|high/medium| E[flagged → manual review]
D -->|low/nenhum| G[passed → manual review]
E --> R{Reviewer decide}
G --> R
R -->|approve| P[published]
R -->|reject| X[archived + reason]Publisher checklist
Before submitting, confirm:
- [ ]
composer.json#type === "arqel-plugin" - [ ]
extra.arqel.plugin-typecorrect - [ ]
extra.arqel.compat.arqelis a valid semver constraint - [ ]
extra.arqel.categorypopulated - [ ]
keywordsincludesarqel+plugin - [ ]
LICENSEin the repository (MIT preferred) - [ ] README with installation, usage example, screenshots
- [ ] At least 1 tagged GitHub release
- [ ] Package published on Packagist
- [ ] (Optional) companion npm package published
- [ ] Local auto-checks via
arqel:plugin:list --validate
Next steps
- Want to build a plugin from scratch? See the Development tutorial.
- Want to enable payments? See Payments & licensing.
- Plugin rejected for security? See Security best practices.