Skip to main content
A merchant should brand once and have it show up everywhere a customer sees them. The appearance builder is a Lemon-Squeezy-style visual editor backed by a draft/publish config that lives in the core schema’s StoreConfig.

One config, six surfaces

The six surfaces (general, store, checkout, overlay, portal, email) are the tabs in the builder. general holds the brand tokens that cascade everywhere; each other tab overrides locally.

Draft, publish, discard

Edits are staged in draft and only go live on Publish, exactly like a CMS: A PATCH to a surface deep-merges into the draft (so toggling one portal permission doesn’t wipe the rest); hasUnpublishedChanges is computed by comparing draft to published, driving the “Unpublished changes” badge. The merge is bounded — surface level plus one nested level, which is all the config shape needs, so there’s no unbounded recursion. Verified live: per-surface deep-merge including nested permissions, publish, and discard-reverts-to-published.

The portal permissions live here

The customer portal asks this config what it’s allowed to offer — portal.permissions.cancelSubscription, pauseSubscription, updatePlan, etc. A merchant who disables cancellation in the builder disables it in the portal. Configuration and enforcement are the same source of truth.

Powered by RBAC

Editing appearance needs the appearance:manage permission (owner, admin, and developer roles have it) — see the security model. The published config is read by the public surfaces without auth; only the editing is gated. Next: the security model.