TierLock

SaaSIntermediateJan 29, 2026

TierLock

Developer-first API for effortless SaaS feature gating.

The Problem

Solo developers and small SaaS teams often struggle to implement robust feature gating based on subscription plans. After integrating Stripe for payments, they're left to build complex, error-prone custom logic within their application to determine what features a user can access. This involves manual checks against Stripe webhooks, managing trial states, and mapping product features to different subscription tiers. This process is time-consuming, wasting an estimated 5-10 hours per feature change, and can lead to inconsistent user experiences or security vulnerabilities if not done perfectly. There's no single source of truth for feature entitlements, forcing developers to scatter access logic throughout their codebase. This directly impacts time-to-market for new features and makes pricing experiments cumbersome and risky, losing potential revenue from delayed launches.

The Solution

TierLock provides a simple, developer-first API and dashboard to define your SaaS features and link them declaratively to subscription tiers. Instead of writing custom `if/else` logic everywhere, developers can make a single, authenticated `tierlock.hasAccess('feature_name', user_id)` API call. The intuitive dashboard allows defining features (e.g., 'Unlimited Projects', 'AI Integrations'), linking them to specific Stripe plan IDs, and managing trial periods or custom entitlements. It integrates seamlessly with existing authentication systems (e.g., NextAuth.js, Supabase Auth) and Stripe webhooks to keep entitlements in real-time sync. TierLock's unique differentiator is its laser focus purely on *feature access as a service*, abstracting away the boilerplate of subscription state management, rather than being a full billing system. This empowers solo developers to ship faster, experiment confidently with pricing, and maintain a clean, scalable codebase.

Tech Stack

Frontend
Next.js 14ReactTailwind CSSshadcn/ui
Backend
Next.js API RoutesSupabase Client (for database interaction)Stripe Webhook SDK
Database
PostgreSQL (Supabase)
APIs
Stripe (Webhooks & API)Resend (Email)

System Architecture

Database Schema

-- For TierLock's own users/admins (using Supabase Auth)
-- CREATE TABLE auth.users ... (managed by Supabase)

