Quickstart
Pluralize gives your single-player app multi-tenant auth, data, files and billing without a rewrite. This page takes you from an empty Next.js project to the running app below in about ten minutes.
Recipes
1. Create a workspace
Sign up at pluralize.app/signup, create your first app, and copy the
App ID (app_xxx) and publishable key (pk_live_xxx) from the dashboard. You'll
need both; keep the publishable key out of server-only code because it's designed to ship
to browsers.
2. Install the SDK
npm install @pluralize/sdkThe SDK works in browsers, Node.js, and React. A React-specific entry lives under
@pluralize/sdk/react.
3. Configure environment variables
# .env.local
NEXT_PUBLIC_PLURALIZE_APP_ID=app_xxx
NEXT_PUBLIC_PLURALIZE_API_KEY=pk_live_xxxNEXT_PUBLIC_ is required for client-side access in Next.js App Router. These values are
not secrets — the key is scoped by appId and only works from the origins you allowlist
in the dashboard.
4. Initialize the SDK once
Create a singleton you import from every component that talks to Pluralize. There's no Provider — just a plain module export:
// lib/pluralize.ts
import { Pluralize } from '@pluralize/sdk';
export const app = Pluralize.init({
appId: process.env.NEXT_PUBLIC_PLURALIZE_APP_ID!,
apiKey: process.env.NEXT_PUBLIC_PLURALIZE_API_KEY!,
});app is a singleton: one HTTP client, one token store, one cross-tab BroadcastChannel.
Initialize it once at the module level, never inside components.
5. Add a tiny useCurrentUser hook
The SDK exposes app.auth.onChange(cb) for session reactivity. A two-line hook
wraps it so every component can read the current user:
// lib/use-current-user.ts
'use client';
import { useEffect, useState } from 'react';
import type { TenantInfo } from '@pluralize/sdk';
import { app } from '@/lib/pluralize';
export function useCurrentUser() {
const [user, setUser] = useState<TenantInfo | null | undefined>(undefined);
useEffect(() => app.auth.onChange(setUser), []);
return { user, loading: user === undefined };
}undefined means we haven't checked yet; null means signed out. That's what
prevents the "logged-in flash" on first paint.
6. Ship a first page
The page below signs a visitor up, stores a recipe scoped to them, and lists what's there. Every user gets their own rows automatically — no tenant provisioning, no database migrations.
// app/page.tsx
'use client';
import { useEffect, useState } from 'react';
import { app } from '@/lib/pluralize';
import { useCurrentUser } from '@/lib/use-current-user';
import type { DataRecord } from '@pluralize/sdk';
export default function Home() {
const { user, loading } = useCurrentUser();
const [recipes, setRecipes] = useState<DataRecord[]>([]);
useEffect(() => {
if (!user) return;
app.db.collection('recipes').find().then((res) => setRecipes(res.records));
}, [user]);
if (loading) return <p>Loading…</p>;
if (!user) return <SignupButton />;
return (
<main className="mx-auto max-w-xl p-6">
<h1 className="mb-4 text-xl font-semibold">Hi {user.email}</h1>
<ul className="space-y-1">
{recipes.map((r) => (
<li key={r.id}>{(r.data as { title: string }).title}</li>
))}
</ul>
</main>
);
}
function SignupButton() {
return (
<button
onClick={() => app.auth.signup('ada@example.com', 'correct horse battery staple')}
className="rounded-md bg-black px-4 py-2 text-white"
>
Sign up
</button>
);
}That's every piece: a singleton, a one-line session hook, and a collection call. The same shape extends to files and billing.
Prefer drop-in components?
If you'd rather not write your own forms, the SDK ships pre-styled <PluralizeLogin>,
<PluralizeSignup>, and <PluralPricing> — each takes the singleton app as a prop:
'use client';
import { PluralizeSignup } from '@pluralize/sdk/react';
import { app } from '@/lib/pluralize';
export default function SignupPage() {
return <PluralizeSignup app={app} onSuccess={() => location.assign('/')} />;
}Plain JavaScript (no React)
If you're not using React, the same singleton works directly — no hook needed:
import { app } from './lib/pluralize';
await app.auth.signup(email, password);
const { records } = await app.db.collection('recipes').find();Next steps
- Authentication — login forms, protected routes, session hooks.
- Data — filters, sort, pagination, unique fields, and share links.
- Files — per-tenant uploads with MIME and size guardrails.
- Billing — Stripe checkout, the customer portal, and plan entitlements.