01
Core concepts
The ownership boundary. Tables, storage, functions, policies, keys, logs, webhooks, and apps belong to a project.
dev, test, and prod isolate data and services. Codex should use dev by default.
A published app URL under /f/{folder}/. One project can publish multiple folders that share the same project services.
A business role such as admin, guest, or legal. It controls what the running app can do.
02
Recommended workflow
- Create or select a project in the console.
- Build the mini app using the Appshare SDK rather than raw REST calls.
- Write sensitive or identity-dependent business logic as a Project Function.
- Publish the folder to
devand test with realistic server data. - Configure runtime policy, folder visibility, and allowed runtime profiles.
- Promote explicitly to
prodwhen the app is ready.
Do not use browser storage as the source of truth for business data. Use project tables, project storage, or functions.
03
Managed services
Tables
Project tables store JSON rows per environment. Schemas can validate shape, generate SDK types, and mark PII/privacy metadata.
Storage
Project storage keeps files separate from published app assets. Buckets are environment-scoped and controlled by runtime policy.
Project Functions
Functions are small server-side JavaScript handlers for business logic. They receive a safe ctx and cannot access Node APIs, filesystem, network, or secrets directly.
Email, LLM, jobs, and webhooks
Email and LLM calls are logged and quota-controlled. Jobs handle async work such as imports/exports. Webhooks emit signed events with delivery history.
To bulk-load rows into a table, use a table_import job rather than looping create_table_row: it writes the whole batch in one store write and one SQLite transaction. Pass the data inline as source.content (JSON or CSV, payload up to 5MB) or from a storage object via source.bucket/source.key (up to 10MB). To load several tables from one upload, use a table_import_zip job pointing at an uploaded zip that contains a manifest.json (a tables array of file/table/mapping entries) plus the data files. Pass wait: true to run an import job inline and get the finished result in one call instead of polling.
To read from an external API (e.g. Personio), an admin sets up a connector that holds the credential once and grants access per person or role. Apps call it live with appshare.connector("personio").call("employees") (the token stays on the server), or sync it into a table with a table_import job using source.connector/source.module. You never put the API token in the app or in per-project secrets.
04
Frontend lanes
Use HTML, vanilla JS, Bootstrap, /sdk/appshare.js, /sdk/appshare-ui.js, and /sdk/appshare-icons.js. This is the default for CRUDs, dashboards, forms, and master-detail modals.
Use React only for advanced state, nested editors, rich interaction, or reusable components. Keep the stack closed and use the Appshare React Kit.
const appshare = Appshare.create({
projectId: "prj_...",
environment: "dev"
});
const rows = await appshare.table("orders").list({ limit: 50 });
const result = await appshare.function("mis_incidencias_pendientes").invoke({ limit: 20 });
Icons
Appshare self-hosts a curated icon set (about 100 icons, the same Lucide geometry the console uses), so apps do not need an icon CDN.
Load /sdk/appshare-icons.js and use the <as-icon> element or a data-icon placeholder; icons render inline,
take the current text colour and size with the surrounding text.
<script src="/sdk/appshare-icons.js"></script>
<button class="btn btn-dark"><as-icon name="plus"></as-icon> New task</button>
<button class="btn btn-outline-dark btn-sm" aria-label="Refresh"><i data-icon="refresh-cw"></i></button>
// when building HTML strings
row.innerHTML = AppshareIcons.markup("trash-2", { label: "Delete" });
The full name list is at /sdk/appshare-icons.svg (one <symbol> per icon). Names are Lucide names in
kebab-case: search, trash-2, refresh-cw, triangle-alert, chart-column… If an icon you need is missing,
ask for it: the set is generated by scripts/gen_appshare_icons.mjs and adding a name is a one-line change.
05
Security model
Administrative roles
Project owner, editor, and viewer control who can manage the project from the console and APIs.
Runtime policies
Runtime policies control what the app can do at runtime: list/view/create/edit/delete table rows, invoke functions, call email/LLM, create jobs, access storage, or trigger webhooks.
Folder profile gates
A folder can set allowed_runtime_roles. For example, a public folder may still be available only to profile legal.
PII and privacy levels
Table schemas can mark fields with pii, x-pii, privacy_level, and pii_category. When a response exposes PII, appshare writes an audit log of who accessed which field classes.
Secrets
Secrets are stored server-side and returned only as redacted metadata. Browser apps, generated SDKs, REST responses, and MCP tools must not expose secret values.
06
Agents and MCP
Codex and other agents should use project-aware MCP tools such as publish_app, promote_project_app,
upsert_project_function, create_project_job, and generate_project_sdk.
Use LLM instructions for machine-readable guidance. Use this page when a human needs the product model.
Project API keys use the hpk_... prefix, are scoped to one project, and should include only the scopes needed by the agent or integration.
07
Example: pending incidents
If an app needs "my pending incidents", do not download all incidents to the browser and filter there.
Create a Project Function that receives ctx.user, reads the allowed rows server-side, and returns only the safe result.
The repository includes a complete reference app at examples/pending-incidents/ with
frontend files, Project Functions, schema, runtime policy, seed data, publish payload, and import job mapping.
The public gallery is available at /examples.
function handler(event, ctx) {
return ctx.tables.list("incidents", {
where: { assignee_email: ctx.user.email, status: "pending" },
limit: event.limit || 50
});
}