Skip to content

Server Functions

The application’s server-side logic lives in the server/functions.js (or .ts) file. These functions run on the server and can be called from the client using sdk.remote.call().

Required permission: remote_functions in manifest.json.

server/functions.js
/**
* Get server time
* @param {Object} params - Parameters sent by the client
* @param {Object} context - Execution context (pluginId, userId, db)
*/
export async function getServerTime(params, context) {
const now = new Date();
return {
iso: now.toISOString(),
locale: now.toLocaleString('en-US'),
timestamp: now.getTime()
};
}

With TypeScript:

server/functions.ts
interface Context {
pluginId: string;
userId: string;
db: {
execute: (sql: string, params?: unknown[]) => Promise<{ rows: unknown[] }>;
};
}
export async function getServerTime(
params: { format?: 'ISO' | 'locale' | 'timestamp' },
context: Context
) {
const now = new Date();
return {
iso: now.toISOString(),
locale: now.toLocaleString('en-US'),
timestamp: now.getTime()
};
}

Every server function receives the context parameter:

FieldTypeDescription
pluginIdstringThe plugin identifier
userIdstringThe ID of the calling user
dbobjectDatabase connection (only with database permission)
export async function myFunction(params, context) {
const { pluginId, userId, db } = context;
console.log(`[${pluginId}] Called by user: ${userId}`);
// ...
}

The db object provides access to the plugin’s own schema (plugin_{plugin_id}). Required permission: database.

export async function getItems(params, context) {
const { db, pluginId } = context;
// Query a table in the plugin's own schema
const result = await db.execute(`
SELECT id, name, created_at
FROM plugin_${pluginId}.items
WHERE active = $1
ORDER BY created_at DESC
LIMIT $2
`, [true, params.limit ?? 20]);
return {
items: result.rows,
total: result.rows.length
};
}
server/functions.js
export async function createItem(params, context) {
const { db, pluginId, userId } = context;
const { name, description } = params;
if (!name || name.trim().length === 0) {
throw new Error('Name is required');
}
const result = await db.execute(`
INSERT INTO plugin_${pluginId}.items (name, description, created_by)
VALUES ($1, $2, $3)
RETURNING id, name, created_at
`, [name.trim(), description ?? null, userId]);
return { item: result.rows[0] };
}
export async function updateItem(params, context) {
const { db, pluginId } = context;
const { id, name, description } = params;
await db.execute(`
UPDATE plugin_${pluginId}.items
SET name = $1, description = $2, updated_at = NOW()
WHERE id = $3
`, [name, description, id]);
return { success: true };
}
export async function deleteItem(params, context) {
const { db, pluginId } = context;
await db.execute(`
DELETE FROM plugin_${pluginId}.items WHERE id = $1
`, [params.id]);
return { success: true };
}
<script lang="ts">
const sdk = window.webOS!;
interface Item {
id: number;
name: string;
created_at: string;
}
let items = $state<Item[]>([]);
let loading = $state(false);
async function loadItems() {
loading = true;
try {
const result = await sdk.remote.call<{ items: Item[] }>('getItems', {
limit: 50
});
items = result.items;
} catch (error) {
sdk.ui.toast('Failed to load items', 'error');
} finally {
loading = false;
}
}
async function addItem(name: string) {
try {
await sdk.remote.call('createItem', { name });
sdk.ui.toast('Item created', 'success');
await loadItems();
} catch (error) {
sdk.ui.toast((error as Error).message, 'error');
}
}
</script>

Errors thrown from server functions automatically propagate to the client:

export async function riskyOperation(params, context) {
if (!params.id) {
throw new Error('ID is required');
}
try {
const result = await context.db.execute(
`SELECT * FROM plugin_${context.pluginId}.items WHERE id = $1`,
[params.id]
);
if (result.rows.length === 0) {
throw new Error('Item not found');
}
return { item: result.rows[0] };
} catch (error) {
// Log on the server side
console.error(`[${context.pluginId}] Error in riskyOperation:`, error);
// Propagate error to the client
throw error;
}
}

On the client:

try {
const result = await sdk.remote.call('riskyOperation', { id: 123 });
} catch (error) {
// error.message contains the error message thrown by the server
sdk.ui.toast(error.message, 'error');
}

Remote calls have a default timeout of 30 seconds. For longer operations, specify a custom timeout:

const result = await sdk.remote.call('longRunningTask', params, {
timeout: 120000 // 2 minutes
});

You can simulate server functions during development using the Mock SDK:

src/main.ts
MockWebOSSDK.initialize({
remote: {
handlers: {
getItems: async () => ({
items: [
{ id: 1, name: 'Test item', created_at: new Date().toISOString() }
]
}),
createItem: async ({ name }) => ({
item: { id: Date.now(), name, created_at: new Date().toISOString() }
})
}
}
});