Building a Custom Member Portal on Shopify
ReCharge ships a member portal out of the box. It works. Subscribers can see their next order date, skip a delivery, update their address, and cancel. The problem is that it looks like ReCharge — not your brand. The typography, the layout, the interaction patterns all belong to the platform, not to the product you've spent years building around.
For subscription brands where the member experience is part of the value proposition — where the portal is a touchpoint, not just a utility — the default is a ceiling. You can't embed brand-specific content, surface personalized recommendations, build retention flows that respond to subscriber context, or connect portal actions to your own operational tooling. What you can do is configure what ReCharge has already decided to support.
This post walks through how to replace the default portal entirely: the architecture that makes it possible, the features worth building first, and the implementation patterns that connect a Shopify storefront to a custom backend without losing the subscription management primitives that ReCharge provides.
Architecture
The core idea is simple: use a Shopify App Proxy to route member portal requests from the storefront to a backend you control. ReCharge remains the system of record for subscriptions — you're not replacing it, you're building a presentation and logic layer in front of it.
Shopify Storefront
Liquid / JS
Symfony Backend
PHP 8 / Symfony 7
ReCharge API
Subscriptions
The request chain: a Shopify App Proxy forwards storefront requests to the Symfony backend, which communicates with ReCharge as needed.
The App Proxy is Shopify's mechanism for proxying requests from a storefront URL — something like /a/portal — to an external server. The response from your server is rendered in-context on the storefront, inheriting the theme layout. Critically, the request carries the Shopify session, so you can authenticate the customer on the backend without a separate login.
The Symfony backend sits in the middle: it validates the proxy signature from Shopify, maps the authenticated customer to their ReCharge subscriber record, calls the ReCharge API for subscription data, applies any business logic your brand needs, and returns a rendered Twig response. The storefront never talks to ReCharge directly.
Key features to build
Not everything in the default portal is worth rebuilding from scratch. Focus first on the features where your brand logic or design requirements can't be met by the platform default.
-
Subscription management — skip, swap, pause
The core portal actions. Building these yourself means you control where they appear in the UX, what confirmation copy they show, whether they're gated behind retention offers, and what happens downstream when a subscriber takes action.
-
Delivery scheduling
Allowing subscribers to shift their next charge date within a defined window reduces cancellations driven by inconvenient timing. Hard to surface clearly in the default portal; straightforward to build as a dedicated view.
-
Payment method updates
Failed payment is one of the top drivers of involuntary churn. A well-designed payment update flow — ideally proactively surfaced when a card is approaching expiry — recovers a meaningful slice of revenue that the default portal leaves on the table.
-
Order history
A full order history — with tracking links, product details, and reorder options — reduces support contact and increases subscriber confidence. Often underpowered in default portals where it competes with subscription management for screen real estate.
-
Product recommendations
The portal is a high-intent surface — a subscriber who just managed their subscription is engaged. Personalized add-on or upsell recommendations in the portal convert at meaningfully higher rates than the same recommendations surfaced through email or ads.
Implementation approach
Three layers need to work together: the Liquid template that embeds the portal in the storefront, the Symfony controller that handles the proxy request, and the Twig view that renders the subscriber data. Here's what each looks like in practice.
templates/page.member-portal.liquid
{% if customer %}
<div id="member-portal" data-customer="{{ customer.id }}">
{% assign portal_url = '/a/portal/dashboard' %}
<div
hx-get="{{ portal_url }}"
hx-trigger="load"
hx-swap="innerHTML"
hx-target="#member-portal"
class="member-portal__container"
>
<div class="member-portal__loading">
Loading your account…
</div>
</div>
</div>
{% else %}
<p>Please <a href="{{ routes.account_login_url }}">log in</a> to manage your subscription.</p>
{% endif %}
src/Controller/ShopifyProxy/MemberPortalController.php
#[Route('/proxy/portal/dashboard', name: 'proxy_portal_dashboard')]
public function dashboard(
Request $request,
ReChargeSubscriptionService $subscriptionService,
CustomerResolver $customerResolver,
): Response {
// Verify the HMAC signature from Shopify
if (!$this->proxySignatureService->verify($request)) {
throw new AccessDeniedHttpException('Invalid proxy signature.');
}
// Resolve the Shopify customer
$customerId = $request->query->get('logged_in_customer_id');
$customer = $customerResolver->resolveByShopifyId((int) $customerId);
if (!$customer) {
return $this->redirectToRoute('proxy_portal_login');
}
// Fetch subscriptions and upcoming charges from ReCharge
$subscriptions = $subscriptionService->getActiveSubscriptions(
rechargeCustomerId: $customer->getRechargeCustomerId()
);
$upcomingCharges = $subscriptionService->getUpcomingCharges(
rechargeCustomerId: $customer->getRechargeCustomerId(),
limit: 3
);
return $this->render('proxy/member_portal/dashboard.html.twig', [
'customer' => $customer,
'subscriptions' => $subscriptions,
'upcomingCharges' => $upcomingCharges,
]);
}
templates/proxy/member_portal/dashboard.html.twig
{% for subscription in subscriptions %}
<div class="subscription-card" data-id="{{ subscription.id }}">
<span class="subscription-card__status">
{{ subscription.status|capitalize }}
</span>
<h3>{{ subscription.productTitle }}</h3>
<dl class="subscription-card__meta">
<dt>Frequency</dt>
<dd>Every {{ subscription.orderIntervalFrequency }} {{ subscription.orderIntervalUnit }}</dd>
<dt>Next charge</dt>
<dd>{{ subscription.nextChargeScheduledAt|date('d M Y') }}</dd>
</dl>
<button hx-post="{{ path('proxy_portal_skip', {id: subscription.id}) }}">
Skip next delivery
</button>
</div>
{% endfor %}
The Liquid template is deliberately thin — its only job is to check session state and fire the initial HTMX request. All logic lives on the backend, which keeps the Shopify theme clean and means portal behavior can be updated without a theme deployment.
HTMX is a natural fit here: partial page updates from the Symfony backend keep the portal feeling responsive without the overhead of a full JavaScript framework, and the backend can return fragments of Twig-rendered HTML for actions like skip, pause, and address update without a full page reload.
Results
Replacing the default portal with a custom implementation built on this architecture produced measurable improvements across the metrics that matter most for a subscription operation.
reduction in voluntary churn in the first 90 days post-launch
drop in subscription-related support tickets routed to the helpdesk
increase in skip usage vs. cancelation when skip was prominently surfaced
The churn reduction came primarily from two changes: surfacing skip and pause as prominent primary actions (rather than buried alternatives), and integrating a reason-based cancelation flow that routed subscribers to targeted save offers before confirming. The support ticket drop came from a clearer order history view and inline payment method update — the two most common reasons subscribers contacted support.
When to build custom vs. use the default
The default ReCharge portal — or Skio's, per our ReCharge vs Skio decision guide — is not a bad product. For brands in their early stages — subscription revenue under $50k/month, small operational team, limited engineering capacity — it does the job. The cost of building and maintaining a custom portal is real, and the payoff requires enough subscriber volume to be meaningful.
The calculus changes when the member portal becomes a brand surface, not just a utility. If your retention strategy depends on contextual save flows, if your product recommendations require subscriber-specific data, if your operational tooling needs to act on portal events in real time — the default portal will constrain you at every turn. The proxy architecture described here removes those constraints without abandoning ReCharge as the subscription engine.
The decision is ultimately about where your leverage is. If the biggest gains in your subscription metrics are still in acquisition or early activation, the portal can wait. If you've optimized the front of the funnel and voluntary churn is the next material problem to solve, a custom member portal built on your own backend is one of the highest-return infrastructure investments you can make.