VISIMADE
← Developer Hub

Getting Started

  • Authentication
  • AI Coding Agents

Core APIs

Data APIs

Guides

  • Storage Modes
  • Client-Side APIs
  • Collection Schemas
  • Agent Spec
  • CMS Blog Example

Reference

Client-Side JavaScript APIs

JavaScript APIs automatically injected into your pages for data storage and retrieval based on storage mode.

When you create a page with a storage mode other than page, the platform automatically injects JavaScript APIs into your page. These APIs allow your page's JavaScript to store and retrieve data.

Important: Your HTML must include data-page-id="{{PAGE_ID}}" in the body tag for these APIs to work. The placeholder is automatically replaced with the actual page ID when the page is served.

Critical - Each storage mode provides ONE API only:

solo_appwindow.SoloData (private per-user data - others cannot see it)
team_appwindow.TeamData (shared team data)
social_appwindow.SocialData (public data with ownership - everyone can see, only creator can edit)

You cannot mix APIs! If your page is social_app mode, only SocialData is available.

Choosing between solo_app and social_app:

social_app can handle both "per-user data" AND "shared data":
SocialData.findMine('collection') - get only current user's records
record.createdBy.id - check who owns each record
• Only the creator can edit/delete their own records

Use solo_app only if data must be truly private (e.g., personal journals, private notes, sensitive health data). If other users seeing the data is acceptable, social_app works well.

Critical - Use the exact global variable names:

window.visimade.socialData - wrong namespace
window.socialData - wrong capitalization
const SocialData = ... - never redeclare these variables
SocialData.create(...) - use directly, it's already a global

Property naming convention: The client-side SDKs use camelCase for all property names, while the REST API uses snake_case. This follows JavaScript conventions for client code.

userId (not user_id) · createdAt (not created_at) · createdBy (not created_by) · joinedAt (not joined_at)

Critical - Always await .ready before checking auth:

The SDKs load asynchronously. You MUST await SocialData.ready (or TeamData.ready, SoloData.ready) before calling getCurrentUser() or isAuthenticated(). Otherwise, the user will appear logged out even when they are logged in.

Common mistake:
❌ Checking if SocialData is defined, then immediately calling getCurrentUser()
✅ Always await SocialData.ready first - this waits for the auth check to complete

// ❌ WRONG - user will always appear logged out
function waitForAPI() {
  if (typeof SocialData !== 'undefined') {
    const user = SocialData.getCurrentUser(); // Auth not loaded yet!
  }
}

// ✅ CORRECT - wait for auth check to complete
async function init() {
  await SocialData.ready;  // Waits for auth check
  const user = SocialData.getCurrentUser();  // Now works correctly
  if (user) {
    console.log('Logged in as', user.username);
  }
}

How PAGE_ID replacement works:

The {{PAGE_ID}} placeholder is replaced everywhere in your HTML when the page is served, including inside <script> tags. You can use it directly in JavaScript code.

<body data-page-id="{{PAGE_ID}}">

<script>
// Both approaches work:

// Option 1: Use the placeholder directly (replaced at serve time)
const PAGE_ID = {{PAGE_ID}};  // Becomes: const PAGE_ID = 123;

// Option 2: Read from data attribute
const PAGE_ID = document.body.dataset.pageId;

// Use in API calls - {{PAGE_ID}} works in strings too
fetch('/api/pages/{{PAGE_ID}}/social-data/posts')
  .then(res => res.json())
  .then(data => console.log(data));
</script>

Method Reference

All client-side SDKs (TeamData, SoloData, SocialData, CmsData) share the same core CRUD methods. Use the exact method names listed below.

Important: The methods are named find, findById, create, update, and delete. Common aliases like list, get, remove, and put also work, but prefer the canonical names shown below.

MethodAliasDescription
create(collection, data)Create a record
find(collection, options?)listList/query records
findById(collection, id)getGet a single record by ID
findMine(collection, options?)List only your records (Team/Social)
update(collection, id, data)putUpdate a record (shallow merge)
delete(collection, id)removeDelete a record
count(collection, options?)Count records
countBy(collection, field, values?)Count grouped by a field
batch(collection, operations)Batch create/update/delete (max 100)
defineSchema(collection, schema)Define a collection schema (owner only)
getSchema(collection)Get the schema for a collection
removeSchema(collection)Remove a schema (owner only)
uploadFile(file)Upload a file
listFiles(options?)List files
getFile(fileId)Get file metadata
getDownloadUrl(fileId)Get download URL (sync)
downloadFile(fileId, options?)Download as blob or open in tab
deleteFile(fileId)Delete a file
Auth & Identity Methods (all SDKs)
MethodDescription
readyPromise that resolves when auth check completes — always await before checking auth
getCurrentUser()Returns { id, username } or null
isAuthenticated()Returns boolean
checkAuth()Re-checks auth and returns result
promptLogin()Opens login modal or redirects to login
TeamData-Only Methods
MethodDescription
getMembers()List all team members
inviteMember(identifier, role?)Invite by userId, email, or username
generateInviteLink(role?)Generate a shareable invite link
joinWithCode(inviteCode)Join a team with an invite code
removeMember(userId)Remove a member (admin+)
updateMemberRole(userId, newRole)Change a member's role (owner only)
getRole()Current user's role
isMember()Whether user is an accepted team member
getMembership(){ userId, role, status } or null
canCreate()member+ can create records
canEdit(record)Creator or admin+ can edit
canDelete(record)Creator or admin+ can delete
canInvite()admin+ can invite
canManageRoles()owner only

SoloData API (solo_app mode)

For personal apps where each user has their own private data synced across devices. Data is only visible to the user who created it.

// SoloData is automatically available as window.SoloData
// NEVER declare or assign it - just use it directly

async function init() {
  await SoloData.ready;  // Wait for auth check
  if (!SoloData.isAuthenticated()) {
    SoloData.promptLogin();
    return;
  }
  // User is logged in, load their data
}

// CRUD Operations
await SoloData.create('todos', { text: 'Buy groceries', completed: false });
const { records } = await SoloData.find('todos', { orderBy: 'createdAt' });
// records = [{ id, data, createdAt, updatedAt }, ...]
// Note: camelCase (createdAt, updatedAt) not snake_case

await SoloData.update('todos', recordId, { completed: true });
await SoloData.delete('todos', recordId);

// Count records
const total = await SoloData.count('todos');  // count all
const done = await SoloData.count('todos', { where: { completed: true } });

// Count grouped by a field
const byCategory = await SoloData.countBy('todos', 'category');
// { "work": 5, "personal": 12 }

// Batch Operations — up to 100 ops in one request, one transaction
await SoloData.batch('todos', [
  { op: 'create', data: { text: 'New todo' } },
  { op: 'update', id: recordId, data: { completed: true } },
  { op: 'delete', id: oldRecordId }
]);

// File Management (max 50MB per file)
const file = await SoloData.uploadFile(fileInput.files[0]);
const { files } = await SoloData.listFiles();
const downloadUrl = SoloData.getDownloadUrl(fileId);

// Auth Helpers
SoloData.getCurrentUser()      // { id, username } or null
SoloData.isAuthenticated()     // boolean
SoloData.promptLogin()         // Opens login modal

TeamData API (team_app mode)

For team collaboration apps. Data is shared among invited team members with role-based permissions. Roles: owner (full control), admin (CRUD any record, invite members),member (CRUD own records), viewer (read-only).

// TeamData is automatically available as window.TeamData
// NEVER declare or assign it - just use it directly

async function init() {
  await TeamData.ready;  // Wait for auth and membership check
  if (!TeamData.isMember()) {
    showInvitePrompt();
    return;
  }
  // User is a team member, load shared data
}

// CRUD Operations (team-scoped)
await TeamData.create('tasks', { title: 'New task', status: 'pending' });
const { records } = await TeamData.find('tasks', { where: { status: 'active' } });
// records = [{ id, data, createdBy: { id, username }, createdAt, updatedAt }, ...]
// Note: camelCase (createdBy, createdAt) not snake_case

await TeamData.findMine('tasks');  // Only records created by current user
await TeamData.update('tasks', recordId, { status: 'done' });
await TeamData.delete('tasks', recordId);

// Count records
const activeCount = await TeamData.count('tasks', { where: { status: 'active' } });

// Count grouped by a field — replaces N separate find() calls with 1 request
const replyCounts = await TeamData.countBy('replies', 'parentId');
// { "msg-1": 5, "msg-2": 12 }

// Limit to specific values
const counts = await TeamData.countBy('replies', 'parentId', ['msg-1', 'msg-2']);

// Batch Operations — up to 100 ops in one request, one transaction
await TeamData.batch('tasks', [
  { op: 'create', data: { title: 'Task A' } },
  { op: 'update', id: recordId, data: { order: 0 } },
  { op: 'delete', id: oldRecordId }
]);

// Team Management (admin+ required for most)
const { members } = await TeamData.getMembers();
// members = [{ userId, username, role, status, joinedAt }, ...]
// Note: uses camelCase (userId, joinedAt) not snake_case (user_id, joined_at)

await TeamData.inviteMember('user@example.com', 'member');
const { inviteUrl } = await TeamData.generateInviteLink('member');
await TeamData.joinWithCode('ABC123');
await TeamData.removeMember(userId);  // Use member.userId, not member.user_id
await TeamData.updateMemberRole(userId, 'admin');  // owner only

// File Management
await TeamData.uploadFile(file);  // member+ required
const { files } = await TeamData.listFiles();
const url = TeamData.getDownloadUrl(fileId);
await TeamData.deleteFile(fileId);  // creator or admin+

// Permission Checks
TeamData.getRole()          // 'owner' | 'admin' | 'member' | 'viewer' | null
TeamData.canCreate()        // Can create records (member+)
TeamData.canEdit(record)    // Can edit this record (creator or admin+)
TeamData.canDelete(record)  // Can delete this record (creator or admin+)
TeamData.canInvite()        // Can invite members (admin+)
TeamData.canManageRoles()   // Can change roles (owner only)

// Auth Helpers
TeamData.getCurrentUser()   // { id, username } or null
TeamData.isMember()         // Is an accepted team member
TeamData.getMembership()    // { userId, role, status } or null

SocialData API (social_app mode)

For public, social features. All data is publicly readable, but users can only edit/delete their own records.

// SocialData is automatically available as window.SocialData
// NEVER declare or assign it - just use it directly

async function init() {
  await SocialData.ready;  // Wait for auth check
  await loadPublicData();  // Anyone can read
}

// CRUD Operations (public read, owner write)
await SocialData.create('comments', { text: 'Great post!' });
const { records } = await SocialData.find('comments', { where: { postId: 123 } });
// records = [{ id, data, createdBy: { id, username }, createdAt, updatedAt }, ...]
// Note: camelCase (createdBy, createdAt) not snake_case (created_by, created_at)

await SocialData.findMine('comments');  // Only user's own comments
await SocialData.update('comments', recordId, { text: 'Updated' });  // Owner only
await SocialData.delete('comments', recordId);  // Owner only

// Count records
const totalComments = await SocialData.count('comments', { where: { postId: '123' } });

// Count grouped by a field
const commentsPerPost = await SocialData.countBy('comments', 'postId');
// { "123": 8, "456": 3 }

// Batch Operations — up to 100 ops in one request, one transaction
await SocialData.batch('comments', [
  { op: 'create', data: { text: 'New comment' } },
  { op: 'update', id: recordId, data: { text: 'Edited' } },
  { op: 'delete', id: oldRecordId }
]);

// File Management (public view, owner delete)
await SocialData.uploadFile(file);
const { files } = await SocialData.listFiles();
const url = SocialData.getDownloadUrl(fileId);
await SocialData.deleteFile(fileId);  // Creator only

// Auth Helpers
SocialData.getCurrentUser()    // { id, username } or null
SocialData.isAuthenticated()   // boolean
SocialData.isPageOwner()       // Is the creator of THIS page (for admin UI)
SocialData.promptLogin()       // Opens login modal

// Check ownership for edit/delete buttons
const currentUser = SocialData.getCurrentUser();
const canEdit = currentUser && currentUser.id === record.createdBy.id;

Collection naming: Use lowercase with underscores (e.g., tasks, user_comments, poll_votes).


CmsData API (page_api_cms capability)

For creator-managed content like blogs, product catalogs, and FAQs. Only the page owner can write; all visitors can read free content. Works independently of storage mode — enable it as a page capability.

CmsData vs SocialData: CmsData is for content managed by the page creator (one writer, many readers). SocialData is for community content where any logged-in user can write. Use CmsData when only you (the creator) should publish content.

// CmsData is automatically available as window.CmsData
// NEVER declare or assign it - just use it directly

async function init() {
  await CmsData.ready;  // Wait for auth check
  await loadContent();  // Anyone can read free content
}

// Reading (everyone)
const { records, total } = await CmsData.find('posts', {
  where: { category: 'news' },
  orderBy: 'created_at',
  order: 'desc',
  limit: 10,
  offset: 0
});
// records = [{ id, collection, data, planId, createdAt, updatedAt }, ...]

const post = await CmsData.findById('posts', recordId);

// Writing (creator only)
await CmsData.create('posts', { title: 'New post', content: '<p>Hello</p>' });

// Paid content (gated behind a membership plan)
await CmsData.create('posts', { title: 'Premium' }, { planId: 42 });

// Update (merges data)
await CmsData.update('posts', recordId, { title: 'Updated' });

// Change plan gating
await CmsData.update('posts', recordId, {}, { planId: 42 });

// Delete
await CmsData.delete('posts', recordId);

// Batch Operations — up to 100 ops in one request, one transaction
await CmsData.batch('posts', [
  { op: 'create', data: { title: 'New post' } },
  { op: 'update', id: recordId, data: { title: 'Updated' } },
  { op: 'delete', id: oldRecordId }
]);

// Auth & Identity
CmsData.isCreator()        // true if current user owns this page
CmsData.isAuthenticated()  // boolean
CmsData.getCurrentUser()   // { id, username } or null
CmsData.promptLogin()      // Opens login modal

// Show admin UI only to the page creator
if (CmsData.isCreator()) {
  document.getElementById('admin-panel').style.display = 'block';
}

On this page

  • Setup & PAGE_ID
  • Method Reference
  • SoloData API
  • TeamData API
  • SocialData API
  • CmsData API