Understanding Member Data & Security: Custom Fields, Metadata, JSON, and DataTables
⏱️ TL;DR
Where should I store member data? It depends on who needs to read and write it:
- Visible Custom Fields: Public profile data (First Name, Company). Read-writeable on the frontend via HTML data attributes or JavaScript.
-
Hidden Custom Fields (
restrictToAdmin): Sensitive admin state. Completely backend-only and 100% inaccessible to the browser. - Member JSON: Complex UI states or preferences (nested objects, lists of favorites). Read-writeable on the frontend via JavaScript.
- Metadata: Read-only on the frontend, writeable ONLY from the backend. Useful for external IDs or read-only flags.
- DataTables: Structured, multi-row database tables built directly inside Memberstack. Perfect for relational data and full CRUD operations.
⚠️ The JSON Gotcha: Updating Member JSON replaces the entire object. You must fetch, merge, and then save to avoid deleting existing data!
🔒 Security Rule: Never gate content using custom fields or JSON. These are client-side properties and can be bypassed by a smart user. Secure content gating must always be enforced via Plans.
1. The Member Data Matrix (Security Boundaries)
To keep your app secure, you must understand the difference between frontend-writeable data (which the member can modify in their browser) and backend-only data (which only your server can modify).
| Data Type | Frontend Read | Frontend Write | Backend Read/Write | Best Used For | Limits |
|---|---|---|---|---|---|
| Visible Custom Fields | ✅ Yes | ✅ Yes | ✅ Yes | Basic profile info (First Name, Job Title) | Max 100 fields per project |
| Hidden Custom Fields | ❌ No | ❌ No | ✅ Yes | Sensitive admin flags, backend integrations | Shared with 100-field limit |
| Member JSON | ✅ Yes | ✅ Yes | ✅ Yes | Complex UI state, arrays, nested preferences | Max 1MB per member |
| Metadata | ✅ Yes | ❌ No | ✅ Yes | Stripe IDs, paid status, read-only permissions | Max 500 characters |
| DataTables | Configurable | Configurable | ✅ Yes | Relational data, transaction logs, custom tables | Scale-dependent |
2. Custom Fields (Visible & Hidden)
Custom fields are global key-value pairs that you define globally in your Memberstack Dashboard. To manage them, go to Members in the left-hand menu, and then click the Custom Fields button in the top-right corner.
Visible Custom Fields (Frontend Read-Write)
You can link visible custom fields directly to HTML inputs on your website using data attributes.
-
The Attribute:
data-ms-member="your-field-id" - Security Level: Low. Any logged-in member can modify their own custom fields. If they open the browser console, they can write a script to change their job title or first name.
- When to use: Use for standard user-facing profile information.
To retrieve and update the logged-in member's custom fields via the DOM package:
const memberstack = window.$memberstackDom;
// 1. Retrieve custom fields
const { data: member } = await memberstack.getCurrentMember();
if (member) {
console.log(member.customFields); // Access custom fields object
}
// 2. Update custom fields
await memberstack.updateMember({
customFields: {
country: "Canada",
username: "johndoe"
}
});
Hidden Custom Fields (Backend-Only)
If you check the Hide from frontend (restrictToAdmin) option in your dashboard for a custom field, it becomes a Hidden Custom Field.
- Security Level: High. These fields are completely stripped from client-side payloads. The browser cannot read or write them.
- When to use: Use for sensitive admin states, internal CRM IDs, or flags that the frontend browser must never see.
Learn more about custom fields.
3. Member JSON (Frontend Read-Write, Complex Data)
If you need to store nested data, lists, or configurations that don't fit into a standard form field, use Member JSON.
- Security Level: Low. Like custom fields, the member has full write access to their JSON from the browser console.
- When to use: Storing non-sensitive UI states, dashboard configurations, or user preferences.
- Limit: Max 1MB per member.
⚠️ The "Replace" Gotcha (Extremely Important!)
Calling updateMemberJSON() completely replaces the existing JSON object. If you only send the key you want to update, you will delete everything else!
const memberstack = window.$memberstackDom;
// ❌ The Dangerous Way (Deletes all other keys in the JSON object):
await memberstack.updateMemberJSON({
json: {
theme: "dark" // This deletes any other keys currently stored in JSON!
}
});
// ✅ The Safe Way (Fetch, Merge, Update):
const { data: currentJson } = await window.$memberstackDom.getMemberJSON();
// ✅ Merge your new data with the old data, then update
json: {
...data, //Spreads existing keys
theme: "dark" // Safely updates or adds 'theme' while preserving existing keys
};
4. Metadata (Backend-Only Write, Frontend Read-Only)
Metadata is Memberstack's secure vault for individual member records.
- Security Level: High. It is physically impossible for a member to modify their own Metadata from the browser. It can be read on the frontend, but it can only be written or modified from the backend using your Server-Side Admin Secret Key.
- When to use: Use for administrative states, external IDs, or clearance flags that the frontend needs to read, but must not be allowed to change.
How to update Metadata securely from Node.js:
To update a member's metadata from your backend, use the @memberstack/admin package.
⚠️ Casing Rule: The property name inside the data object must be spelled
metaData(with a capital D).
import memberstackAdmin from "@memberstack/admin";
// Initialize the Admin SDK with your secret API key
const memberstack = memberstackAdmin.init("YOUR_SECRET_KEY");
try {
// Update the member's metadata
const updatedMember = await memberstack.members.update({
id: "mem_abc123",
data: {
metaData: {
admin_approved: true
clearance_level: "level-3"
}
}
});
5. DataTables (Structured & Relational)
DataTables act like a custom spreadsheet or relational database built directly inside Memberstack. Unlike Custom Fields, Metadata, or Member JSON, which store flat or nested data directly attached to a single member's profile, DataTables allow you to build structured, multi-row databases that respect member authentication and access rules.
- Why it matters: Unlike Member JSON, DataTables support partial updates and full CRUD (Create, Read, Update, Delete) operations on individual records without the "replace" gotcha.
- When to use: User-submitted blog posts, course progress/lesson completions, community forum posts, or transaction logs.
Retrieving Data Records
const memberstack = window.$memberstackDom;
try {
const { data: record } = await memberstack.getDataRecord({
table: 'articles',
recordId: 'rec_abc123'
});
console.log("Article Title:", record.data.title);
} catch (error) {
console.error("Failed to get record:", error.message);
}
Creating, Updating, and Deleting Records
const memberstack = window.$memberstackDom;
// 1. Create a new record
try {
const { data: newRecord } = await memberstack.createDataRecord({
table: 'articles',
data: {
title: 'My First Article',
content: 'Article content here...',
published: true
}
});
console.log("Created record ID:", newRecord.id);
} catch (error) {
console.error("Failed to create record:", error.message);
}
// 2. Update an existing record (Partial update)
try {
const { data: updatedRecord } = await memberstack.updateDataRecord({
recordId: 'rec_abc123',
data: {
published: false // Only updates the 'published' field
}
});
console.log("Updated record:", updatedRecord);
} catch (error) {
console.error("Failed to update record:", error.message);
}
// 3. Delete a record
try {
const { data: deletedRecord } = await memberstack.deleteDataRecord({
recordId: 'rec_abc123'
});
console.log("Deleted record ID:", deletedRecord.id);
} catch (error) {
console.error("Failed to delete record:", error.message);
}
// 4. Retrieve multiple records with filtering
try {
const { data } = await memberstack.queryDataRecords({
table: 'articles',
query: {
where: { published: { equals: true } },
orderBy: { createdAt: 'desc' },
take: 10
}
});
console.log("Published articles:", data.records);
} catch (error) {
console.error("Failed to query records:", error.message);
}
6. Security Rule: Why Gating Content Requires Plans
A common security mistake is attempting to gate website content using custom fields or JSON flags.
❌ The Insecure Pattern:
You write JavaScript on your page that checks a custom field:
// INSECURE: Do not do this for sensitive content!
const { customFields } = await memberstack.getCurrentMember();
if (member.customFields.is_premium !== "true") {
window.location.href = "/unauthorized";
}
- Why this is insecure: A user can easily bypass this by disabling JavaScript in their browser, using browser extensions to block redirects, or manually editing their custom fields in the console.
The Secure Pattern:
For Webflow sites, always gate your pages and content using Memberstack Plans and Content Groups. Memberstack handles this client-side by checking the member's active plans and redirecting them if they lack access.
To make this as secure as possible and prevent content flashing, you should also add Memberstack's Gating CSS snippet to your site header, which hides gated elements by default before the browser renders them. Learn more here!
Comments
6 comments
Please make an article on how to get the JSON of a member using the front-end JavaScript API. Thank you.
It'd be awesome to have an article on how to access and update the User's Json via Webflow's script in the front-end. Thanks Team!
In dire need of a guidance article here, specifically for accessing the JSON from a Webflow front-end. Thanks in advance 🙂
Just updated the article. To get JSON data use:
You can read more in our DOM package docs
Hello,
I would like to know if I could update the metadata and / or the JSON via Make (via a http request) or Postman ?
Thanks
William
Hey William de Broucker 👋
You should be able to update member metadata and json using the Memberstack Admin Package for REST
Please sign in to leave a comment.