The Extension Kit provides data storage capabilities for persisting app data on the Teachfloor platform. Data is automatically scoped by organization and app, with optional user-level scoping.
Overview
Three types of storage are available:
Storage Type Scope Use Case App Data Organization + App Shared settings, configurations User Data Organization + App + User User-specific preferences, state User Collection Organization + App + User Lists, activity logs, history
Each storage type requires appropriate read/write permissions. Write permissions automatically include read access. See Permissions Reference for details.
Security : All data is automatically encrypted at rest on the Teachfloor platform.
App Data Storage
Store data shared across all users in your organization.
Permissions Required
Read and Write :
{
"permissions" : [
{
"permission" : "appdata_write" ,
"purpose" : "Save and load app configuration and settings"
}
]
}
appdata_write automatically includes read access.
Read-Only (if you only need to read):
{
"permissions" : [
{
"permission" : "appdata_read" ,
"purpose" : "Load app configuration and settings"
}
]
}
Usage
import { store, retrieve } from '@teachfloor/extension-kit'
// Store app-wide data
await store ( 'settings' , {
theme: 'dark' ,
language: 'en' ,
notifications: true
}, 'appdata' )
// Retrieve app-wide data
const settings = await retrieve ( 'settings' , 'appdata' )
console. log (settings.theme) // 'dark'
Use Cases
Global app configuration
Organization-wide settings
Shared templates or presets
Feature flags
API keys (encrypted)
Example: App Configuration
import { store, retrieve, showToast } from '@teachfloor/extension-kit'
// Save configuration
async function saveAppConfig ( config ) {
try {
await store ( 'app-config' , config, 'appdata' )
showToast ( 'Configuration saved' , { color: 'green' })
} catch (error) {
console. error ( 'Failed to save:' , error)
showToast ( 'Failed to save configuration' , { color: 'red' })
}
}
// Load configuration
async function loadAppConfig () {
try {
const config = await retrieve ( 'app-config' , 'appdata' )
return config || getDefaultConfig ()
} catch (error) {
console. error ( 'Failed to load:' , error)
return getDefaultConfig ()
}
}
// Usage
await saveAppConfig ({
apiEndpoint: 'https://api.example.com' ,
maxRetries: 3 ,
timeout: 5000
})
const config = await loadAppConfig ()
User Data Storage
Store data specific to individual users.
Permissions Required
Read and Write :
{
"permissions" : [
{
"permission" : "userdata_write" ,
"purpose" : "Save and load your personal preferences and app data"
}
]
}
userdata_write includes read access.
Read-Only :
{
"permissions" : [
{
"permission" : "userdata_read" ,
"purpose" : "Load your personal preferences and app data"
}
]
}
Usage
import { store, retrieve } from '@teachfloor/extension-kit'
// Store user-specific data
await store ( 'preferences' , {
theme: 'light' ,
fontSize: 14 ,
sidebarCollapsed: false
}, 'userdata' )
// Retrieve user-specific data
const prefs = await retrieve ( 'preferences' , 'userdata' )
console. log (prefs.theme) // 'light'
Use Cases
User preferences
Personal settings
User state (last viewed page, filters)
User-specific configurations
Draft content
Example: User Preferences
import { store, retrieve } from '@teachfloor/extension-kit'
class PreferencesManager {
constructor () {
this .defaults = {
theme: 'light' ,
fontSize: 14 ,
notifications: true ,
autoSave: true
}
}
async load () {
try {
const prefs = await retrieve ( 'user-preferences' , 'userdata' )
return { ... this .defaults, ... prefs }
} catch (error) {
console. error ( 'Failed to load preferences:' , error)
return this .defaults
}
}
async save ( preferences ) {
try {
await store ( 'user-preferences' , preferences, 'userdata' )
return true
} catch (error) {
console. error ( 'Failed to save preferences:' , error)
return false
}
}
async update ( key , value ) {
const prefs = await this . load ()
prefs[key] = value
return this . save (prefs)
}
}
// Usage
const prefsManager = new PreferencesManager ()
// Load preferences
const prefs = await prefsManager. load ()
// Update a preference
await prefsManager. update ( 'theme' , 'dark' )
// Save all preferences
await prefsManager. save ({
theme: 'dark' ,
fontSize: 16 ,
notifications: false
})
User Collection Storage
Store lists of data items for a user, with pagination support.
Permissions Required
Read and Write :
{
"permissions" : [
{
"permission" : "usercollection_write" ,
"purpose" : "Save and load your activity history and saved items"
}
]
}
Note : usercollection_write includes read access.
Read-Only :
{
"permissions" : [
{
"permission" : "usercollection_read" ,
"purpose" : "Load your activity history and saved items"
}
]
}
Usage
Collections allow you to store multiple items under the same key and retrieve them with pagination.
import { createCollection } from '@teachfloor/extension-kit'
// Create a collection manager
const notes = createCollection ( 'user-notes' , { limit: 15 })
// Add items to the collection
await notes. add ({
title: 'My Note' ,
content: 'Note content' ,
createdAt: Date. now ()
})
await notes. add ({
title: 'Another Note' ,
content: 'More content' ,
createdAt: Date. now ()
})
// List items (first page)
const page1 = await notes. list ()
console. log (page1.items) // Array of collection records
console. log (page1.items[ 0 ].value) // Your actual data
console. log (page1.items[ 0 ].id) // Database record ID
console. log (page1.hasMore) // true if more pages exist
console. log (page1.nextCursor) // Cursor for next page
// Load next page
if (page1.hasMore) {
const page2 = await notes. list ({ cursor: page1.nextCursor })
}
// Update an existing item
const itemId = page1.items[ 0 ].id
await notes. update (itemId, {
title: 'Updated Title' ,
content: 'Updated content' ,
updatedAt: Date. now ()
})
// Remove an item
await notes. remove (itemId)
// Get all items (auto-pagination)
const allNotes = await notes. getAll ()
import { createCollection } from '@teachfloor/extension-kit'
const messages = createCollection ( 'chat-messages' , { limit: 20 })
// Manual pagination
const page1 = await messages. list ()
console. log (page1.items) // First 20 collection records
console. log (page1.items[ 0 ].value) // First item's data
console. log (page1.hasMore) // true if more exist
// Load next page
if (page1.hasMore) {
const page2 = await messages. list ({ cursor: page1.nextCursor })
}
// Auto-pagination (get all items)
const allMessages = await messages. getAll ()
Use Cases
Activity logs
User notes or annotations
Saved items or bookmarks
History or timeline data
Multi-entry forms
Example: Chat Messages
Complete example using the Collection Manager API:
import { createCollection, showToast } from '@teachfloor/extension-kit'
import { useState, useEffect } from 'react'
function ChatApp () {
const [ messages , setMessages ] = useState ([])
const [ isLoading , setIsLoading ] = useState ( false )
const [ nextCursor , setNextCursor ] = useState ( null )
// Create collection manager
const chatMessages = createCollection ( 'chat-messages' , { limit: 15 })
// Load initial messages
useEffect (() => {
loadMessages ()
}, [])
const loadMessages = async ( cursor = null ) => {
setIsLoading ( true )
try {
const page = await chatMessages. list ({ cursor })
// Extract the .value from each item
const items = page.items. map ( item => item.value)
setMessages ( prev => cursor ? [ ... prev, ... items] : items)
setNextCursor (page.hasMore ? page.nextCursor : null )
} catch (error) {
console. error ( 'Failed to load messages:' , error)
showToast ( 'Failed to load messages' , { type: 'error' })
} finally {
setIsLoading ( false )
}
}
const sendMessage = async ( text ) => {
try {
const message = {
role: 'user' ,
text,
timestamp: Date. now ()
}
await chatMessages. add (message)
// Optimistically add to UI
setMessages ( prev => [message, ... prev])
showToast ( 'Message sent' , { type: 'success' })
} catch (error) {
console. error ( 'Failed to send message:' , error)
showToast ( 'Failed to send message' , { type: 'error' })
}
}
const editMessage = async ( itemId , newText ) => {
try {
const page = await chatMessages. list ()
const item = page.items. find ( i => i.id === itemId)
if (item) {
await chatMessages. update (itemId, {
... item.value,
text: newText,
edited: true ,
editedAt: Date. now ()
})
// Update in UI
setMessages ( prev => prev. map ( m =>
m.id === itemId ? { ... item.value, text: newText, edited: true } : m
))
showToast ( 'Message updated' , { type: 'success' })
}
} catch (error) {
console. error ( 'Failed to update message:' , error)
showToast ( 'Failed to update message' , { type: 'error' })
}
}
const deleteMessage = async ( itemId ) => {
try {
await chatMessages. remove (itemId)
// Remove from UI
setMessages ( prev => prev. filter ( m => m.id !== itemId))
showToast ( 'Message deleted' , { type: 'success' })
} catch (error) {
console. error ( 'Failed to delete message:' , error)
showToast ( 'Failed to delete message' , { type: 'error' })
}
}
return (
< div >
{messages. map (( msg , i ) => (
< div key = {i}>{msg.text}</ div >
))}
{nextCursor && (
< button onClick = {() => loadMessages (nextCursor)}>
Load More
</ button >
)}
</ div >
)
}
Updating Collection Items
You can update existing collection items by their ID:
import { createCollection, showToast } from '@teachfloor/extension-kit'
const notes = createCollection ( 'user-notes' )
async function updateNote ( noteId , updates ) {
try {
// Get the current item
const page = await notes. list ()
const note = page.items. find ( item => item.id === noteId)
if ( ! note) {
showToast ( 'Note not found' , { type: 'error' })
return
}
// Update with merged data
await notes. update (noteId, {
... note.value,
... updates,
updatedAt: Date. now ()
})
showToast ( 'Note updated successfully' , { type: 'success' })
} catch (error) {
console. error ( 'Update failed:' , error)
showToast ( 'Failed to update note' , { type: 'error' })
}
}
// Usage
await updateNote ( '123' , {
title: 'Updated Title' ,
content: 'Updated content'
})
Important :
Requires usercollection_write permission
Item ID comes from item.id when listing items
Update replaces the entire value - merge with existing data if needed
Returns the updated value
Removing Collection Items
You can delete collection items by their ID:
import { createCollection, showToast } from '@teachfloor/extension-kit'
const bookmarks = createCollection ( 'saved-bookmarks' )
async function removeBookmark ( bookmarkId ) {
try {
await bookmarks. remove (bookmarkId)
showToast ( 'Bookmark removed' , { type: 'success' })
return true
} catch (error) {
console. error ( 'Delete failed:' , error)
showToast ( 'Failed to remove bookmark' , { type: 'error' })
return false
}
}
// Usage
const page = await bookmarks. list ()
const itemToDelete = page.items[ 0 ]
await removeBookmark (itemToDelete.id)
Important :
Requires usercollection_write permission
Item ID comes from item.id when listing items
Delete operations are permanent
Returns null on success
Complete CRUD Example
Here's a complete example showing create, read, update, and delete operations:
import { createCollection, showToast } from '@teachfloor/extension-kit'
import { useState, useEffect } from 'react'
function NotesManager () {
const [ notes , setNotes ] = useState ([])
const notesCollection = createCollection ( 'user-notes' , { limit: 20 })
// Create
const addNote = async ( title , content ) => {
try {
await notesCollection. add ({
title,
content,
createdAt: Date. now ()
})
await loadNotes () // Refresh list
showToast ( 'Note added' , { type: 'success' })
} catch (error) {
showToast ( 'Failed to add note' , { type: 'error' })
}
}
// Read
const loadNotes = async () => {
try {
const page = await notesCollection. list ()
setNotes (page.items)
} catch (error) {
showToast ( 'Failed to load notes' , { type: 'error' })
}
}
// Update
const updateNote = async ( noteId , updates ) => {
try {
const note = notes. find ( n => n.id === noteId)
await notesCollection. update (noteId, {
... note.value,
... updates,
updatedAt: Date. now ()
})
await loadNotes () // Refresh list
showToast ( 'Note updated' , { type: 'success' })
} catch (error) {
showToast ( 'Failed to update note' , { type: 'error' })
}
}
// Delete
const deleteNote = async ( noteId ) => {
try {
await notesCollection. remove (noteId)
setNotes ( prev => prev. filter ( n => n.id !== noteId))
showToast ( 'Note deleted' , { type: 'success' })
} catch (error) {
showToast ( 'Failed to delete note' , { type: 'error' })
}
}
useEffect (() => {
loadNotes ()
}, [])
return (
< div >
{notes. map ( note => (
< div key = {note.id}>
< h3 >{note.value.title}</ h3 >
< p >{note.value.content}</ p >
< button onClick = {() => updateNote (note.id, { title: 'New Title' })}>
Edit
</ button >
< button onClick = {() => deleteNote (note.id)}>
Delete
</ button >
</ div >
))}
< button onClick = {() => addNote ( 'New Note' , 'Content' )}>
Add Note
</ button >
</ div >
)
}
Data Types
All storage methods automatically handle serialization:
Supported Types
// String
await store ( 'name' , 'John Doe' , 'userdata' )
// Number
await store ( 'count' , 42 , 'userdata' )
// Boolean
await store ( 'enabled' , true , 'userdata' )
// Object
await store ( 'settings' , { theme: 'dark' , lang: 'en' }, 'userdata' )
// Array
await store ( 'items' , [ 1 , 2 , 3 , 4 , 5 ], 'userdata' )
// Nested Objects
await store ( 'config' , {
ui: { theme: 'dark' },
features: { beta: true },
limits: { max: 100 }
}, 'appdata' )
Type Handling
// Data is automatically serialized and deserialized
const settings = await retrieve ( 'settings' , 'userdata' )
// No need to JSON.parse - objects are returned as objects
console. log (settings.theme) // Direct property access
// Arrays remain arrays
const items = await retrieve ( 'items' , 'userdata' )
items. forEach ( item => console. log (item))
Security
All data stored through the Extension Kit is automatically encrypted at rest on the Teachfloor platform.
Safe to store :
User preferences and settings
App configurations
UI state and draft content
Non-sensitive user data
Cached public data
API keys for third-party services
Do not store :
User passwords
Credit card numbers or payment information
Social security numbers or national IDs
Private encryption keys
Data belonging to other users
Next Steps
→ Continue to Permissions
Additional Resources
Last modified on April 29, 2026