Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

WooCommerce Subscription Setup Without Paid Extensions

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.

Custom Subscription ArchitectureCheckoutWC 11.1 orderSubscriptionCustom tableSchedulerAction SchedulerGatewayStripe/KhaltiRenewal Loop (Daily Server Cron)Find due subsCharge tokenRenewal orderEmail + webhook on success or retry
WooCommerce subscription setup without paid extensions — checkout creates a subscription record, then scheduled jobs handle renewals.

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.php constants.

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.

Renewal Cron PipelineServer Cron TriggerEvery hour via crontabwp cron event runReal server cronAction SchedulerProcess due renewalsGateway charge + orderEmail on success or fail
Server cron drives Action Scheduler for reliable WooCommerce subscription renewals without paid extensions.

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.

FeatureDIY custom buildWooCommerce Subscriptions (paid)
Recurring product typeYou build and maintain itBuilt-in with admin UI
Proration / plan switchesManual or custom codeNative support
My Account subscription managementCustom templates requiredIncluded endpoints
Retry rules and dunningYou define retry logicConfigurable defaults
Mixed cart (sub + simple)Complex; often avoidedSupported
Annual licence costRs 0 (+ dev time)~Rs 37,000 / ~USD 279
Support on renewal bugsYour team or agencyWooCommerce.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.

DIY vs Paid ExtensionCustom BuildZero licence feeFull code controlYou own all bugsLimited prorationBest under 200 subsPaid ExtensionPlan switches built-inVendor supportAnnual licence costLess custom logicBest at scaleMigrate when complexity exceeds team capacity
Decision guide for WooCommerce subscription setup without paid extensions versus the official extension.

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.

  1. Clone production to staging with anonymized customer emails.
  2. Use gateway test clocks or backdated next_payment rows to force renewals.
  3. Confirm Action Scheduler shows no pending failures under WooCommerce → Status → Scheduled Actions.
  4. Simulate webhook retries and verify idempotency—no duplicate renewal orders.
  5. Load-test the renewal query with 1,000 fake rows before Black Friday or Dashain sales peaks.
  6. 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.

Pre-Launch Test FlowStaging cloneTest checkoutForce renewalVerifyProduction Gotchas to Catch EarlyWP-Cron never firesToken not savedDuplicate webhooksTimezone drift
Staging tests that prevent failed renewals after WooCommerce subscription setup without paid extensions goes live.

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

It is a DIY recurring billing stack built on WooCommerce 11.1 hooks: a custom subscription product type, a dedicated subscription table, Action Scheduler renewal jobs, and off-session gateway charges using saved payment tokens—no official WooCommerce Subscriptions licence required.

Roughly USD 279 per year, about Rs 37,000, billed before you sell a single recurring product. DIY setup costs Rs 0 in licence fees but requires ongoing developer maintenance time instead.

When recurring revenue is core but active subscriber volume is still small—under a few hundred—and you can accept building and maintaining custom PHP, Action Scheduler jobs, and gateway token billing yourself.

A stable WooCommerce 11.1 store on PHP 8.3 or higher, a payment gateway that supports saved payment methods or off-session charges, SSL on the storefront, webhooks registered for payment events, and test-mode keys isolated from production in wp-config.php constants. Read the official WooCommerce Subscriptions documentation even if you skip the plugin—it defines lifecycle states your custom build should mirror.

Create a must-use plugin at wp-content/mu-plugins/wc-custom-subscriptions.php that adds a custom_subscription entry to the product type selector, maps it to a WC_Product class extending WC_Product_Simple, and saves billing period, interval, and trial days as product meta on the General tab. Using mu-plugins keeps subscription logic alive when the theme changes.

Use a dedicated table such as wp_shop_subscriptions rather than post meta once you pass a few dozen active subscriptions. Index status and next_payment for fast daily renewal queries. Store user_id, parent_order_id, product_id, billing period and interval, next_payment datetime, payment_token, gateway_id, and retry_count. Keep status values small: active, on-hold, cancelled, expired.

WooCommerce ships Action Scheduler—use it instead of visitor-triggered WP-Cron. Disable WP-Cron in production and trigger wp-cron.php from a system crontab every five minutes. Register a recurring Action Scheduler hook such as wc_custom_process_renewals that selects active rows where next_payment is due and processes each charge.

Increment retry_count on each failed off-session charge. Cap retries at three, then set subscription status to on-hold and notify the customer. Map on-hold to a WooCommerce order status where helpful, and tie failure emails into your existing eCommerce email automation so reminders match your branded templates.

On first checkout, create a gateway customer record—Stripe Customer plus PaymentMethod is the most documented path—and store customer ID and payment method ID in your subscription row, not plain order post meta. On renewal, call the gateway off-session: for Stripe, create and confirm a PaymentIntent with off_session true. Always verify webhook signatures and use idempotency keys to prevent duplicate renewal orders.

Clone line items from the parent order or rebuild from the product ID using wc_create_order(), set the customer, add the subscription product with the correct tax class, call payment_complete(), and link the renewal order back to the subscription record. Fire woocommerce_order_status_completed only after stock updates and customer emails succeed—the same discipline as one-time orders.

Your custom build covers basic renewals but not native proration or plan switches, built-in My Account subscription self-service, mixed carts combining one-time and recurring items, gift subscriptions, automatic upgrades, or WooCommerce.com support on renewal bugs. Configurable dunning defaults also require code you write and maintain yourself.

Yes, but they often need custom gateway wrapper work for token reuse and off-session charges, similar in spirit to custom Stripe integration inside WordPress hooks. Confirm token or mandate storage is enabled on the gateway dashboard, register webhook endpoints for success and failure events, and configure NPR pricing so monthly plan totals shown at checkout match what gets charged on rebill.

WP-Cron only fires when someone visits the site, so low-traffic Nepal storefronts miss scheduled renewal batches. A missed cron on the first rebill erodes customer trust faster than a slow product page. System crontab hitting wp-cron.php every five minutes plus Action Scheduler gives predictable renewal processing independent of visitor traffic.

Clone production to staging with anonymized customer emails. Use gateway test mode or test clocks, backdate next_payment rows to force renewals, and confirm WooCommerce → Status → Scheduled Actions shows no pending failures. Simulate webhook retries to verify idempotency—no duplicate renewal orders. Load-test the renewal query with around 1,000 fake rows before peak sales periods like Black Friday or Dashain.

When subscriber count grows past a few hundred, finance needs proration and reporting out of the box, or you require mixed carts, gift subscriptions, and customer self-service portals on day one. Design your custom table with export-friendly columns from the start so migration is not blocked. For greenfield businesses, also compare WooCommerce against Shopify subscription apps before committing long term.

Share this article

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.

Quick Contact Options
Choose how you want to connect me: