Skip to content

Marketplace Overview

The Marketplace package connects a Capell installation to the Capell extension marketplace.

  • Browse extension listings in the admin panel.
  • Connect a Capell account to the local installation.
  • Verify public production domains when Marketplace needs external trust.
  • Request extension install and upgrade authorizations.
  • Present explicit marketplace states for purchase, activation, authorized install, installed, and incompatible extensions.
  • Install theme packages independently, using package metadata such as kind, themeKey, and extends.
  • Publish Composer changes through Deployments when that package is available; otherwise show the Composer command.
  • Send installed package snapshots for heartbeat and advisory checks.
  • Store update advisory snapshots and per-user dismissals.

Marketplace is a normal Capell package, but it contributes several admin surfaces at boot:

SurfaceRegistration
Marketplace pagesCapellAdmin::contributeToAdminSurface(AdminSurfaceContributionData::page(...)) for MarketplacePage and MarketplaceExtensionDetailPage.
Installed Extensions alert/actionsMarketplaceExtensionsPageExtender tagged with ExtensionsPageExtender::TAG.
Theme resource header actionThemeMarketplaceHeaderActionExtender tagged with ResourceHeaderActionExtender::TAG.
Extensions header actionsExtensionsPageActionRegistry keys capell-marketplace.open-marketplace, capell-marketplace.connect-account, and capell-marketplace.validate-domain.
Marketplace browserLivewire aliases capell-marketplace.marketplace-extensions-browser and capell-marketplace::marketplace-extensions-browser.
Signed activation checksContainer binding capell.marketplace.activation-verifier points to VerifyMarketplaceSignedActivationAction::run(...).

Third-party packages normally do not extend Marketplace directly. They should publish accurate capell.json metadata, expose package settings or admin pages through Admin extension contracts, and let Marketplace read catalogue/licence state from Capell App.

The installed Extensions page is the single admin surface for installed extension management. Marketplace dynamically contributes a connection alert to that page so admins can see when the site still needs Marketplace setup and connect a Capell account from one primary action.

Marketplace must not add browse controls, install actions, or catalogue content to the installed Extensions page. Marketplace-owned surfaces render Marketplace connection status, account connection, optional public domain verification, diagnostics, heartbeat controls, and catalogue browsing only when that integration is available.

Local installed extensions remain managed from the main Extensions table. Marketplace can request install authorization and provide Composer or deployment guidance, but removal, enable, disable, and bulk uninstall actions stay on the installed Extensions table.

The Marketplace catalogue action is state-aware:

  • Purchase opens the public purchase/detail URL when one is supplied by Capell App.
  • Activate licence opens a confirmation form for the admin’s email, verified domain, licence key, and install options.
  • Install requests a signed install authorization and then either publishes a Composer change through Deployments or shows the Composer command.
  • Incompatible is disabled and explains the platform mismatch.

Capell App owns the catalogue install_state and primary_action contract. The admin UI may override that state only for local facts it can prove, such as an extension already being installed or the local platform being incompatible. Purchase URLs are opened with return context (source, return_url, composer_name, and instance_id) so checkout can send the admin back to the correct installation.

TablePurpose
marketplace_instancesConnected Marketplace instance ID, encrypted signing secret, connection mode, account data, verified domains, and heartbeat timestamp.
marketplace_account_connection_sessionsShort-lived account-link sessions with hashed state/verifier values and callback status.
marketplace_registration_sessionsPublic domain verification challenges, status, expiry, and last error.
marketplace_connection_domainsPer-domain account-link/public-verification status and diagnostics.
marketplace_install_intentsRequested Marketplace installs that may be resolved once Composer/deployment completes.
marketplace_update_advisory_snapshotsLast heartbeat/update response stored locally for admin notices.
marketplace_update_notice_dismissalsPer-user dismissal state for update/security notices.

Instance IDs and signing secrets are secrets. Do not render them in public output or unauthenticated admin copy.

Catalogue, Cache, And Install Authorization

Section titled “Catalogue, Cache, And Install Authorization”

MarketplaceClient is the API boundary. It sends JSON requests to the configured Marketplace API with a 10-second timeout, parses response data into package Data objects, and throws friendly runtime exceptions when the remote API is unavailable or returns the wrong shape.

Catalogue requests are cached under capell-marketplace.marketplace.* keys. The cache payload includes query filters and connection context from BuildMarketplaceConnectionContextAction: instance ID, account ID, claimed domains, publicly verified domains, and preferred domain. This matters because the same extension can show a different action when the site is unconnected, account-linked, publicly verified, already installed, or incompatible.

Install authorization is separate from catalogue browsing. CreateExtensionAcquisitionAction sends the selected listing, licence/email/domain input, and allowed install options to Marketplace. If an instance is known, it records an install intent with a signed payload and the current installed package snapshot. The returned Composer command is guidance only; package installation still happens through Deployments or a human running Composer.