-- Customers of the SaaS applications using TierLock
CREATE TABLE customers (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    external_user_id TEXT UNIQUE NOT NULL, -- The user ID from the client's own auth system (e.g., their own auth.users.id)
    stripe_customer_id TEXT UNIQUE,        -- Stripe Customer ID
    email TEXT UNIQUE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE customers IS 'Represents the end-users of SaaS applications using TierLock.';
COMMENT ON COLUMN customers.external_user_id IS 'The unique ID of the user from the client''s own authentication system.';

-- Defined features that can be gated
CREATE TABLE features (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    name TEXT UNIQUE NOT NULL,             -- e.g., 'unlimited_projects', 'ai_integration'
    description TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_features_name ON features (name);
COMMENT ON TABLE features IS 'Defines all possible features that can be gated by TierLock.';

-- Subscription Plans (corresponding to Stripe Product/Price IDs)
CREATE TABLE plans (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    stripe_price_id TEXT UNIQUE NOT NULL,  -- Stripe Price ID (recurring price)
    stripe_product_id TEXT,                -- Stripe Product ID
    name TEXT NOT NULL,                    -- e.g., 'Free', 'Pro', 'Enterprise'
    description TEXT,
    tier_level INT DEFAULT 0,              -- For ordering/hierarchy (e.g., 0=Free, 1=Pro, 2=Enterprise)
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_plans_stripe_price_id ON plans (stripe_price_id);
COMMENT ON TABLE plans IS 'Defines subscription plans, linked to Stripe Price IDs.';

-- Junction table for features and plans (which features are included in which plan)
CREATE TABLE plan_features (
    plan_id UUID NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
    feature_id UUID NOT NULL REFERENCES features(id) ON DELETE CASCADE,
    max_value INT,                         -- Optional: for features with limits (e.g., max_projects=10)
    PRIMARY KEY (plan_id, feature_id)
);
COMMENT ON TABLE plan_features IS 'Links features to plans, defining which features each plan includes.';

-- Customer Subscriptions (linking customers to their active Stripe subscriptions)
CREATE TABLE customer_subscriptions (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
    plan_id UUID NOT NULL REFERENCES plans(id), -- The plan they are subscribed to
    stripe_subscription_id TEXT UNIQUE NOT NULL, -- Stripe Subscription ID
    status TEXT NOT NULL,                  -- e.g., 'active', 'trialing', 'canceled', 'past_due'
    current_period_start TIMESTAMP WITH TIME ZONE,
    current_period_end TIMESTAMP WITH TIME ZONE,
    trial_start TIMESTAMP WITH TIME ZONE,
    trial_end TIMESTAMP WITH TIME ZONE,
    cancel_at_period_end BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    UNIQUE (customer_id, stripe_subscription_id) -- A customer might have multiple subscriptions if a complex use case
);
CREATE INDEX idx_customer_subscriptions_customer_id ON customer_subscriptions (customer_id);
CREATE INDEX idx_customer_subscriptions_stripe_subscription_id ON customer_subscriptions (stripe_subscription_id);
COMMENT ON TABLE customer_subscriptions IS 'Tracks active subscriptions for customers, linked to Stripe.';
COMMENT ON COLUMN customer_subscriptions.status IS 'Status of the subscription from Stripe (e.g., active, trialing, canceled).';

-- Webhook events for auditing/debugging
CREATE TABLE webhook_events (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    event_id TEXT UNIQUE NOT NULL,         -- Stripe event ID
    event_type TEXT NOT NULL,              -- e.g., 'customer.subscription.updated'
    payload JSONB NOT NULL,
    processed BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_webhook_events_event_id ON webhook_events (event_id);
COMMENT ON TABLE webhook_events IS 'Stores incoming Stripe webhook events for processing and auditing.';

-- Optional: Custom entitlements/overrides for specific customers
CREATE TABLE custom_entitlements (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
    feature_id UUID NOT NULL REFERENCES features(id) ON DELETE CASCADE,
    has_access BOOLEAN NOT NULL DEFAULT TRUE,
    max_value INT,                         -- Override plan''s max_value
    expires_at TIMESTAMP WITH TIME ZONE,   -- Temporary access
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    UNIQUE (customer_id, feature_id)
);
COMMENT ON TABLE custom_entitlements IS 'Allows overriding plan-based feature access for individual customers.';

API Endpoints

POST/api/auth/callbackHandles authentication callbacks from Supabase Auth.
GET/api/featuresRetrieves all defined features for the dashboard.
POST/api/featuresCreates a new feature in the system (for dashboard).
GET/api/plansRetrieves all defined subscription plans for the dashboard.
POST/api/plansCreates a new plan, linking it to a Stripe Price ID (for dashboard).
POST/api/plans/[planId]/featuresLinks a specific feature to a plan (for dashboard).
DELETE/api/plans/[planId]/features/[featureId]Unlinks a feature from a plan (for dashboard).
POST/api/customersCreates or updates a customer in TierLock, used by the client's SaaS to register users.
GET/api/customer/[externalUserId]/has-accessChecks if a specific user (identified by externalUserId) has access to a given feature. Core public API.
GET/api/customer/[externalUserId]/entitlementsRetrieves all features and their access status for a specific user. Public API.
POST/api/webhooks/stripeReceives and processes Stripe webhook events (e.g., subscription changes) to update customer entitlements.
🤖

Start Building with AI

Copy this prompt for Cursor, v0, Bolt, or any AI coding assistant

👷

...

builders copied today

Found this useful? Share it with your builder friends!

BD

BuilderDaily Team

Verified

Indie hackers and full-stack engineers creating validated Micro-SaaS blueprints with production-ready tech stacks.

SaaS
Code TestedSchema ValidatedProduction Ready
Coming Soon in Beta

Gap Alert

Today's gap expires in ~14 hours

Get tomorrow's blueprint delivered to your inbox so you never miss a profitable idea.

(Email delivery launching soon — sign up to be first!)

No spam, ever•Unsubscribe anytime