# Memberstack - Comprehensive AI Reference Guide > Memberstack is a membership and authentication platform that provides user authentication, gated content (restricting access based on membership/plans), subscription payments via Stripe, and flexible data storage for websites. This is the authoritative reference for all Memberstack packages and integrations. **PACKAGE:** @memberstack/dom **VERSION:** 2.x (current) **CRITICAL:** This guide documents the ONLY supported client-side package. Do not use patterns from older versions or other libraries. --- ## DEPRECATED PACKAGES WARNING **The following packages are DEPRECATED and maintained ONLY for legacy customers:** - `@memberstack/react` - DO NOT USE for new projects - `@memberstack/nextjs` - DO NOT USE for new projects **Use `@memberstack/dom` directly with ANY JavaScript framework** including React, Next.js, Vue, Nuxt, Svelte, SvelteKit, Angular, or vanilla JavaScript. --- ## Table of Contents 1. [Installation & Setup](#installation--setup) 2. [Authentication Methods](#authentication-methods) 3. [Profile Management](#profile-management) 4. [Member JSON Storage](#member-json-storage) 5. [Plan Management](#plan-management) 6. [Data Tables](#data-tables) 7. [Pre-built Modals](#pre-built-modals) 8. [Session Management](#session-management) 9. [Error Handling](#error-handling) 10. [Framework Integration](#framework-integration) 11. [Webflow Integration](#webflow-integration) 12. [Type Definitions](#type-definitions) 13. [Server-Side Admin Package](#server-side-admin-package-memberstackadmin) 14. [MCP Server](#mcp-server-ai-integration) 15. [Common Gotchas & Critical Notes](#common-gotchas--critical-notes) --- ## Installation & Setup ### Installation ```bash npm install @memberstack/dom # OR yarn add @memberstack/dom # OR pnpm add @memberstack/dom ``` ### Basic Import & Initialization ```javascript import memberstackDOM from '@memberstack/dom'; // Initialize Memberstack (REQUIRED before any other calls) const memberstack = memberstackDOM.init({ publicKey: 'pk_your_public_key_here', // REQUIRED - get from Memberstack dashboard useCookies: true, // Optional: enable cookie-based auth (default: false) setCookieOnRootDomain: true, // Optional: set cookies on root domain (requires useCookies: true) domain: 'https://custom-api.example.com' // Optional: custom API domain }); ``` ### Configuration Options | Option | Type | Default | Description | |--------|------|---------|-------------| | `publicKey` | string | *required* | Your Memberstack public key (starts with `pk_` for live, `pk_sb_` for sandbox) | | `useCookies` | boolean | false | Use cookies instead of localStorage for auth tokens | | `setCookieOnRootDomain` | boolean | false | Set cookies on root domain for subdomain sharing | | `domain` | string | Memberstack API | Custom API domain (rarely needed) | --- ## Authentication Methods ### Email/Password Signup ```javascript // METHOD: memberstack.signupMemberEmailPassword(params, options?) // REQUIRED params: { email: string, // Member's email address password: string // Member's password (minimum 8 characters) } // OPTIONAL params: { customFields?: object, // Custom fields object metaData?: object, // Additional metadata plans?: Array<{ planId: string }>, // CRITICAL: Array of OBJECTS with planId captchaToken?: string, // CAPTCHA token if enabled inviteToken?: string // Invite token for restricted signup } // RETURNS: Promise<{ data: { member: Member, tokens: { accessToken: string } } }> // COMPLETE EXAMPLE: try { const result = await memberstack.signupMemberEmailPassword({ email: "user@example.com", password: "securePassword123", customFields: { firstname: "John", lastname: "Doe", company: "Acme Inc" }, plans: [{ planId: "pln_free-plan-id" }] // CRITICAL: Array of objects, NOT strings }); console.log("Signup successful:", result.data.member); console.log("Member ID:", result.data.member.id); console.log("Email:", result.data.member.auth.email); // Note: .auth.email console.log("Access token:", result.data.tokens.accessToken); // Redirect to dashboard or show welcome message window.location.href = '/dashboard'; } catch (error) { console.error("Signup failed:", error.message); // Handle specific errors if (error.code === 'email-already-in-use') { alert('An account with this email already exists.'); } else if (error.code === 'invalid-password-too-short') { alert('Password is too weak. Use at least 8 characters.'); } } ``` ### Email/Password Login ```javascript // METHOD: memberstack.loginMemberEmailPassword(params, options?) // REQUIRED params: { email: string, password: string } // RETURNS: Promise<{ data: { member: Member, tokens: { accessToken: string } } }> // COMPLETE EXAMPLE: try { const result = await memberstack.loginMemberEmailPassword({ email: "user@example.com", password: "userPassword" }); console.log("Login successful:", result.data.member); console.log("Email:", result.data.member.auth.email); console.log("Plans:", result.data.member.planConnections); // Redirect based on plan or default const loginRedirect = result.data.member.loginRedirect || '/dashboard'; window.location.href = loginRedirect; } catch (error) { console.error("Login failed:", error.message); if (error.code === 'invalid-credentials') { alert('Invalid email or password'); } } ``` ### Logout ```javascript // METHOD: memberstack.logout(options?) // RETURNS: Promise // COMPLETE EXAMPLE: try { await memberstack.logout(); console.log("Logout successful"); window.location.href = '/'; // Redirect to home } catch (error) { console.error("Logout failed:", error.message); } ``` ### Get Current Member ```javascript // METHOD: memberstack.getCurrentMember(options?) // OPTIONAL options: { useCache?: boolean // Default: false - set true to use cached data } // RETURNS: Promise<{ data: Member | null }> // COMPLETE EXAMPLE: try { const { data: member } = await memberstack.getCurrentMember(); if (member) { console.log("Member is logged in"); console.log("ID:", member.id); console.log("Email:", member.auth.email); // CRITICAL: Use .auth.email console.log("Verified:", member.verified); console.log("Plans:", member.planConnections); // CRITICAL: Use .planConnections console.log("Custom fields:", member.customFields); console.log("Profile image:", member.profileImage); // Check if member has a specific plan const hasPremium = member.planConnections?.some( conn => conn.planId === 'pln_premium' && conn.active ); } else { console.log("No member logged in"); // Redirect to login or show public content } } catch (error) { console.error("Failed to get member:", error.message); } ``` ### Auth State Changes ```javascript // METHOD: memberstack.onAuthChange(callback) // callback: (member: Member | null) => void // RETURNS: { unsubscribe: () => void } // COMPLETE EXAMPLE: const authListener = memberstack.onAuthChange((member) => { if (member) { console.log("User logged in:", member.auth.email); showLoggedInUI(member); } else { console.log("User logged out"); showLoggedOutUI(); } }); // CRITICAL: Always unsubscribe when component unmounts // In React useEffect cleanup: return () => { authListener.unsubscribe(); }; // In Vue onUnmounted: onUnmounted(() => { authListener.unsubscribe(); }); // In Svelte onDestroy: onDestroy(() => { authListener.unsubscribe(); }); ``` ### Social Provider Authentication ```javascript // SIGNUP with provider // METHOD: memberstack.signupWithProvider(params) // REQUIRED params: { provider: string // “google”, “facebook”, “github”, “linkedin”, “dribbble”, “spotify” } // OPTIONAL params: { customFields?: object, plans?: Array<{ planId: string }>, allowLogin?: boolean, // Allow existing users to login instead inviteToken?: string } // IMPORTANT: This method REDIRECTS IMMEDIATELY to the provider // Code after this call will NOT execute try { await memberstack.signupWithProvider({ provider: "google", allowLogin: true, // If user exists, log them in plans: [{ planId: "pln_free-plan-id" }] }); // This code will NOT run - browser redirects to Google } catch (error) { console.error("Provider signup failed:", error.message); } // LOGIN with provider // METHOD: memberstack.loginWithProvider(params) try { await memberstack.loginWithProvider({ provider: "google", allowSignup: true // If user doesn't exist, sign them up }); // This code will NOT run - browser redirects to Google } catch (error) { console.error("Provider login failed:", error.message); } ``` ### Connect/Disconnect Provider ```javascript // CONNECT additional provider to existing account // METHOD: memberstack.connectProvider(params) // IMPORTANT: Also redirects immediately try { await memberstack.connectProvider({ provider: "github" }); // This code will NOT run - browser redirects } catch (error) { console.error("Failed to connect provider:", error.message); } // DISCONNECT provider from account // METHOD: memberstack.disconnectProvider(params) // RETURNS: Promise<{ data: { providers: Array } }> try { const result = await memberstack.disconnectProvider({ provider: "github" }); console.log("Remaining providers:", result.data.providers); } catch (error) { console.error("Failed to disconnect provider:", error.message); } ``` ### Passwordless Authentication ```javascript // SEND OTP for login // METHOD: memberstack.sendMemberLoginPasswordlessEmail(params) try { await memberstack.sendMemberLoginPasswordlessEmail({ email: "user@example.com", redirectUrl: window.location.origin + "/dashboard" }); console.log("6-digit code sent! Check your email."); } catch (error) { console.error("Failed to send 6-digit code:", error.message); } // SEND OTP for signup // METHOD: memberstack.sendMemberSignupPasswordlessEmail(params) try { await memberstack.sendMemberSignupPasswordlessEmail({ email: "newuser@example.com" }); console.log("Signup 6-digit code sent!"); } catch (error) { console.error("Failed to send signup code:", error.message); } // COMPLETE login with 6-digit code // METHOD: memberstack.loginMemberPasswordless(params) try { const result = await memberstack.loginMemberPasswordless({ email: "user@example.com", passwordlessToken: "123456" // 6-digit code from email }); console.log("Passwordless login successful:", result.data.member); } catch (error) { console.error("Passwordless login failed:", error.message); } // COMPLETE signup with 6-digit code // METHOD: memberstack.signupMemberPasswordless(params) try { const result = await memberstack.signupMemberPasswordless({ email: "newuser@example.com", passwordlessToken: "123456", // 6-digit code from email customFields: { firstname: "Jane" }, plans: [{ planId: "pln_free-plan-id" }] }); console.log("Passwordless signup successful:", result.data.member); } catch (error) { console.error("Passwordless signup failed:", error.message); } ``` ### Password Reset ```javascript // SEND password reset email // METHOD: memberstack.sendMemberResetPasswordEmail(params) try { await memberstack.sendMemberResetPasswordEmail({ email: "user@example.com" }); console.log("Password reset email sent"); } catch (error) { console.error("Failed to send reset email:", error.message); } // RESET password with token // METHOD: memberstack.resetMemberPassword(params) try { await memberstack.resetMemberPassword({ token: "reset_token_from_email", // 6-digit code from email newPassword: "newSecurePassword123" }); console.log("Password reset successful"); // Redirect to login window.location.href = '/login'; } catch (error) { console.error("Password reset failed:", error.message); } // SET password (for passwordless members who want to add a password) // METHOD: memberstack.setPassword(params) try { const result = await memberstack.setPassword({ password: "newPassword123" }); console.log("Password set successfully:", result.data); } catch (error) { console.error("Failed to set password:", error.message); } ``` ### Email Verification ```javascript // SEND verification email // METHOD: memberstack.sendMemberVerificationEmail() try { await memberstack.sendMemberVerificationEmail(); console.log("Verification email sent"); } catch (error) { console.error("Failed to send verification email:", error.message); } ``` ### Get Auth Token ```javascript // METHOD: memberstack.getMemberCookie() // RETURNS: string | null (synchronous) const token = memberstack.getMemberCookie(); if (token) { console.log("Auth token:", token); // Use for authenticated API requests to your backend fetch('/api/protected-resource', { headers: { 'Authorization': `Bearer ${token}` } }); } else { console.log("No auth token - user not logged in"); } ``` --- ## Profile Management ### Update Custom Fields ```javascript // METHOD: memberstack.updateMember(params, options?) // REQUIRED params: { customFields: object // Object with custom field updates } // RETURNS: Promise<{ data: Member }> try { const result = await memberstack.updateMember({ customFields: { "first-name": "Jane", "last-name": "Smith", company: "New Company", bio: "Software developer" } }); console.log("Profile updated:", result.data); } catch (error) { console.error("Profile update failed:", error.message); } ``` ### Update Email or Password ```javascript // METHOD: memberstack.updateMemberAuth(params, options?) // OPTIONAL params (at least one required): { email?: string, // New email address oldPassword?: string, // Current password (required only when changing password) newPassword?: string // New password } // Update email only (no password required) try { const result = await memberstack.updateMemberAuth({ email: "newemail@example.com" }); console.log("Email updated:", result.data); } catch (error) { console.error("Email update failed:", error.message); } // Update password (requires current password) try { const result = await memberstack.updateMemberAuth({ oldPassword: "currentPassword", newPassword: "newSecurePassword" }); console.log("Password updated:", result.data); } catch (error) { console.error("Password update failed:", error.message); } ``` ### Update Profile Image ```javascript // METHOD: memberstack.updateMemberProfileImage(params) // REQUIRED params: { profileImage: File // File object from input[type="file"] } // RETURNS: Promise<{ data: Member }> const fileInput = document.querySelector('input[type="file"]'); const file = fileInput.files[0]; try { const result = await memberstack.updateMemberProfileImage({ profileImage: file }); console.log("Profile image updated:", result.data.profileImage); } catch (error) { console.error("Profile image update failed:", error.message); } ``` ### Delete Member Account ```javascript // METHOD: memberstack.deleteMember(options?) // RETURNS: Promise try { // Usually show confirmation dialog first if (confirm("Are you sure you want to delete your account? This cannot be undone.")) { await memberstack.deleteMember(); console.log("Account deleted"); window.location.href = '/'; } } catch (error) { console.error("Failed to delete account:", error.message); } ``` --- ## Member JSON Storage **CRITICAL WARNING**: `updateMemberJSON()` COMPLETELY REPLACES existing data - it does NOT merge! This is the #1 source of confusion and bugs. ### Get Member JSON ```javascript // METHOD: memberstack.getMemberJSON(options?) // RETURNS: Promise<{ data: object }> try { const { data } = await memberstack.getMemberJSON(); const json = data; if (json) { console.log("Member JSON:", json); console.log("Theme:", json.preferences?.theme); } else { console.log("No JSON data stored for this member"); } } catch (error) { console.error("Failed to get member JSON:", error.message); } ``` ### Update Member JSON (CRITICAL - READ CAREFULLY) ```javascript // METHOD: memberstack.updateMemberJSON(params, options?) // REQUIRED params: { json: object // COMPLETELY REPLACES existing JSON - does NOT merge! } // RETURNS: Promise<{ data: { json: object } }> // ============================================ // WRONG - This will DELETE all existing data! // ============================================ await memberstack.updateMemberJSON({ json: { theme: "dark" // Everything else is DELETED! } }); // ============================================ // CORRECT - Always fetch, merge, then update // ============================================ async function updateMemberJSONSafely(updates) { // Step 1: Fetch current data const { data } = await memberstack.getMemberJSON(); const currentJson = data || {}; // Step 2: Merge with updates (using spread operator) const mergedJson = { ...currentJson, ...updates }; // Step 3: Save merged data await memberstack.updateMemberJSON({ json: mergedJson }); } // Usage example await updateMemberJSONSafely({ theme: "dark" }); // Preserves all other data ``` ### Member JSON Patterns ```javascript // PATTERN: Nested object updates async function updateNestedPreference(key, value) { const { data } = await memberstack.getMemberJSON(); const currentJson = data || {}; await memberstack.updateMemberJSON({ json: { ...currentJson, preferences: { ...currentJson.preferences, [key]: value } } }); } // PATTERN: Array updates async function addToFavorites(itemId) { const { data } = await memberstack.getMemberJSON(); const currentJson = data || {}; const favorites = currentJson.favorites || []; // Add if not already in array if (!favorites.includes(itemId)) { await memberstack.updateMemberJSON({ json: { ...currentJson, favorites: [...favorites, itemId] } }); } } // PATTERN: User preferences with defaults async function getUserPreferences() { const { data } = await memberstack.getMemberJSON(); const json = data || {}; // Return with defaults return { theme: json.preferences?.theme || 'light', language: json.preferences?.language || 'en', notifications: json.preferences?.notifications ?? true, timezone: json.preferences?.timezone || 'UTC' }; } // PATTERN: Onboarding progress async function completeOnboardingStep(step) { const { data } = await memberstack.getMemberJSON(); const currentJson = data || {}; const completedSteps = currentJson.onboarding?.completedSteps || []; await memberstack.updateMemberJSON({ json: { ...currentJson, onboarding: { ...currentJson.onboarding, completedSteps: [...new Set([...completedSteps, step])], lastStep: step, updatedAt: new Date().toISOString() } } }); } ``` --- ## Plan Management ### Get Plans ```javascript // GET all plans // METHOD: memberstack.getPlans() // RETURNS: Promise<{ data: Plan[] }> try { const { data: plans } = await memberstack.getPlans(); console.log("Available plans:", plans); plans.forEach(plan => { console.log(`${plan.name} (${plan.id})`); plan.prices?.forEach(price => { console.log(` - ${price.name}: ${price.amount} ${price.currency}/${price.interval}`); }); }); } catch (error) { console.error("Failed to get plans:", error.message); } // GET single plan // METHOD: memberstack.getPlan(params) try { const { data: plan } = await memberstack.getPlan({ planId: 'pln_premium-plan-id' }); console.log("Plan details:", plan); } catch (error) { console.error("Failed to get plan:", error.message); } ``` ### Add Free Plan to Member ```javascript // METHOD: memberstack.addPlan(params, options?) // REQUIRED params: { planId: string // Plan ID (starts with "pln_") - for FREE plans only } // RETURNS: Promise<{ data: Member }> try { const result = await memberstack.addPlan({ planId: "pln_free-tier-id" // Use Plan ID for free plans }); console.log("Plan added:", result.data.member.planConnections); } catch (error) { console.error("Failed to add plan:", error.message); } ``` ### Remove Free Plan from Member ```javascript // METHOD: memberstack.removePlan(params, options?) try { const result = await memberstack.removePlan({ planId: "pln_plan-to-remove" }); console.log("Plan removed:", result.data.member.planConnections); } catch (error) { console.error("Failed to remove plan:", error.message); } ``` ### Stripe Checkout (Paid Plans) ```javascript // METHOD: memberstack.purchasePlansWithCheckout(params, options?) // REQUIRED params: { priceId: string // Price ID (starts with "prc_") - NOT Plan ID! } // OPTIONAL params: { couponId?: string, // Stripe coupon ID successUrl?: string, // Redirect URL after successful payment cancelUrl?: string, // Redirect URL if user cancels autoRedirect?: boolean, // Auto redirect to Stripe (default: true) metadataForCheckout?: object } // RETURNS: Promise<{ data: { url: string } }> try { // With auto redirect (default) await memberstack.purchasePlansWithCheckout({ priceId: "prc_monthly-pro-price", // CRITICAL: Use Price ID, not Plan ID successUrl: "/thank-you", cancelUrl: "/pricing" }); // User is redirected to Stripe Checkout } catch (error) { console.error("Checkout failed:", error.message); } // Without auto redirect (get URL to redirect manually) try { const { data } = await memberstack.purchasePlansWithCheckout({ priceId: "prc_monthly-pro-price", successUrl: "/thank-you", cancelUrl: "/pricing", autoRedirect: false // Don't auto redirect }); console.log("Checkout URL:", data.url); // Redirect manually when ready window.location.href = data.url; } catch (error) { console.error("Checkout failed:", error.message); } ``` ### Stripe Customer Portal ```javascript // METHOD: memberstack.launchStripeCustomerPortal(params?, options?) // OPTIONAL params: { returnUrl?: string, // URL to return to after portal priceIds?: string[], // Limit which prices are shown configuration?: object, // Portal configuration autoRedirect?: boolean // Auto redirect (default: true) } // RETURNS: Promise<{ data: { url: string } }> try { await memberstack.launchStripeCustomerPortal({ returnUrl: "/account" }); // User is redirected to Stripe Customer Portal } catch (error) { console.error("Portal launch failed:", error.message); } // Without auto redirect try { const { data } = await memberstack.launchStripeCustomerPortal({ returnUrl: "/account", autoRedirect: false }); // Open in new tab window.open(data.url, '_blank'); } catch (error) { console.error("Portal launch failed:", error.message); } ``` --- ## Data Tables Data Tables provide structured, relational data storage with advanced querying, relationships, and access control. ### Rate Limits (IMPORTANT) | Operation | Limit | |-----------|-------| | Global | 200 requests per 30 seconds per IP | | Reads | 25 requests per second per IP | | Creates | 10 requests per minute per IP | | Writes | 30 requests per minute per IP | ### Get Tables ```javascript // GET all tables // METHOD: memberstack.getDataTables() try { const { data } = await memberstack.getDataTables(); data.tables.forEach(table => { console.log(`Table: ${table.key} (${table.name})`); console.log(` Records: ${table.recordCount}`); console.log(` Fields:`, table.fields.map(f => f.key)); }); } catch (error) { console.error("Failed to get tables:", error.message); } // GET single table // METHOD: memberstack.getDataTable(params) try { const { data } = await memberstack.getDataTable({ table: 'articles' // Table key }); console.log("Table:", data.name); console.log("Access rules:", { create: data.createRule, read: data.readRule, update: data.updateRule, delete: data.deleteRule }); } catch (error) { console.error("Failed to get table:", error.message); } ``` ### CRUD Operations ```javascript // CREATE record // METHOD: memberstack.createDataRecord(params) try { const { data } = await memberstack.createDataRecord({ table: 'articles', data: { title: 'My First Article', content: 'Article content here...', published: true, category: 'tutorial' } }); console.log("Created record:", data.id); console.log("Created by:", data.createdByMemberId); } catch (error) { console.error("Failed to create record:", error.message); } // GET single record // METHOD: memberstack.getDataRecord(params) try { const { data } = await memberstack.getDataRecord({ table: 'articles', recordId: 'rec_abc123' }); console.log("Article:", data.data.title); } catch (error) { console.error("Failed to get record:", error.message); } // UPDATE record (partial update - unlike Member JSON!) // METHOD: memberstack.updateDataRecord(params) try { const { data } = await memberstack.updateDataRecord({ recordId: 'rec_abc123', data: { title: 'Updated Title', // Only updates specified fields published: false } }); console.log("Updated record:", data); } catch (error) { console.error("Failed to update record:", error.message); } // DELETE record // METHOD: memberstack.deleteDataRecord(params) try { await memberstack.deleteDataRecord({ recordId: 'rec_abc123' }); console.log("Record deleted"); } catch (error) { console.error("Failed to delete record:", error.message); } ``` ### Advanced Querying ```javascript // METHOD: memberstack.queryDataRecords(params) // Basic query with filtering try { const { data } = await memberstack.queryDataRecords({ table: 'articles', query: { findMany: { where: { published: { equals: true } }, orderBy: { createdAt: 'desc' }, take: 20 } } }); console.log("Found", data.records.length, "articles"); } catch (error) { console.error("Query failed:", error.message); } // Complex filtering with AND/OR try { const { data } = await memberstack.queryDataRecords({ table: 'articles', query: { findMany: { where: { AND: [ { published: { equals: true } }, { OR: [ { category: { equals: 'tutorial' } }, { category: { equals: 'guide' } } ] }, { title: { contains: 'JavaScript' } }, { createdAt: { gt: '2024-01-01T00:00:00.000Z' } } ] }, orderBy: { createdAt: 'desc' }, take: 10 } } }); } catch (error) { console.error("Query failed:", error.message); } // Available filter operators: // equals, not, contains, startsWith, endsWith // gt, gte, lt, lte (greater than, less than) // in, notIn (array of values) // Pagination (cursor-based) try { // First page let { data } = await memberstack.queryDataRecords({ table: 'articles', query: { findMany: { where: { published: { equals: true } }, orderBy: { createdAt: 'desc' }, take: 20 } } }); console.log("Page 1:", data.records.length); console.log("Has more:", data.pagination?.hasMore); // Next page if (data.pagination?.hasMore) { const nextPage = await memberstack.queryDataRecords({ table: 'articles', query: { findMany: { where: { published: { equals: true } }, orderBy: { createdAt: 'desc' }, take: 20, after: String(data.pagination.endCursor) } } }); console.log("Page 2:", nextPage.data.records.length); } } catch (error) { console.error("Pagination failed:", error.message); } // Include relationships try { const { data } = await memberstack.queryDataRecords({ table: 'articles', query: { where: { published: { equals: true } }, include: { author: true, // REFERENCE field category: true, // REFERENCE field _count: { select: { comments: true, likes: true } } }, take: 10 } }); data.records.forEach(article => { console.log(`${article.data.title} by ${article.data.author?.data.name}`); console.log(` Comments: ${article._count.comments}`); }); } catch (error) { console.error("Query failed:", error.message); } ``` --- ## Pre-built Modals ### Modal Types - `'LOGIN'` - Login modal - `'SIGNUP'` - Signup modal - `'PROFILE'` - Profile management modal - `'FORGOT_PASSWORD'` - Forgot password modal - `'RESET_PASSWORD'` - Reset password modal ### Opening Modals ```javascript // METHOD: memberstack.openModal(type, params?) // Basic login modal try { await memberstack.openModal("LOGIN"); console.log("Login modal opened"); } catch (error) { console.error("Failed to open modal:", error.message); } // Login modal with signup plans // CRITICAL: Modal params use different format than direct signup methods! try { await memberstack.openModal("LOGIN", { signup: { plans: ["pln_free-plan-id"] // Array of STRINGS, not objects! } }); } catch (error) { console.error("Failed to open modal:", error.message); } // Signup modal with plans try { await memberstack.openModal("SIGNUP", { signup: { plans: ["pln_free-plan-id"] // Array of STRINGS, not objects! } }); } catch (error) { console.error("Failed to open modal:", error.message); } // Profile modal try { await memberstack.openModal("PROFILE"); } catch (error) { console.error("Failed to open modal:", error.message); } ``` ### Closing Modals ```javascript // METHOD: memberstack.hideModal() // IMPORTANT: Modals do NOT close automatically after success // You must close them manually try { await memberstack.hideModal(); console.log("Modal closed"); } catch (error) { console.error("Failed to close modal:", error.message); } ``` ### Modal Flow Pattern ```javascript async function handleLogin() { try { // Open login modal - resolves when login succeeds const result = await memberstack.openModal("LOGIN"); console.log("Login successful:", result); // CRITICAL: Close modal manually await memberstack.hideModal(); // Redirect or update UI window.location.href = '/dashboard'; } catch (error) { console.error("Login failed:", error.message); } } ``` --- ## Session Management ### Configuration ```javascript const memberstack = memberstackDOM.init({ publicKey: 'pk_...', useCookies: true, // Use cookies instead of localStorage setCookieOnRootDomain: true, // Share auth across subdomains }); ``` ### Session Monitoring ```javascript // Set up auth state listener on app init const authListener = memberstack.onAuthChange((member) => { if (member) { console.log("Session active:", member.auth.email); // Update UI for logged in state } else { console.log("Session ended"); // Update UI for logged out state } }); // CRITICAL: Clean up on app/component unmount // Store reference and call unsubscribe() when done ``` --- ## Error Handling ### Error Structure ```javascript // All Memberstack errors have this structure: { message: string, // Human-readable error message statusCode?: number, // HTTP status code (400, 401, 403, 404, 500) code?: string, // Memberstack error code details?: any // Additional error details } ``` ### Common Error Codes | Code | Description | |------|-------------| | `invalid-credentials` | Wrong email or password | | `email-already-in-use` | Account already exists | | `invalid-password-too-short` | Password doesn't meet requirements | | `invalid-email` | Invalid email format | | `invalid-token` | Expired or invalid token | ### Error Handling Pattern ```javascript try { const result = await memberstack.loginMemberEmailPassword({ email: email, password: password }); // Handle success } catch (error) { console.error("Error:", error); // Handle specific error codes switch (error.code) { case 'invalid-credentials': showError('Invalid email or password'); break; default: showError(error.message || 'An error occurred'); } // Handle HTTP status codes if (error.statusCode === 401) { // Unauthorized } else if (error.statusCode === 403) { // Forbidden } else if (error.statusCode >= 500) { // Server error } } ``` --- ## Framework Integration ### React ```javascript import { useEffect, useState } from 'react'; import memberstackDOM from '@memberstack/dom'; // Create memberstack instance outside component to avoid re-initialization let memberstackInstance = null; function useMemberstack() { const [memberstack, setMemberstack] = useState(null); const [member, setMember] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { // Initialize once if (!memberstackInstance) { memberstackInstance = memberstackDOM.init({ publicKey: process.env.REACT_APP_MEMBERSTACK_KEY, useCookies: true }); } setMemberstack(memberstackInstance); // Set up auth listener const authListener = memberstackInstance.onAuthChange((member) => { setMember(member); setLoading(false); }); // Check current auth state memberstackInstance.getCurrentMember().then(({ data }) => { setMember(data); setLoading(false); }); // Cleanup return () => { authListener.unsubscribe(); }; }, []); return { memberstack, member, loading }; } // Usage in component function App() { const { memberstack, member, loading } = useMemberstack(); if (loading) return
Loading...
; return (
{member ? (

Welcome, {member.auth.email}

) : ( )}
); } ``` ### Vue 3 (Composition API) ```javascript // composables/useMemberstack.js import { ref, onMounted, onUnmounted } from 'vue'; import memberstackDOM from '@memberstack/dom'; let memberstackInstance = null; export function useMemberstack() { const member = ref(null); const loading = ref(true); let authListener = null; onMounted(async () => { if (!memberstackInstance) { memberstackInstance = memberstackDOM.init({ publicKey: import.meta.env.VITE_MEMBERSTACK_KEY, useCookies: true }); } authListener = memberstackInstance.onAuthChange((m) => { member.value = m; loading.value = false; }); const { data } = await memberstackInstance.getCurrentMember(); member.value = data; loading.value = false; }); onUnmounted(() => { if (authListener) { authListener.unsubscribe(); } }); return { memberstack: memberstackInstance, member, loading }; } // Usage in component ``` ### Svelte/SvelteKit ```javascript // stores/memberstack.js import { writable } from 'svelte/store'; import memberstackDOM from '@memberstack/dom'; import { browser } from '$app/environment'; export const member = writable(null); export const loading = writable(true); let memberstackInstance = null; let authListener = null; export function initMemberstack() { if (!browser) return null; if (!memberstackInstance) { memberstackInstance = memberstackDOM.init({ publicKey: import.meta.env.VITE_MEMBERSTACK_KEY, useCookies: true }); authListener = memberstackInstance.onAuthChange((m) => { member.set(m); loading.set(false); }); memberstackInstance.getCurrentMember().then(({ data }) => { member.set(data); loading.set(false); }); } return memberstackInstance; } export function getMemberstack() { return memberstackInstance; } // Usage in +layout.svelte // Usage in component {#if $loading}

