Define Plans in Code
Use the SDK catalog to define features and plans as code, then sync to the dashboard
Define Plans in Code
Define your features and plans in TypeScript and push them to Owostack with a single command. No dashboard clicks required.
Prerequisites
- An Owostack organization with an API key
@owostack/coreinstalled in your project
1. Define your features
Create a file (e.g. src/billing/features.ts) and define your features using metered() and boolean():
import { metered, boolean } from "@owostack/core";
export const apiCalls = metered("api-calls", { name: "API Calls" });
export const analytics = boolean("analytics", { name: "Analytics Dashboard" });
export const seats = metered("seats", { name: "Team Seats" });2. Define your plans
Create a config file (e.g. owo.config.ts) that assembles features into plans:
import { Owostack, plan } from "@owostack/core";
import { apiCalls, analytics, seats } from "./src/billing/features";
export default new Owostack({
secretKey: process.env.OWOSTACK_SECRET_KEY!,
catalog: [
plan("starter", {
name: "Starter",
price: 0,
currency: "NGN",
interval: "monthly",
features: [
apiCalls.limit(1000),
analytics.off(),
seats.limit(3, { reset: "never" }),
],
}),
plan("pro", {
name: "Pro",
price: 500000,
currency: "NGN",
interval: "monthly",
features: [
apiCalls.limit(50000, { overage: "charge", overagePrice: 100 }),
analytics.on(),
seats.limit(20, { reset: "never" }),
],
}),
plan("enterprise", {
name: "Enterprise",
price: 2000000,
currency: "NGN",
interval: "monthly",
features: [
apiCalls.unlimited(),
analytics.on(),
seats.unlimited(),
],
}),
],
});3. Sync to the API
Using the CLI
npx owostack sync --config ./owo.config.tsUsing owo.sync() in a script
import owo from "./owo.config";
const result = await owo.sync();
console.log(result);
// { features: { created: [...], updated: [...] }, plans: { ... } }As a deploy step
{
"scripts": {
"sync": "npx owostack sync",
"deploy": "pnpm sync && pnpm build && pnpm start"
}
}4. Use feature handles in your app
Once features are defined, use them directly for access checks and usage tracking:
import { apiCalls, analytics } from "./billing/features";
// Check access
const access = await apiCalls.check("user_123");
if (!access.allowed) {
console.log("Access denied:", access.code);
}
// Track usage
await apiCalls.track("user_123", 1);
// Boolean features — no .track()
const { allowed } = await analytics.check("user_123");What sync does
- Creates features and plans that don't exist yet
- Updates features and plans that have changed in code
- Never deletes — removing from code doesn't remove from the API
- Tags resources with
source: "sdk"so the dashboard knows they're code-managed - Idempotent — running sync multiple times with the same config is safe
Conflict resolution
Every feature and plan has a source field ("dashboard" or "sdk"). Sync follows a code-wins policy for resources it manages:
| Scenario | What happens |
|---|---|
| Feature exists only in code | Created on sync |
| Feature exists only in dashboard | Untouched |
| Feature exists in both, code changed | Code version wins |
| Feature removed from code but exists in API | Nothing — resource stays |
The dashboard shows a "Managed by SDK" badge for code-managed resources.
Recommended workflows
Small teams: Use the catalog for everything. Define features and plans in code, sync on deploy. Dashboard is read-only for verification.
Larger teams: Use the catalog for core features and plans. Let product managers create experimental plans in the dashboard. The two don't interfere.
Resources created in the dashboard remain untouched by sync. Code-managed and dashboard-managed resources coexist safely.