
September 08, 2026
13 min read
By Kokil Thapa | Last reviewed: September 2026
WooCommerce subscription setup without paid extensions sounds like a shortcut, but for many Nepal SMBs and bootstrapped stores it is a deliberate budget choice. The official WooCommerce Subscriptions extension costs roughly USD 279 per year (~Rs 37,000), and that fee hits before you sell a single recurring box or monthly retainer. WordPress 7.1 and WooCommerce 11.1 give you enough hooks, order data, and scheduling tools to ship recurring billing yourself if you accept ongoing maintenance. This guide walks through a pattern I have used on production stores: custom subscription products, Action Scheduler renewals, and gateway token billing—no paid subscription plugin required.
Why choose WooCommerce subscription setup without paid extensions?
Paid extensions buy speed and support. They also bundle proration, switching plans, synchronized renewals, and customer self-service portals. A DIY stack trades that convenience for control and zero licence cost.
Choose the custom route when recurring revenue is core but volume is still small—under a few hundred active subscribers. Examples include monthly coffee deliveries, SaaS-style hosting retainers, or membership boxes. On florist stores like Petals Qatar, recurring flower plans often start as a pilot before the business commits to extension licences.
Skip DIY when you need complex subscription rules on day one. Mixed carts with one-time and recurring items, gift subscriptions, or automatic upgrades usually justify the official plugin or a move to Shopify subscription apps instead.
What do you need before building recurring billing in WooCommerce?
Start with a stable WooCommerce 11.1 store on PHP 8.3 or higher. Confirm your payment gateway supports saved payment methods or off-session charges. Stripe, PayPal, and Nepal gateways like Khalti often need custom work for token reuse. Read the WooCommerce Subscriptions documentation even if you skip the plugin—it explains lifecycle states you should mirror.
Gateway and currency checklist
- Token or mandate storage enabled on the gateway dashboard.
- Webhook endpoint registered for payment success and failure events.
- NPR pricing configured if you sell locally—see WooCommerce NPR localization.
- SSL active; off-session charges fail on mixed-content or expired certificates.
- Test mode keys isolated from production in
wp-config.phpconstants.
Use the Nepal EMI calculator when presenting monthly plans to customers. Transparent totals reduce chargeback disputes on recurring orders.
How do you register a custom subscription product type?
WooCommerce product types are PHP classes extending WC_Product. A subscription type adds interval fields: billing period, interval count, trial days, and signup fee. Store these as product meta so the cart and checkout can read them without hard-coded SKUs.
Create a small must-use plugin so theme changes do not wipe your logic. Place it at wp-content/mu-plugins/wc-custom-subscriptions.php.
<?php
/**
* Plugin Name: WC Custom Subscriptions (No Paid Extension)
* Requires Plugins: woocommerce
*/
defined( 'ABSPATH' ) || exit;
final class WC_Custom_Subscriptions_Bootstrap {
public static function init(): void {
add_filter( 'product_type_selector', [ __CLASS__, 'add_type' ] );
add_filter( 'woocommerce_product_class', [ __CLASS__, 'product_class' ], 10, 2 );
add_action( 'woocommerce_process_product_meta', [ __CLASS__, 'save_meta' ] );
}
public static function add_type( array $types ): array {
$types['custom_subscription'] = __( 'Custom Subscription', 'wc-custom-sub' );
return $types;
}
public static function product_class( string $classname, string $type ): string {
if ( 'custom_subscription' === $type ) {
require_once __DIR__ . '/includes/class-wc-product-custom-subscription.php';
return 'WC_Product_Custom_Subscription';
}
return $classname;
}
public static function save_meta( int $post_id ): void {
$fields = [ '_subscription_period', '_subscription_interval', '_subscription_trial_days' ];
foreach ( $fields as $key ) {
if ( isset( $_POST[ $key ] ) ) {
update_post_meta( $post_id, $key, wc_clean( wp_unslash( $_POST[ $key ] ) ) );
}
}
}
}
WC_Custom_Subscriptions_Bootstrap::init();
The product class itself extends WC_Product_Simple and sets $this->product_type = 'custom_subscription'. Add admin fields on the General tab for period (day, week, month, year) and interval integer. This mirrors patterns from custom WooCommerce product types but adds subscription-specific meta keys your renewal job will query later.
Cart and checkout hooks
On woocommerce_checkout_create_order_line_item, copy product meta onto the line item. On woocommerce_payment_complete, create a subscription row linked to the parent order ID. Never rely on front-end JavaScript alone to mark an order as recurring—validate interval and price server-side in a checkout callback.
How do you store subscription data and handle renewals?
A dedicated table beats post meta once you pass a few dozen active subscriptions. Query speed and clean status indexes matter when the daily renewal job runs.
CREATE TABLE wp_shop_subscriptions (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
parent_order_id BIGINT UNSIGNED NOT NULL,
product_id BIGINT UNSIGNED NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'active',
billing_period VARCHAR(10) NOT NULL,
billing_interval SMALLINT UNSIGNED NOT NULL DEFAULT 1,
next_payment DATETIME NOT NULL,
payment_token VARCHAR(191) NULL,
gateway_id VARCHAR(50) NOT NULL,
retry_count TINYINT UNSIGNED NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY status_next (status, next_payment),
KEY user_id (user_id)
) DEFAULT CHARSET=utf8mb4;
Create the table on plugin activation with dbDelta(). Status values should stay small: active, on-hold, cancelled, expired. Map them to WooCommerce order statuses where helpful—on-hold after a failed charge, processing on successful renewal.
Schedule renewals with Action Scheduler
WooCommerce ships Action Scheduler—do not depend on visitor-triggered WP-Cron alone. The WordPress Cron documentation explains why low-traffic sites miss scheduled tasks. Disable WP-Cron in production and hit it from the system crontab instead.
# /etc/cron.d/wordpress — run every 5 minutes
*/5 * * * * www-data cd /var/www/store && php wp-cron.php >/dev/null 2>&1
Register a recurring Action Scheduler hook:
add_action( 'init', function () {
if ( false === as_next_scheduled_action( 'wc_custom_process_renewals' ) ) {
as_schedule_recurring_action(
time() + 300,
HOUR_IN_SECONDS,
'wc_custom_process_renewals',
[],
'wc-custom-subscriptions'
);
}
});
add_action( 'wc_custom_process_renewals', 'wc_custom_run_due_renewals' );
The renewal runner selects rows where status = 'active' and next_payment <= NOW(). For each row, call the gateway charge method, then either create a renewal order or increment retry_count. Cap retries at three, then set status to on-hold and notify the customer. Tie email templates into your wider eCommerce email automation stack so failed-payment reminders stay consistent with promotional mail.
How do you charge saved cards without the official plugin?
Gateway integration is the hardest piece. Stripe Billing or Payment Intents with saved payment_method IDs is the most documented path. For Nepal projects I often pair WooCommerce with a custom gateway wrapper—similar in spirit to custom payment gateway development and Laravel Stripe subscription patterns, but implemented inside WordPress hooks.
On first checkout, create a Stripe Customer and attach the PaymentMethod. Store the customer ID and payment method ID in your subscription row—not in plain post meta on the order. Charge off-session on renewal:
function wc_custom_charge_stripe( array $subscription ): bool {
\Stripe\Stripe::setApiKey( STRIPE_SECRET_KEY );
try {
$intent = \Stripe\PaymentIntent::create([
'amount' => wc_custom_get_renewal_amount( $subscription ),
'currency' => 'npr',
'customer' => $subscription['gateway_customer'],
'payment_method' => $subscription['payment_token'],
'off_session' => true,
'confirm' => true,
'metadata' => [
'subscription_id' => $subscription['id'],
'site' => home_url(),
],
]);
return 'succeeded' === $intent->status;
} catch ( \Stripe\Exception\CardException $e ) {
wc_custom_log_renewal_failure( $subscription['id'], $e->getMessage() );
return false;
}
}
Always verify webhook signatures before updating subscription status from Stripe events. Idempotency keys prevent duplicate renewal orders if the gateway retries the same charge. Log every attempt to a custom table or WooCommerce logger channel for support staff.
Creating the renewal order
When a charge succeeds, clone line items from the parent order or rebuild from the product ID. Use wc_create_order(), set the customer, add the subscription product with correct tax class, call payment_complete(), and link the renewal order to the subscription record. Fire woocommerce_order_status_completed only after stock and emails succeed—same discipline as one-time orders on recurring grocery delivery projects.
What features are missing compared to WooCommerce Subscriptions?
Be honest with stakeholders about gaps. DIY covers basic renewals well. It does not automatically deliver everything the paid extension ships.
| Feature | DIY custom build | WooCommerce Subscriptions (paid) |
|---|---|---|
| Recurring product type | You build and maintain it | Built-in with admin UI |
| Proration / plan switches | Manual or custom code | Native support |
| My Account subscription management | Custom templates required | Included endpoints |
| Retry rules and dunning | You define retry logic | Configurable defaults |
| Mixed cart (sub + simple) | Complex; often avoided | Supported |
| Annual licence cost | Rs 0 (+ dev time) | ~Rs 37,000 / ~USD 279 |
| Support on renewal bugs | Your team or agency | WooCommerce.com support |
For a platform comparison across engines, see Magento vs Shopify vs WooCommerce in 2026. WooCommerce wins on ownership cost when you already run WordPress and can maintain custom PHP.
How do you test, secure, and maintain a custom subscription stack?
Treat renewals like payment infrastructure—not a one-time theme tweak. Schedule quarterly reviews if the store depends on recurring revenue.
- Clone production to staging with anonymized customer emails.
- Use gateway test clocks or backdated
next_paymentrows to force renewals. - Confirm Action Scheduler shows no pending failures under WooCommerce → Status → Scheduled Actions.
- Simulate webhook retries and verify idempotency—no duplicate renewal orders.
- Load-test the renewal query with 1,000 fake rows before Black Friday or Dashain sales peaks.
- Document cancel and pause flows for support staff.
Security basics: capability checks on My Account cancel forms, nonces on AJAX endpoints, and encrypted storage for tokens if regulations require it. Expose read-only subscription status through the WooCommerce REST API only after authentication and rate limiting are in place.
Hook renewal and failure events into order notification patterns from custom WooCommerce order notifications. Customers should receive the same branded templates for initial purchase and every rebill.
Engage testing and optimization before launch if the store processes high-value subscriptions. A missed cron on the first renewal batch erodes trust faster than a slow product page.
When subscriber count grows past a few hundred, or when finance needs proration and reporting out of the box, migrate to the official extension or export data to a dedicated billing platform. Design your custom table with export columns from day one so you are not trapped.
For greenfield subscription businesses comparing stacks, read how to start a subscription-based online business. Pair technical setup with clear cancellation policy pages—especially on legal and service portals where recurring retainers are common.
Ongoing maintenance belongs in a support and maintenance retainer. Renewal code touches money; deploy it through the same Git-based workflow you use for the rest of the site. Track WooCommerce 11.x release notes because checkout block changes can break custom product type templates.
If you prefer WordPress specialists handle the build, see WordPress development services and review relevant portfolio case studies. Track rebill revenue in WooCommerce GA4 ecommerce tracking so marketing knows which plans retain customers.
Key Takeaways
- WooCommerce subscription setup without paid extensions needs a custom product type, subscription table, and Action Scheduler renewal job—not theme hacks alone.
- Disable visitor-dependent WP-Cron; use system crontab plus Action Scheduler so renewals run on low-traffic Nepal storefronts.
- Store gateway tokens securely and charge off-session; verify webhooks with signature checks and idempotent order creation.
- Plan for missing proration, mixed carts, and self-service portals—migrate to the paid extension when complexity outgrows your team.
- Test forced renewals on staging with gateway test mode before accepting live NPR recurring charges.
- Budget developer maintenance time; the licence fee you skip becomes ongoing PHP and DevOps ownership.
People Also Ask
Can WooCommerce do subscriptions without a plugin?
Not natively. WooCommerce 11.1 sells one-time and simple recurring-adjacent products, but true subscription lifecycle management requires either the official WooCommerce Subscriptions extension or custom code using product types, scheduled actions, and gateway token billing as described above.
Is WooCommerce Subscriptions free?
No. WooCommerce Subscriptions is a paid extension sold through WooCommerce.com. The DIY approach in this article avoids that licence cost by implementing renewals yourself, which shifts effort to development and long-term maintenance.
What is the best free subscription plugin for WooCommerce?
There is no fully featured free plugin that matches the official extension. Lightweight options exist on WordPress.org, but most production stores either pay for WooCommerce Subscriptions or ship a small custom mu-plugin tailored to one billing interval and one gateway—exactly the pattern outlined here.
How do I migrate from custom subscriptions to WooCommerce Subscriptions later?
Export your custom subscription table to CSV with parent order IDs, next payment dates, and gateway customer references. Map statuses to the paid plugin’s model, import during a maintenance window, and run parallel cron disabled until you verify the first automated renewal on the new system.
Ship recurring revenue on your own terms
WooCommerce subscription setup without paid extensions is a practical path when licence cost matters and your billing rules stay simple. You gain full control over renewal logic, local gateways, and NPR pricing—but you own every cron failure and webhook edge case. Start with one interval, one gateway, and a hard subscriber ceiling; buy the official extension when proration and self-service become non-negotiable. Need help architecting or hardening a custom subscription layer on WooCommerce 11.1? Contact us or explore custom software development for a scoped build and migration plan.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

