@kanvas/core SDK
The official TypeScript SDK for Kanvas. One client for the GraphQL API, plus small modules — auth, settings, files, receivers, commerce, locations — that you import only when you need them.
Installation
npm install @kanvas/core@betaThe modular SDK (1.0.0-beta) ships on the beta tag. Without a tag, npm install @kanvas/core resolves to the legacy 0.x KanvasCore class, which has none of the @kanvas/core/* entry points below — see Migrating from the Legacy SDK at the end of this page.
Two Clients
Pick the client by where the code runs. Both return the same Client: an Apollo Client for GraphQL, plus an Axios instance at client.axios for the REST endpoints (uploads, receivers).
createClient — browser-safe
Sends your app key as X-Kanvas-App. Headers are functions, re-evaluated on every request, so a user token can live wherever you keep it:
import { createClient } from "@kanvas/core/app";
const client = createClient({
appKey: process.env.NEXT_PUBLIC_KANVAS_API_KEY!, // X-Kanvas-App (app key)
baseUrl: process.env.NEXT_PUBLIC_KANVAS_URL!,
headers: {
Authorization: () => `Bearer ${localStorage.getItem("kanvas_token") ?? ""}`,
},
});createAdminClient — server only
For agents and server-to-server work — no login required. Adds your admin key as X-Kanvas-Key, and throws if it is ever constructed in a browser so the key cannot leak. Set ssrMode when rendering on the server.
import { createAdminClient } from "@kanvas/core/app";
const admin = createAdminClient({
appKey: process.env.KANVAS_API_KEY!,
adminKey: process.env.KANVAS_ADMIN_KEY!, // X-Kanvas-Key (super admin)
baseUrl: process.env.KANVAS_URL!,
ssrMode: true, // Server Components, API routes, workers
});Client Options
| Field | Type | Description |
|---|---|---|
| appKey | string | Your app key, sent as X-Kanvas-App on every request |
| baseUrl | string | API origin, e.g. https://your-kanvas-api-url. The SDK appends /graphql |
| adminKey | string | createAdminClient only. Sent as X-Kanvas-Key |
| headers | Record<string, () => string | Promise<string>> | Extra headers as functions, evaluated per request |
| ssrMode | boolean | Apollo's SSR mode, for server rendering |
| apiVersion | string | Path joined to baseUrl. Defaults to "graphql" |
| credentials | "same-origin" | "omit" | "include" | Fetch credentials mode for the GraphQL link |
Modules
Every module is a factory that takes the client — createX(client) — and each entry point is its own bundle, so you only ship what you import.
| Field | Type | Description |
|---|---|---|
| @kanvas/core/app | createClient, createAdminClient | Build the client |
| @kanvas/core/auth | createAuth | Login, logout, refresh token, social login, password reset and change |
| @kanvas/core/settings | createSettings | App, company and user settings |
| @kanvas/core/file-system | createFileSystem | Uploads, attaching files to entities, profile photos, CSV import |
| @kanvas/core/receiver | createReceiver | Submit data to a webhook receiver |
| @kanvas/core/commerce | createCart, createOrder | Cart, orders, payment intents, in-app purchases |
| @kanvas/core/locations | createLocations | Countries, states and regions |
Everything else — users, leads, people, inventory, messages, workflows — is one GraphQL call away through the client itself; see Custom Modules below.
Authentication
import { createAuth } from "@kanvas/core/auth";
const auth = createAuth(client);
// Login. The session comes back under `login`.
const { login: session } = await auth.login({
email: "agent@example.com",
password: "password",
});
localStorage.setItem("kanvas_token", session.token);
// Also: session.refresh_token, session.token_expires, session.refresh_token_expires
// Refresh before the token expires
const renewed = await auth.refreshToken(session.refresh_token);
// OAuth / social providers
const social = await auth.socialLogin({ token: oauthToken, provider: "google" });
// Password flows
await auth.resetPassword({ hashKey, newPassword, verifyPassword });
await auth.changePassword({ currentPassword, newPassword, newPasswordConfirmation });
// Logout
await auth.logout();Registration is a plain GraphQL mutation — see Authentication — and runs through client.mutate like any custom call.
Settings
import { createSettings } from "@kanvas/core/settings";
const settings = createSettings(admin);
// App
const all = await settings.appSettings();
const theme = await settings.appSetting("theme");
await settings.setAppSetting({ entity_uuid: appUuid, key: "theme", value: "dark", public: true });
// Company and user settings follow the same shape
const company = await settings.getCompanySettings(companyUuid);
const locale = await settings.companySetting(companyUuid, "locale");
await settings.setUserSettings({ entity_uuid: userUuid, key: "locale", value: "es" });Files
import { createFileSystem } from "@kanvas/core/file-system";
const files = createFileSystem(client);
// Upload, then attach to any entity through its system module
const uploaded = await files.uploadFile(file); // { id, uuid, name, url }
await files.attachFile({
filesystem_uuid: uploaded.uuid,
field_name: "contract",
system_module_uuid: leadsModuleUuid,
entity_id: leadId,
});
// Profile photos and CSV imports
await files.updateUserPhotoProfile(photo, userId);
await files.uploadFileCsv(csv);Receivers
Push data into a workflow receiver from a form, a cron job or another system. It goes over REST, so it works with either client:
import { createReceiver } from "@kanvas/core/receiver";
const receiver = createReceiver(client);
await receiver.submitReceiverData(receiverUuid, {
title: "New lead from website",
pipeline_stage_id: 1,
people: { firstname: "Jane", lastname: "Doe" },
});
// Extra headers, e.g. a shared secret the receiver checks
await receiver.submitReceiverDataWithHeaders(receiverUuid, payload, { "X-Signature": signature });Commerce
import { createCart, createOrder } from "@kanvas/core/commerce";
const cart = createCart(client);
const orders = createOrder(client);
await cart.addToCart([{ variant_id: 1, quantity: 2 }]);
const current = await cart.getCart(); // { cart: { id, total, items, discounts } }
await cart.updateDiscountCodes(["WELCOME10"]);
const result = await orders.createOrderFromCart({
cartId: "default",
customer: { email: "customer@example.com", phone: "+18095551234" },
billing: { address: "123 Main St", city: "Santo Domingo", state: "DN", country: "DO", postal_code: "10101" },
});
// Payment intent for an amount, order history
const intent = await orders.generatePaymentIntent(49.99);
const history = await orders.getOrders({ first: 20, page: 1 });Locations
import { createLocations } from "@kanvas/core/locations";
const locations = createLocations(client);
const countries = await locations.getAllCountries({ first: 50 });
const states = await locations.getStatesByCountry(countryId);Custom Modules
The client is an Apollo Client, so anything the GraphQL API exposes is available without waiting for a wrapper. The built-in modules follow one shape — a private #client and a createX factory — so a custom module reads the same as the ones above. Add @apollo/client to your project for gql.
import { createClient } from "@kanvas/core/app";
import { gql } from "@apollo/client";
type Client = ReturnType<typeof createClient>;
class Leads {
#client: Client;
constructor(client: Client) { this.#client = client; }
async create(input: { title: string; people_id: number; pipeline_stage_id: number }) {
const { data } = await this.#client.mutate({
mutation: gql`mutation CreateLead($input: LeadInput!) {
createLead(input: $input) { id uuid title }
}`,
variables: { input },
});
return data.createLead;
}
async list(options: { first?: number; page?: number } = {}) {
const { data } = await this.#client.query({
query: gql`query Leads($first: Int, $page: Int) {
leads(first: $first, page: $page) { data { id uuid title } }
}`,
variables: options,
fetchPolicy: "no-cache",
});
return data.leads;
}
}
export const createLeads = (client: Client) => new Leads(client);
const leads = createLeads(client);
const lead = await leads.create({ title: "Enterprise deal", people_id: 1, pipeline_stage_id: 1 });Next.js
// lib/kanvas.ts — one admin client per server process
import { createAdminClient } from "@kanvas/core/app";
import { createSettings } from "@kanvas/core/settings";
export function getKanvas() {
const client = createAdminClient({
appKey: process.env.KANVAS_API_KEY!,
adminKey: process.env.KANVAS_ADMIN_KEY!,
baseUrl: process.env.KANVAS_URL!,
ssrMode: true,
});
return { client, settings: createSettings(client) };
}
// app/page.tsx — a Server Component
import { getKanvas } from "@/lib/kanvas";
export default async function Page() {
const { settings } = getKanvas();
const appSettings = await settings.appSettings();
return <pre>{JSON.stringify(appSettings, null, 2)}</pre>;
}In Client Components use createClient with the NEXT_PUBLIC_ variables. The admin key never belongs in a browser bundle.
Environment Variables
# Browser-safe (NEXT_PUBLIC_*): the app key only
NEXT_PUBLIC_KANVAS_URL=https://your-kanvas-api-url
NEXT_PUBLIC_KANVAS_API_KEY=your_app_key
# Server only. Never expose these
KANVAS_URL=https://your-kanvas-api-url
KANVAS_API_KEY=your_app_key
KANVAS_ADMIN_KEY=your_admin_keyErrors
Calls reject with the Apollo error object: graphQLErrors for anything the API returned, networkError for connectivity. See Error Handling for the categories and the validation payload.
Migrating from the Legacy SDK
The 0.x package exposed one KanvasCore instance with every module attached. The 1.0 SDK splits it into a client and factories:
| Legacy (0.x) | 1.0 |
|---|---|
| new KanvasCore({ url, key }) | createClient({ baseUrl, appKey }) · createAdminClient({ …, adminKey }) |
| genericAuthMiddleware(getToken) | headers: { Authorization: () => `Bearer ${getToken()}` } |
| kanvas.auth.login(email, password) | createAuth(client).login({ email, password }) |
| kanvas.settings.* | createSettings(client) |
| kanvas.filesystem.* | createFileSystem(client) |
| kanvas.cart.* · kanvas.order.* | createCart(client) · createOrder(client) |
| kanvas.receiver.* | createReceiver(client) |
| kanvas.locations.* | createLocations(client) |
| kanvas.users · leads · people · inventory · … | Not wrapped yet: call the GraphQL API through the client (see Custom Modules) |