Data & Storage

    App Data Storage

    User-specific and collection/product-scoped data storage patterns.

    App Data Storage Guide

    The SmartLinks platform provides two distinct types of data storage for apps:

    Choose the Right Model First

    There are now two different families of flexible app data in SmartLinks, and they solve different problems:

    NeedBest FitWhy
    One settings blob per scopeappConfiguration.getConfig / setConfigSimple app setup and feature flags
    A few keyed scoped documents by IDappConfiguration.getData / setDataItemLightweight, direct, minimal overhead
    Rich app-owned entities with filtering, visibility, ownership, or lifecycleapp.recordsBetter default for real domain objects
    Workflow items that move through resolutionapp.casesStatus, priority, assignment, history
    Discussions, comments, reply chainsapp.threadsBuilt around replies and conversation flow

    Rule of Thumb

    • Use appConfiguration when the thing you are storing is basically config or a small keyed document.
    • Use app.records when the thing you are storing is a real object in your app.
    • Use app.cases when the object represents work to resolve.
    • Use app.threads when the object is conversational.

    This matters for AI-generated apps too: if the documentation only mentions setDataItem, builders will overuse it. In most non-trivial app flows, app.records is usually the stronger default.

    1. User-Specific Data (Global per User+App)

    Use the userAppData namespace for all user-specific data.

    User data is shared across all collections for a given user and app. This is perfect for storing user preferences, personal settings, and user-generated content that should persist regardless of which collection they're viewing.

    Use Cases

    • User allergies in an allergy tracking app
    • Garden bed layouts in a garden planning app
    • User preferences and settings
    • Shopping lists, wishlists, favorites
    • Personal notes and annotations

    API Endpoints

    GET    /public/auth/app/:appId              - Get user's single config blob
    POST   /public/auth/app/:appId              - Set user's single config blob
    DELETE /public/auth/app/:appId              - Delete user's config blob
    
    GET    /public/auth/app/:appId/data         - Get all user's data items
    GET    /public/auth/app/:appId/data/:itemId - Get a specific user data item
    POST   /public/auth/app/:appId/data         - Create/update a user data item
    DELETE /public/auth/app/:appId/data/:itemId - Delete a user data item
    

    SDK Usage

    Single Config Blob (Simple Key-Value)

    import { userAppData } from '@proveanything/smartlinks';
    
    // Get user's config
    const config = await userAppData.getConfig('allergy-tracker');
    
    // Save user's config
    await userAppData.setConfig('allergy-tracker', { 
      allergies: ['peanuts', 'shellfish'],
      notifications: true 
    });
    
    // Delete user's config
    await userAppData.deleteConfig('allergy-tracker');
    

    Multiple Keyed Data Items (Recommended)

    // Get all user's garden beds
    const beds = await userAppData.list('garden-planner');
    // Returns: [{ id: 'bed-1', name: 'Vegetables', ... }, { id: 'bed-2', ... }]
    
    // Get specific bed
    const bed = await userAppData.get('garden-planner', 'bed-1');
    
    // Save/update a bed
    await userAppData.set('garden-planner', { 
      id: 'bed-1', 
      name: 'Vegetable Bed', 
      plants: ['tomatoes', 'peppers'],
      location: { x: 10, y: 20 }
    });
    
    // Delete a bed
    await userAppData.remove('garden-planner', 'bed-1');
    

    Important Notes

    • Clean, simple API - Just pass the appId (no collection/product scoping)
    • ✅ User data requires authentication (Bearer token)
    • ✅ Data is automatically scoped to the authenticated user
    • ✅ Impossible to accidentally scope to collections

    2. Collection/Product-Scoped Data (Admin Configuration)

    Use the appConfiguration namespace for collection/product-scoped config and simple keyed documents.

    This data is scoped to specific collections, products, variants, or batches. It's typically configured by collection admins/owners and applies to all users viewing that collection/product.

    If you need richer app-owned entities with querying, lifecycle, access zones, parent-child relationships, or workflow semantics, use app-objects.md instead of forcing everything through setDataItem.

    Critical: admin: true Controls the Endpoint, Not Field Privacy

    This is the part that often gets misunderstood by apps and AI tools:

    • admin: true means "call the admin endpoint".
    • It does not mean "everything I wrote is now admin-only".
    • For app config and collection settings, root-level fields are usually part of the public payload.
    • If you need confidential values, store them under a top-level admin object.
    • Public reads omit that admin block; admin reads include it.

    Example for app config:

    await appConfiguration.setConfig({
      appId: 'warranty-portal',
      collectionId: 'my-collection',
      admin: true,
      config: {
        theme: 'gold',
        supportEmail: 'support@example.com',
        admin: {
          accessToken: 'secret-token',
          webhookSecret: 'top-secret'
        }
      }
    })
    
    const publicView = await appConfiguration.getConfig({
      appId: 'warranty-portal',
      collectionId: 'my-collection'
    })
    // { theme: 'gold', supportEmail: 'support@example.com' }
    
    const adminView = await appConfiguration.getConfig({
      appId: 'warranty-portal',
      collectionId: 'my-collection',
      admin: true
    })
    // { theme: 'gold', supportEmail: 'support@example.com', admin: { ... } }
    

    The same visibility pattern applies to collection settings via collection.getSettings() / collection.updateSettings().

    Use Cases

    • App-specific settings for a collection
    • Product-level configuration
    • Feature flags and toggles
    • Theme and branding settings
    • Public content that all users see
    • Small keyed content entries such as FAQs, menu items, or quick lookup documents

    API Endpoints

    GET    /public/collection/:collectionId/app/:appId
    POST   /admin/collection/:collectionId/app/:appId
    DELETE /admin/collection/:collectionId/app/:appId
    
    GET    /public/collection/:collectionId/product/:productId/app/:appId/data
    POST   /admin/collection/:collectionId/product/:productId/app/:appId/data
    ...
    

    SDK Usage

    import { appConfiguration } from '@proveanything/smartlinks';
    
    // Get collection-level app config
    const collectionConfig = await appConfiguration.getConfig({ 
      appId: 'warranty-portal', 
      collectionId: 'my-collection'
    });
    
    // Set collection-level config (requires admin auth)
    await appConfiguration.setConfig({ 
      appId: 'warranty-portal', 
      collectionId: 'my-collection',
      admin: true,
      config: { 
        warrantyPeriod: 24, 
        supportEmail: 'support@example.com' 
      }
    });
    
    // Get product-level data items
    const items = await appConfiguration.getData({ 
      appId: 'product-docs', 
      collectionId: 'my-collection',
      productId: 'product-123'
    });
    
    // Set product-level data item (requires admin auth)
    await appConfiguration.setDataItem({ 
      appId: 'product-docs', 
      collectionId: 'my-collection',
      productId: 'product-123',
      admin: true,
      data: { 
        id: 'manual-1', 
        title: 'User Manual',
        url: 'https://...' 
      }
    });
    

    Comparison Table

    FeatureUser DataScoped Config/DataApp Objects
    NamespaceuserAppDataappConfigurationapp.records / app.cases / app.threads
    ScopeUser + App (global)Collection/Product/Variant/BatchCollection + App
    Best forPersonal preferences and user-owned itemsConfig blobs and small keyed documentsRich entities and workflows
    Shared across collections?✅ Yes❌ No❌ No
    Requires auth?✅ Yes (user token)✅ Yes (admin token for write)Depends on public/admin endpoint and visibility
    Querying / filteringMinimalMinimalStrong
    Access control zonesUser-scoped onlyRoot fields + reserved admin block for settings/configdata / owner / admin zones
    Admin write required?❌ No✅ Yes (for write operations)Only for admin endpoints / admin fields

    When setDataItem Is Still the Right Answer

    setDataItem is still valuable and should not be treated as deprecated. It is the right fit when:

    • You need a handful of documents attached to a collection or product
    • Each item has a stable known ID
    • You mostly fetch by exact ID or list all items
    • You do not need rich lifecycle fields, reply chains, assignment, or advanced querying

    Examples:

    • Product FAQs
    • Menu definitions
    • Marketing content blocks
    • Widget registry entries
    • Static lookup tables for an app

    If the object starts needing richer semantics, migrate that use case to app.records, app.cases, or app.threads rather than stretching scoped data items too far.


    Migration from Old SDK

    Old SDK → New SDK

    // OLD: Get user config
    RemoteApi.get({ path: `public/auth/app/${appId}` })
    // NEW:
    userAppData.getConfig(appId)
    
    // OLD: Set user config
    RemoteApi.post({ path: `public/auth/app/${appId}`, data })
    // NEW:
    userAppData.setConfig(appId, data)
    
    // OLD: Get user data items
    RemoteApi.get({ path: `public/auth/app/${appId}/data` })
    // NEW:
    userAppData.list(appId)
    
    // OLD: Get user data item
    RemoteApi.get({ path: `public/auth/app/${appId}/data/${itemId}` })
    // NEW:
    userAppData.get(appId, itemId)
    
    // OLD: Set user data item
    RemoteApi.post({ path: `public/auth/app/${appId}/data`, data: item })
    // NEW:
    userAppData.set(appId, item)
    
    // OLD: Delete user data item
    RemoteApi.delete({ path: `public/auth/app/${appId}/data/${itemId}` })
    // NEW:
    userAppData.remove(appId, itemId)
    

    Complete API Reference

    userAppData (User-Specific Data)

    FunctionSignatureDescription
    getConfig(appId: string) => Promise<any>Get user's config blob
    setConfig(appId: string, config: any) => Promise<any>Set user's config blob
    deleteConfig(appId: string) => Promise<void>Delete user's config blob
    list(appId: string) => Promise<any[]>List all user's data items
    get(appId: string, itemId: string) => Promise<any>Get specific data item
    set(appId: string, item: any) => Promise<any>Create/update data item
    remove(appId: string, itemId: string) => Promise<void>Delete data item

    appConfiguration (Collection/Product-Scoped Data)

    FunctionSignatureDescription
    getConfig(opts: AppConfigOptions) => Promise<any>Get config for scope
    setConfig(opts: AppConfigOptions) => Promise<any>Set config for scope
    deleteConfig(opts: AppConfigOptions) => Promise<void>Delete config for scope
    getData(opts: AppConfigOptions) => Promise<any[]>List data items in scope
    getDataItem(opts: AppConfigOptions) => Promise<any>Get specific data item
    setDataItem(opts: AppConfigOptions) => Promise<any>Create/update data item
    deleteDataItem(opts: AppConfigOptions) => Promise<void>Delete data item