Heartbeat/update checks use PhoneHomeAction. A heartbeat requires:

  • a Marketplace API base URL;
  • a public callback URL resolved from CAPELL_MARKETPLACE_WEBHOOK_URL or APP_URL;
  • a known Marketplace instance ID from marketplace_instances or CAPELL_INSTANCE_ID;
  • network access to /instances/heartbeat.

When heartbeat succeeds, RecordUpdateAdvisorySnapshotAction stores updates/advisories locally so the admin UI can render notices without asking the browser to run Composer.

How does Marketplace domain verification work?

Section titled “How does Marketplace domain verification work?”

Account linking is the default connection path. The admin clicks Connect Capell account, signs in to Capell App, approves the requested site, and returns to the CMS. Capell App requires the account email to be verified before it issues Marketplace instance credentials.

This works for localhost, 127.0.0.1, .test, .localhost, private staging hosts, public staging hosts, and production domains. Capell App stores the claimed domain on the verified account even when the domain is not publicly reachable.

Public domain validation is still available as a stronger production trust signal. It proves that Capell App can reach a public host and that the host serves the expected Marketplace challenge.

When an admin starts verification, Capell sends the requested domain, APP_URL, webhook URL, and any existing instance ID to Capell App. Capell App returns a registration session with a challenge ID, token, public challenge path, optional approval URL, and expiry time.

The validation modal first asks Capell App for a challenge, writes the challenge token to public/.well-known/capell/marketplace/{challengeId}, confirms that local file exists, and records the pending registration in marketplace_registration_sessions.

The next modal state shows the domain and .well-known challenge path. When the admin clicks Approve and verify, Capell checks that the local challenge file still exists before asking Capell App to verify the registration session. Capell App fetches the challenge URL from the public domain and approves the session only when the response contains the expected token.

After approval, the package stores or updates a marketplace_instances row with the Marketplace instance ID, signing secret, verified domain list, and heartbeat timestamp, then removes the local challenge file so stale challenges are not left publicly reachable.

Is domain verification still needed if the admin logs in to Capell App?

Section titled “Is domain verification still needed if the admin logs in to Capell App?”

Not for local development or basic account-linked Marketplace access. Login plus a verified email proves who owns the Marketplace account, and the approval flow links the claimed site to that account.

Public verification is still useful for production purchase-domain binding, webhook delivery, public reachability checks, and any Marketplace policy that requires Capell App to prove it can independently fetch the site.

The admin should not normally need to think about challenge files. Capell creates and serves the challenge during public verification. The file/path details are mainly useful when debugging server, DNS, or CDN issues.

Why does verification fail even though the challenge file exists?

Section titled “Why does verification fail even though the challenge file exists?”

The most common cause is a host mismatch. The challenge controller only serves a pending challenge when the request host exactly matches the domain stored for the registration, after lowercasing and trimming a trailing dot.

For example, a session for example.com will not be served from www.example.com, admin.example.com, localhost, an IP address, or an internal tunnel host.

The second common cause is public web server routing. The path must be reachable from the internet at https://your-domain/.well-known/capell/marketplace/{challengeId} and must not be blocked by redirects, auth middleware, maintenance mode, CDN rules, static file handling, or page cache rewrites.

Expired sessions also fail. Starting a new verification expires older pending or failed sessions for the same domain and writes a fresh challenge file.

How should I debug a domain verification issue?

Section titled “How should I debug a domain verification issue?”

Start with the exact domain shown in the Marketplace page. Check it matches the public host you expect Marketplace to verify.

Confirm the app URL and webhook URL:

Terminal window
php artisan config:show app.url
php artisan config:show capell-marketplace.marketplace.webhook_url
php artisan config:show capell-marketplace.marketplace.base_url

APP_URL must include a valid host. Local hosts such as capell-ruby.test can be account-linked, but public verification must use the exact public host that Capell App can fetch.

Check the current pending session:

select domain, challenge_id, challenge_path, status, expires_at, last_error
from marketplace_registration_sessions
order by id desc
limit 5;

Fetch the public challenge URL from outside the server:

Terminal window
curl -i https://your-domain/.well-known/capell/marketplace/chal_EXAMPLE

The response should be 200 OK, Content-Type: text/plain, and the body should be the stored challenge token. A 404 usually means the host does not match the pending session, the session expired, or the request is not reaching Laravel or the public file.

Check Laravel logs for package messages:

Terminal window
tail -n 100 storage/logs/laravel.log | grep capell-marketplace

The admin notification body is deliberately user-friendly. The log usually keeps the sharper failure reason from the registration or verification request.

Check the configured app URL first:

Terminal window
php artisan config:show app.url
php artisan config:show capell-marketplace.marketplace.base_url

