SKT ("SvelteKit Template") is a boilerplate for SaaS or web apps. The project is based on Svelte 5 and uses SvelteKit, Tailwind / DaisyUI, Supabase (Auth and DB) and Stripe. SKT is fully responsive with a mobile first design and supports light and dark mode, see below for screenshots.
The project is divided in two main routes, public and private. The public route is the front facing part, ie. the content people see when they visit your website, example.com. The private route is only accessible to logged-in users and resides at example.com/app. This is where registered users can edit their profile, manage their subscription and access the actual app or SaaS. The boilerplate will only show a placeholder in lieu of the app.
Clone or download repo and install dependencies:
npm install
Copy .env.example
to .env.local
. You will have to provide the following info and keys:
# Project
PUBLIC_PROJECT = 'Your Projects Name'
# Supabase
PUBLIC_SUPABASE_URL = 'https://YOURPROJECT.supabase.co'
PUBLIC_SUPABASE_ANON_KEY = ''
PRIVATE_SUPABASE_SERVICE_ROLE = ''
# Stripe
PUBLIC_DOMAIN = 'http://localhost:5173'
PUBLIC_STRIPE_CUSTOMER_PORTAL = ''
PUBLIC_STRIPE_API_KEY = ''
PRIVATE_STRIPE_API_KEY = ''
PRIVATE_STRIPE_WHSECRET = ''
Stripe's WHSecret key is optional; the webhook is implemented at $src/routes/(admin)/webhooks/stripe/+server.ts
but currently unused as we use Stripe's embedded checkout, see below.
Create a project in Supabase and make a note of the project URL and keys; you will need to add both the ANON and the SERVICE ROLE key to .env.local
. SKT uses Supabase Auth. Two additional public tables are needed for user management: "Users" and "Profiles". In your project, open the SQL Editor and run supabase.sql
to set up the tables and triggers.
Currently supported is signing up per email / password and Google OAuth. Enable both providers in Supabase > Authentication > Providers. In SKT, all relevant code resides in $src/routes/(admin)/auth
.
For email leave everything at defaults (disabling "Confirm email" speeds things up during development). Note that changing the user's email is currently not supported.
To setup Google OAuth you can follow Supabase's walkthrough. You can ignore the code samples in the walkthrough, all of this is already built-in. Or follow these steps:
In Authentication > Providers, open the Google dropdown and make a note of the callback URL, you will need it in step #7 below. Client ID and Client secret will be filled in at step #12.
In Google Cloud, setup a project, then navigate to Google Auth Platform and click "Get Started". Then:
Note: This assumes that you are working in Stripe's test mode.
In your Stripe dashboard, make a note of the API keys. You need to add both the public and the private key to your .env.local
file (the keys should begin with "pk_test" and "sk_test"). To have the user manage their subscription, enable the customer portal here and add the URL to .env.local
.
SKT assumes three tiers (named "Free", "Core" and "Pro" in the boilerplate). The "Core" and "Pro" tiers feature monthly or yearly billing. Newly registered users default to the "Free" tier so only the other two have to be setup in Stripe. Create two products with two prices each, one for a monthly payment, one for a yearly payment. Make a note of the respective product IDs ("prod...") and price IDs ("price...").
Modify the table in $lib/utility/plans.ts
to represent your products. You will need to provide all details, like so:
{
id: 'core',
name: 'Core',
description: 'Core plan description.',
popular: 'true',
price: ['$9.99', '$99.99'],
priceIntervalName: ['monthly', 'yearly'],
priceId: ['price_1QSdXlEIoDgScXMg0KHwWiDH', 'price_1QSdXlEIoDgScXMgrEfsS170'],
productId: 'prod_RLKCfgua9ZWSiN',
features: ['Everything in Free', 'Core Feature 1', 'Core Feature 2']
}
The data will be pulled from the table and properly formatted. The relevant code lives at $src/(public)/(subscription)/pricing
and in $lib/components/PlanCard
. Note that the pricing table is dynamic; the button text and destinations change depending on the status of the user. Modify the logic for your use case in PlanCard.svelte, for example if you want to offer upgrades from one tier to a higher one.
SKT integrates Stripe's embedded checkout (see $src/routes/(public)/(subscription)checkout
). Once the payment is completed, Stripe's plugin redirects to $src/routes/(public)/(subscription)/subscribed
. The code then updates the "Users" table and simply shows a Thank you-note, redirecting the new subscriber to the private section of the site. When a registered user accesses the private section, the code in $src/routes/(private)/app/+layout.server.ts
checks with Stripe if the subscription is still active and updates the status accordingly. It's up to you to handle status changes, e.g. to restrict features of your app when a subscription has been deleted or is past due.
In this scenario, webhooks are not required. However, it can be useful to capture additional Stripe subscription events via webhooks. Enable webhooks in the Stripe dashboard and select the events you are interested in. Make a note of the webhook's secret ("whsec...") and add it to .env.local
. You will have to provide a publicly accessible URL as a destination. The webhook endpoint sits at $src/routes/(admin)/webhooks/stripe
. When live on the web the destination URL would be example.com/webhooks/stripe. In development on localhost, you will need a service like ngrok to make this URL available to the outside.
Tip: Use the Stripe CLI to generate events locally.
Run the project with:
npm run dev
Then, in your browser, navigate to http://localhost:5173.
Based on SvelteKit Stripe Demo
MIT.
SKT SvelteKit Saas Boilerplate Website
SKT SvelteKit Saas Boilerplate Sign In
SKT SvelteKit Saas Boilerplate Pricing
SKT SvelteKit Saas Boilerplate App