Skip to main content

TypeScript

Manual document types

import { createFrappeClient, type FrappeDoc, type FrappeInsert, FrappeError } from 'frappe-js-client'

type ToDo = FrappeDoc<{
doctype: 'ToDo'
description: string
status: 'Open' | 'Closed'
}>

type ToDoInsert = FrappeInsert<ToDo>

const frappe = createFrappeClient({ url })
const row = await frappe.db.getDoc<ToDo>('ToDo', 'abc')
await frappe.db.createDoc<ToDo>('ToDo', { description: 'Follow up', status: 'Open' })

FrappeDoc<T> adds name, doctype, owner, creation, modified, modified_by, idx, docstatus (0 | 1 | 2), and optional parent / parentfield / parenttype. Extra keys from the API exist at runtime but are not typed.

FrappeInsert<T> is the create payload: server-assigned meta is omitted, and doctype is optional because createDoc already takes it.

Link<"Customer"> is a branded string for documentation. At runtime it is still a string.

DocFromMap<Docs, K> looks up a generated map entry, or falls back to FrappeDoc<object>.

Literal fields arrays on getDocList / paginate / getDocListPage narrow the row (Pick) when passed as const. fields: '*' (the default) keeps the full document.

const rows = await frappe.db.getDocList('ToDo', { fields: ['name', 'status'] as const })
// Pick<ToDo, 'name' | 'status'>[] when Docs is generated

RPC-style generics default to unknown (call.get, db.getValue, db.getMeta, db.runMethod, workflow.apply, …). Pass a type argument or refine the result.

createFrappeClient<Docs>() defaults Docs to object — untyped getDoc('ToDo', name) is FrappeDoc<object>.

Failures are instanceof FrappeError (and instanceof Error).

Static types describe expected server data; they do not validate every successful response at runtime. Structural responses used by the client itself, including list containers, search-link results, and binary downloads, fail with ResponseError when malformed. Validate domain payloads at your application boundary when the Frappe site or custom method is not fully trusted.

Prefer importing types from frappe-js-client/types in generated files so runtime code is not pulled in.

Core types (from frappe-js-client / frappe-js-client/types)

TypeRole
FrappeDoc<T>Stored document + meta
FrappeInsert<T>Create payload
FrappeDocMetaKeys'name' | 'owner' | …
Link<DocType>Branded string
DocFromMap<Docs, K>Map lookup with FrappeDoc<object> fallback
RequestOptionsTrailing options on every public method
ApiVersion1 | 2
FrappeVersion14 | 15 | 16
CapabilitiesvalidateLinkAndFetch, listExpand, restListHonorsExtendedFilters
FrappeClient / ExtendedFrappeClientClient shapes
FrappeClientOptions / FrappeClientConfigInput vs frozen config
AuthStrategyCustom auth
Filter / Value / GetDocListArgs / RowForList queries
Transport / TransportRequest / TransportResponseCustom network layer

Generate types from your site

frappe-codegen (frappe-codegen) against a live v15+ site writes GeneratedDocTypes / GeneratedInserts:

import { createFrappeClient } from 'frappe-js-client'
import type { GeneratedDocTypes, GeneratedInserts } from './generated/frappe-types'

const frappe = createFrappeClient<GeneratedDocTypes>({ url, auth })
const todo = await frappe.db.getDoc('ToDo', name)
await frappe.db.createDoc('ToDo', {
description: 'Follow up',
status: 'Open',
} satisfies GeneratedInserts['ToDo'])

Check fields are required on the read type (0 | 1) and optional on *Insert. See Codegen.