@memberstack/admin
v1.6.0
Published
Official Memberstack Admin SDK for Node.js — server-side member, plan, and Data Table management with your secret key.
Readme
@memberstack/admin
The official Memberstack Admin SDK for Node.js. Manage members, plans, and Data Tables from your server with your secret key — securely, away from the browser.
⚠️ Server-side only. This package uses your secret key, which grants full administrative access to your app. Never expose it in client-side code, public repositories, or browser environments. For client-side auth, use
@memberstack/dom.
Installation
npm install @memberstack/admin
# or
yarn add @memberstack/adminQuick Start
import memberstackAdmin from '@memberstack/admin';
// Initialize with your secret key (find it in your Memberstack dashboard)
const memberstack = memberstackAdmin.init('sk_...'); // sk_sb_... for sandbox
// List members
const { data: members } = await memberstack.members.list({ limit: 10 });
// Create a member
const { data: member } = await memberstack.members.create({
email: '[email protected]',
password: 'a-strong-password'
});Store your secret key in an environment variable, never in code:
const memberstack = memberstackAdmin.init(process.env.MEMBERSTACK_SECRET_KEY);Secret Keys
- Sandbox keys (
sk_sb_...) — development and testing. Data is isolated from production. - Live keys (
sk_...) — production. Real member data and Stripe transactions.
The key determines the environment automatically; sandbox and live data are never mixed.
API Reference
All methods return a promise. Read methods resolve to a { data } envelope, and failures reject with a { code, message } object (see Error Handling).
Members
// List members (paginated)
const { data, totalCount, endCursor, hasNextPage } = await memberstack.members.list({
limit: 50, // 1–100 (default 50)
after: endCursor, // cursor from a previous page
order: 'DESC' // 'ASC' | 'DESC'
});
// Retrieve a member by id or email
const { data: member } = await memberstack.members.retrieve({ id: 'mem_...' });
const { data: byEmail } = await memberstack.members.retrieve({ email: '[email protected]' });
// Create a member
const { data: created } = await memberstack.members.create({
email: '[email protected]',
password: 'a-strong-password',
plans: [{ planId: 'pln_...' }], // optional, free plans only
customFields: { 'first-name': 'Jane' },
metaData: { source: 'admin-api' },
json: { preferences: { newsletter: true } },
loginRedirect: '/dashboard'
});
// Update a member (only the fields you pass change)
const { data: updated } = await memberstack.members.update({
id: 'mem_...',
data: { verified: true, customFields: { 'first-name': 'Janet' } }
});
// Delete a member
const { data } = await memberstack.members.delete({ id: 'mem_...' }); // -> { id }
// Add / remove a free plan
await memberstack.members.addFreePlan({ id: 'mem_...', data: { planId: 'pln_...' } });
await memberstack.members.removeFreePlan({ id: 'mem_...', data: { planId: 'pln_...' } });Notes:
customFieldsandmetaDataare shallow-merged on update;jsonis replaced. Updating an email does not send a verification email.addFreePlan/removeFreePlanaccept free plans only — use the@memberstack/domcheckout flow for paid plans.
Data Tables
Read, create, query, update, and delete records in your app's Data Tables. Create your tables and fields in the dashboard first — the SDK works with records.
// List tables (with field definitions)
const { data } = await memberstack.dataTables.list();
// Get one table by key
const { data: table } = await memberstack.dataTables.get({ table: 'contacts' });
// Create a record
const { data: record } = await memberstack.dataTables.createRecord({
table: 'contacts',
data: { name: 'Ada Lovelace', score: 9.5, active: true },
memberId: 'mem_...' // optional, associate with a member
});
// Get one record by id
const { data: one } = await memberstack.dataTables.getRecord({
table: 'contacts',
recordId: 'rec_...'
}); // -> { record }
// Query records (Prisma-like findMany / findUnique)
const { data: page } = await memberstack.dataTables.queryRecords({
table: 'contacts',
query: {
findMany: {
where: { active: { equals: true }, score: { gte: 5 } },
orderBy: { score: 'desc' },
take: 10
}
}
}); // -> { records, pagination }
// Update / delete a record
await memberstack.dataTables.updateRecord({ table: 'contacts', recordId: 'rec_...', data: { score: 8 } });
await memberstack.dataTables.deleteRecord({ table: 'contacts', recordId: 'rec_...' });Note:
DECIMALfield values come back as strings fromcreateRecord/updateRecord/deleteRecord, and as numbers fromgetRecord/queryRecords.
Verification
// Verify a member's JWT (from the client). Returns the decoded payload.
const payload = await memberstack.verifyToken({
token,
audience: process.env.MEMBERSTACK_APP_ID // optional but recommended
});
// payload.id -> the member's id
// Verify a webhook signature (svix). Returns true, or throws if invalid.
// The SDK looks up the svix headers by UPPERCASE key, but Node delivers
// incoming headers in lowercase — so map them, don't pass req.headers directly.
memberstack.verifyWebhookSignature({
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,
payload: req.body
});Common Patterns
Express auth middleware
import express from 'express';
import memberstackAdmin from '@memberstack/admin';
const memberstack = memberstackAdmin.init(process.env.MEMBERSTACK_SECRET_KEY);
async function requireAuth(req, res, next) {
try {
const token = (req.headers.authorization || '').replace('Bearer ', '');
const member = await memberstack.verifyToken({ token });
req.memberId = member.id;
next();
} catch {
res.status(401).json({ error: 'Unauthorized' });
}
}
app.get('/api/me', requireAuth, (req, res) => res.json({ memberId: req.memberId }));Webhook handler
app.post('/webhooks/memberstack', express.json(), (req, res) => {
try {
memberstack.verifyWebhookSignature({
// Map the lowercase incoming headers to the UPPERCASE keys the SDK reads.
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,
payload: req.body
});
} catch {
return res.status(400).send('Invalid signature');
}
// handle req.body.event ...
res.sendStatus(200);
});TypeScript Support
The package ships with full type declarations — no @types install needed. Params and responses are typed (e.g. members.list() resolves to a PaginatedPayload<Member>).
Error Handling
API calls reject with a plain object containing a code and message:
try {
await memberstack.members.update({ id: 'mem_does_not_exist', data: { verified: true } });
} catch (error) {
console.error(error.code, error.message);
// e.g. "generic-message", "There is no member with this identifier."
}
members.retrieve()is the exception — a missing member resolves to{ data: null }rather than throwing.
Rate limit: 25 requests/second per IP. Cache responses or batch work if you approach it.
Resources
License
MIT © Memberstack. See LICENSE.