APP_URL must include a host because StartMarketplaceAccountConnectionAction builds the callback URL from it. The callback route sits under the configured admin path and is protected by Filament admin auth.

Check the latest account connection session:

select connection_session_id, claimed_domain, app_url, callback_url, status, expires_at, completed_at, last_error
from marketplace_account_connection_sessions
order by id desc
limit 5;

pending means the admin has not returned from Capell App yet. completing means the callback reserved the session while exchanging the code. failed usually keeps the useful remote or validation error in last_error. expired means the 10-minute session window passed; start again from the Marketplace page.

If the callback says the state is invalid, do not reuse an old approval URL. Start a new connection in the same browser session and complete that new approval URL.

How should I debug catalogue or install authorization?

Section titled “How should I debug catalogue or install authorization?”

Catalogue browsing and installation use different Marketplace endpoints. First confirm that the API URL is the versioned base URL:

Terminal window
php artisan config:show capell-marketplace.marketplace.base_url

Then confirm whether the site has a connected instance:

select instance_id, connection_mode, account_email, verified_domains, last_heartbeat_at
from marketplace_instances
order by last_heartbeat_at desc
limit 5;

If the catalogue loads but the action is purchase, activate licence, or incompatible, use the Marketplace response reason and local compatibility facts. Capell App owns entitlement and install-state decisions. The local UI should only override the response when it can prove a package is already installed or the platform is incompatible.

If the catalogue seems stale during local debugging, clear the local cache:

Terminal window
php artisan cache:clear

Production should prefer the Marketplace browser refresh action or the normal short TTL over broad cache clears.

How should I debug heartbeat and update notices?

Section titled “How should I debug heartbeat and update notices?”

Run the checks in the same order as PhoneHomeAction:

Terminal window
php artisan config:show capell-marketplace.marketplace.base_url
php artisan config:show app.url
php artisan config:show capell-marketplace.marketplace.webhook_url

Then check for a connected instance and recent advisory snapshots:

select instance_id, last_heartbeat_at
from marketplace_instances
order by last_heartbeat_at desc
limit 5;
select source, checked_at, capell_version, metadata
from marketplace_update_advisory_snapshots
order by checked_at desc
limit 5;

If there is no instance, connect the account first. If the webhook URL is null and APP_URL is local or malformed, set CAPELL_MARKETPLACE_WEBHOOK_URL to the public callback URL Capell App should use.

What Marketplace URL should be configured?

Section titled “What Marketplace URL should be configured?”

The default Marketplace API URL is https://capell.app/api/v1. Only set CAPELL_MARKETPLACE_URL when pointing the site at a staging or self-hosted Marketplace API.

If the site reports The route api/registration-sessions could not be found, the URL is probably using the old unversioned API path. Clear the override or set:

CAPELL_MARKETPLACE_URL=https://capell.app/api/v1

Then clear cached config:

Terminal window
php artisan config:clear

Why does Marketplace say a webhook URL is required?

Section titled “Why does Marketplace say a webhook URL is required?”

Marketplace needs a public callback URL before it can send install, update, and instance events back to the site.

By default, the package derives the webhook URL from APP_URL. If the detected URL is not public or is not the address Capell App should call, set CAPELL_MARKETPLACE_WEBHOOK_URL explicitly.

Can I verify localhost, an IP address, or a staging tunnel?

Section titled “Can I verify localhost, an IP address, or a staging tunnel?”

You can account-link localhost, IP addresses, .test, and .localhost domains. Capell App stores them against the verified account as local development aliases.

You cannot publicly verify those local-only hosts because Capell App cannot fetch them from the internet. A tunnel can be publicly verified only when it has a public hostname and that exact hostname is entered as the verification domain. For production Marketplace use, verify the final production domain.

Run diagnostics asks Capell App to check the account-linked domains for public reachability. For local hosts such as capell-ruby.test, diagnostics should report that the domain is account-linked but not publicly verifiable.

For public hosts, diagnostics can store DNS, HTTP, redirect, TLS, challenge, and webhook reachability results against the local marketplace_connection_domains row. The Marketplace status keeps the admin copy short, while the stored diagnostics remain available for troubleshooting and future support tooling.

Starting a new registration for a domain expires older pending or failed registrations for that same domain. Existing verified domains stay on the Marketplace instance.

Clicking Verify on an existing pending or failed registration retries the verification call. If it fails, the session is marked failed and stores the latest user-facing error in last_error.

Why can I browse Marketplace but not install an extension?

Section titled “Why can I browse Marketplace but not install an extension?”

Browsing only needs the catalogue endpoint. Installing needs a connected Marketplace instance so Capell can sign authorization requests and associate purchases, licence activation, and install telemetry with the correct account and site.

Connect a Capell account first, then run diagnostics or heartbeat. If the Marketplace response still blocks an action, use the returned reason: Capell App decides when public verification is required for a specific production action.