Loading...

{:else if $member}

Welcome, {$member.auth.email}

{:else} {/if} ``` ### Next.js (App Router) ```javascript // lib/memberstack.js 'use client'; import { createContext, useContext, useEffect, useState } from 'react'; import memberstackDOM from '@memberstack/dom'; const MemberstackContext = createContext(null); let memberstackInstance = null; export function MemberstackProvider({ children }) { const [member, setMember] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { if (!memberstackInstance) { memberstackInstance = memberstackDOM.init({ publicKey: process.env.NEXT_PUBLIC_MEMBERSTACK_KEY, useCookies: true }); } const authListener = memberstackInstance.onAuthChange((m) => { setMember(m); setLoading(false); }); memberstackInstance.getCurrentMember().then(({ data }) => { setMember(data); setLoading(false); }); return () => authListener.unsubscribe(); }, []); return ( {children} ); } export function useMemberstack() { const context = useContext(MemberstackContext); if (!context) { throw new Error('useMemberstack must be used within MemberstackProvider'); } return context; } // app/layout.js import { MemberstackProvider } from '@/lib/memberstack'; export default function RootLayout({ children }) { return ( {children} ); } // Usage in any client component 'use client'; import { useMemberstack } from '@/lib/memberstack'; export default function Header() { const { memberstack, member, loading } = useMemberstack(); if (loading) return
Loading...
; return member ? (

Welcome, {member.auth.email}

) : ( ); } ``` --- ## Webflow Integration **For Webflow users: Use data attributes - no coding required.** The `@memberstack/webflow` package is automatically loaded when you install Memberstack in your Webflow project. It handles everything through simple HTML attributes you add in Webflow's Designer - no JavaScript knowledge needed. **How it works:** Add `data-ms-*` attributes to any Webflow element to enable membership features. The package automatically detects these attributes and handles all the functionality. --- ### Gated Content (Show/Hide Based on Membership) The most common use case - restricting content visibility based on login status or plan membership: ```html
This content is only visible to logged-in members.
Please log in to see this content.
This premium content is only for Premium plan members.
Upgrade to Premium to unlock this content!
``` --- ### Form Handling ```html

We just sent you a 6-digit code.
Check your inbox and paste the code below.

``` ### Button Actions ```html ``` ### Member Text Injection ```html ``` --- ## Type Definitions ```typescript interface Member { id: string; verified: boolean; profileImage?: string; auth: { email: string; hasPassword: boolean; providers: Array<{ provider: string }>; }; loginRedirect?: string; stripeCustomerId?: string; createdAt: string; metaData: Record; customFields: Record; permissions: string[]; planConnections: Array<{ id: string; planId: string; active: boolean; status: string; type: string; }>; } interface Plan { id: string; name: string; description: string; status: string; redirects: { afterLogin: string; afterLogout: string; afterSignup: string; }; prices?: Array<{ id: string; amount: number; name: string; type: 'ONETIME' | 'SUBSCRIPTION'; currency: string; interval?: { type: 'YEARLY' | 'MONTHLY' | 'WEEKLY'; count: number; }; freeTrial?: any; setupFee?: { amount: number; name: string }; }>; } interface DataRecord { id: string; tableKey: string; createdAt: string; updatedAt: string; createdByMemberId?: string; activeMemberOwnsIt: boolean; data: Record; } interface MemberstackError { message: string; statusCode?: number; code?: string; details?: any; } ``` --- ## Server-Side Admin Package (@memberstack/admin) The Admin package provides server-side capabilities for member management, token verification, and webhook handling. **Never use in client-side code.** ### Installation & Setup ```bash npm install @memberstack/admin # or yarn add @memberstack/admin ``` ```javascript import memberstackAdmin from '@memberstack/admin'; // Initialize with your SECRET key (not public key!) const memberstack = memberstackAdmin.init(process.env.MEMBERSTACK_SECRET_KEY); ``` **Key Differences from DOM Package:** - Uses **secret keys** (`sk_*` for live, `sk_sb_*` for sandbox) - never expose these - Server-side only - no browser support - Can create, update, and delete members programmatically - Can verify JWT tokens from the DOM package ### Member Management ```javascript // List all members with pagination const members = await memberstack.members.list({ limit: 50, // Max 100 after: 12345, // Cursor for pagination order: "DESC" // or "ASC" }); // Returns: { totalCount, endCursor, hasNextPage, data: Member[] } // Get single member by ID const member = await memberstack.members.retrieve({ id: "mem_abc123" }); // Returns: { data: Member } // Create a new member const newMember = await memberstack.members.create({ email: "user@example.com", password: "SecurePassword123", plans: [{ planId: "pln_free-plan" }], // Optional customFields: { // Optional "first-name": "John", "last-name": "Doe" }, metaData: { source: "API" }, // Optional json: { preferences: {} }, // Optional loginRedirect: "/dashboard" // Optional }); // Update a member const updated = await memberstack.members.update({ id: "mem_abc123", data: { email: "newemail@example.com", customFields: { "first-name": "Jane" }, json: { preferences: { theme: "dark" } } } }); // Delete a member (PERMANENT) await memberstack.members.delete({ id: "mem_abc123" }); // Add free plan to member await memberstack.members.addFreePlan({ id: "mem_abc123", data: { planId: "pln_free-tier" } }); // Remove free plan from member await memberstack.members.removeFreePlan({ id: "mem_abc123", data: { planId: "pln_free-tier" } }); ``` ### Token Verification Verify JWT tokens from the DOM package's `getMemberCookie()`: ```javascript // Express.js middleware example async function authMiddleware(req, res, next) { try { const authHeader = req.headers.authorization; if (!authHeader?.startsWith('Bearer ')) { return res.status(401).json({ error: 'No token provided' }); } const token = authHeader.split(' ')[1]; // Verify the token const tokenData = await memberstack.verifyToken({ token, audience: process.env.MEMBERSTACK_APP_ID // Optional but recommended }); // Token payload: // { id: "mem_...", type: "member", iat: timestamp, exp: timestamp, iss, aud } req.member = tokenData; next(); } catch (error) { return res.status(401).json({ error: 'Invalid token' }); } } ``` ### Webhook Verification Verify webhook signatures (uses Svix under the hood): ```javascript // Express.js webhook handler // IMPORTANT: Use express.raw() NOT express.json() app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => { try { const payload = req.body.toString(); const isValid = memberstack.verifyWebhookSignature({ payload: JSON.parse(payload), headers: { 'svix-id': req.headers['svix-id'], 'svix-timestamp': req.headers['svix-timestamp'], 'svix-signature': req.headers['svix-signature'] }, secret: process.env.MEMBERSTACK_WEBHOOK_SECRET }); if (!isValid) { return res.status(401).send('Invalid signature'); } const data = JSON.parse(payload); switch (data.event) { case 'member.created': // Handle new member break; case 'member.updated': // Handle member update break; case 'member.plan.added': // Handle plan assignment break; case 'member.plan.canceled': // Handle plan cancellation break; } res.status(200).send('OK'); } catch (error) { res.status(400).send('Error'); } }); ``` ### Role-Based Access Control Example ```javascript // Check if member has specific plan function requirePlan(requiredPlanId) { return async (req, res, next) => { if (!req.member?.id) { return res.status(401).json({ error: 'Auth required' }); } const { data: member } = await memberstack.members.retrieve({ id: req.member.id }); const hasPlan = member.planConnections.some( plan => plan.planId === requiredPlanId && plan.status === 'ACTIVE' ); if (!hasPlan) { return res.status(403).json({ error: 'Plan required' }); } next(); }; } // Usage app.get('/premium', authMiddleware, requirePlan('pln_premium'), (req, res) => { res.json({ content: 'Premium only content' }); }); ``` --- ## MCP Server (AI Integration) **Beta Feature**: The Memberstack MCP (Model Context Protocol) Server connects AI tools directly to your Memberstack projects for natural language interaction. ### Supported AI Clients - Claude Code (CLI) - Cursor - Codex - Claude Desktop - Any MCP-compatible client ### Installation **Claude Code:** ```bash claude mcp add memberstack --scope user npx mcp-remote https://mcp.memberstack.com/mcp ``` **Codex:** ```bash codex mcp add memberstack -- npx mcp-remote https://mcp.memberstack.com/mcp ``` **Cursor / Claude Desktop (JSON config):** ```json { "mcpServers": { "memberstack": { "command": "npx", "args": ["mcp-remote", "https://mcp.memberstack.com/mcp"] } } } ``` ### Authentication 1. After configuring, start a new conversation 2. Browser window opens for OAuth 2.1 authentication 3. Log in with your Memberstack dashboard credentials 4. AI client now has access to your Memberstack projects **Security**: OAuth 2.1 with PKCE - credentials never shared with the AI client. **To logout/switch accounts:** ```bash rm -rf ~/.mcp-auth ``` ### Available Capabilities (~68 tools) **App & Environment Management:** - `listApps` - List your Memberstack projects - `switchApp` - Select which project to work with - `getMemberstackEnvironment` - Check current environment - `switchMemberstackEnvironment` - Toggle between SANDBOX and LIVE **Member Management:** - `getMembers` / `getMember` - List or retrieve members - `createMemberEmailPassword` - Create new members - `updateMember` - Update member details - `deleteMember` - Remove members - `addFreePlan` / `removeFreePlan` - Manage member plans - `exportMembers` / `importMembers` - Bulk operations **Plans & Pricing:** - `getPlans` / `getPlan` - List subscription plans - `createPlan` / `updatePlan` / `deletePlan` - Manage plans - `createPrice` - Create Stripe prices (LIVE mode only) **Data Tables:** - `getDataTables` / `getDataTable` - List tables and schemas - `createDataTable` / `updateDataTable` / `deleteDataTable` - `createDataTableField` / `updateDataTableField` / `deleteDataTableField` - `createDataRecord` / `updateDataRecord` / `deleteDataRecord` - `getDataRecords` - Query records **Gated Content:** - `getContentGroups` - List protected URL groups - `createRestrictedUrlGroup` - Create new content groups - `createRestrictedUrl` - Add protected URLs - `linkPlansToRestrictedUrlGroup` - Grant plan access to content **Knowledge Search:** - `memberstackKnowledgeSearch` - Search Memberstack documentation ### Example Prompts ``` "List my Memberstack apps" "Switch to the 'My SaaS' app" "What environment am I in?" "Switch to SANDBOX for testing" "Create a test member with email test@example.com" "Show me all members on the Pro plan" "Add the Free plan to member test@example.com" "Create a gated content group called 'Pro Articles'" "Protect /pro/* URLs and give Pro plan members access" "Export all members to CSV" ``` ### Common Workflows **First-time setup:** 1. "List my Memberstack apps" 2. "Switch to [app name]" 3. "What is my current environment?" 4. "Switch to SANDBOX" (for safe testing) **Creating a test member:** 1. "Make sure I'm in SANDBOX" 2. "Create a member with email test@example.com, password Test123!" 3. "Add the Free Tier plan to that member" **Setting up gated content:** 1. "Create a content group called 'Premium Content'" 2. "Add /premium/* to the Premium Content group" 3. "Grant access to the Premium Content group for members on the Pro Membership plan" ### Troubleshooting | Issue | Solution | |-------|----------| | Tools don't appear | Restart your client, check config syntax | | "Unauthorized" errors | Run `rm -rf ~/.mcp-auth` and re-authenticate | | "npx: command not found" | Install Node.js from nodejs.org | | Can't find created member | Check you're in the right environment (SANDBOX vs LIVE) | | "No app context" error | Use `listApps` then `switchApp` to select a project | --- ## Common Gotchas & Critical Notes ### 1. DEPRECATED PACKAGES - `@memberstack/react` - DEPRECATED, use `@memberstack/dom` - `@memberstack/nextjs` - DEPRECATED, use `@memberstack/dom` ### 2. Modal vs Method Parameters ```javascript // METHODS use objects in array: memberstack.signupMemberEmailPassword({ plans: [{ planId: "pln_..." }] // Array of OBJECTS }); // MODALS use strings in array: memberstack.openModal("SIGNUP", { signup: { plans: ["pln_..."] // Array of STRINGS } }); ``` ### 3. Plan IDs vs Price IDs - **Free plans**: Use Plan IDs (`pln_*`) with `addPlan()` - **Paid plans**: Use Price IDs (`prc_*`) with `purchasePlansWithCheckout()` ### 4. Member JSON REPLACES, Not Merges ```javascript // WRONG - deletes everything else: await memberstack.updateMemberJSON({ json: { theme: "dark" } }); // CORRECT - fetch, merge, update: const { data } = await memberstack.getMemberJSON(); await memberstack.updateMemberJSON({ json: { ...data, theme: "dark" } }); ``` ### 5. Data Tables Update IS Partial Unlike Member JSON, `updateDataRecord()` does a partial update - you don't need to fetch first. ### 6. Property Paths ```javascript // CORRECT: member.auth.email member.planConnections // WRONG: member.data.email member.plans ``` ### 7. Provider Auth Redirects Immediately Code after `loginWithProvider()`, `signupWithProvider()`, or `connectProvider()` will NOT execute. ### 8. Always Unsubscribe Auth Listeners ```javascript const authListener = memberstack.onAuthChange(callback); // On unmount: authListener.unsubscribe(); ``` ### 9. Custom Fields Are Strings All custom fields are stored as strings. Parse them when reading: ```javascript const age = parseInt(member.customFields.age); const preferences = JSON.parse(member.customFields.preferences); ``` ### 10. Test vs Live Mode Public Keys - Test/Sandbox: `pk_sb_*` - Live: `pk_*` - Limited to 50 test members in sandbox mode --- ### Common Mistakes to Avoid | Wrong | Correct | |-------|---------| | `memberstack.login()` | `memberstack.loginMemberEmailPassword()` | | `memberstack.signUp()` | `memberstack.signupMemberEmailPassword()` | | `memberstack.getUser()` | `memberstack.getCurrentMember()` | | `plans: ["pln_..."]` in signup methods | `plans: [{ planId: "pln_..." }]` | | `plans: [{ planId: "..." }]` in modals | `signup: { plans: ["pln_..."] }` | | `member.data.email` | `member.auth.email` | | `member.plans` | `member.planConnections` | | Using Plan IDs for paid checkout | Use Price IDs (`prc_*`) | | `window.Memberstack.init()` | `memberstackDOM.init()` | | Updating Member JSON without fetching | Always fetch, merge, then update | --- *Last updated: February 2026* *Package version: @memberstack/dom 2.x*