diff --git a/README.md b/README.md
index 972009ad..55ebc242 100644
--- a/README.md
+++ b/README.md
@@ -70,6 +70,93 @@ base44 deploy
|---------|-------------|
| `base44 site deploy` | Deploy built site files to Base44 hosting |
+### Types
+
+| Command | Description |
+|---------|-------------|
+| `base44 types` | Generate TypeScript types from entity schemas |
+
+**Options:**
+- `-o, --output
` - Output directory (default: `src/base44`)
+- `--entities-only` - Only generate entity types, skip client types
+
+## TypeScript Type Generation
+
+Generate fully-typed interfaces from your entity schemas for type-safe SDK usage.
+
+### Usage
+
+```bash
+# Generate types (outputs to src/base44/)
+base44 types
+
+# Custom output directory
+base44 types --output ./types
+```
+
+### Generated Files
+
+| File | Contents |
+|------|----------|
+| `entities.ts` | Entity interfaces, CreateInput, UpdateInput, Filter types |
+| `client.ts` | Typed SDK client interface |
+| `index.ts` | Barrel exports |
+
+### Setup with @base44/sdk
+
+1. Generate types:
+ ```bash
+ base44 types
+ ```
+
+2. Add to `tsconfig.json`:
+ ```json
+ {
+ "include": ["src", "src/base44/entities.ts"]
+ }
+ ```
+
+3. Use in your code:
+ ```typescript
+ import { createClient } from '@base44/sdk';
+ import type { TypedBase44Client } from './base44/client';
+
+ const base44 = createClient({ appId: 'my-app' }) as TypedBase44Client;
+
+ // Fully typed!
+ const { items: tasks } = await base44.entities.Task.list();
+ await base44.entities.Task.create({ title: 'Buy milk' });
+ ```
+
+### Example
+
+Given an entity schema:
+```jsonc
+// base44/entities/task.jsonc
+{
+ "name": "Task",
+ "type": "object",
+ "properties": {
+ "title": { "type": "string", "description": "Task title" },
+ "completed": { "type": "boolean", "default": false }
+ },
+ "required": ["title"]
+}
+```
+
+Generated types:
+```typescript
+export interface Task extends BaseEntity {
+ title: string;
+ completed?: boolean;
+}
+
+export interface TaskCreateInput {
+ title: string;
+ completed?: boolean;
+}
+```
+
## Configuration
### Project Configuration
@@ -115,7 +202,12 @@ my-project/
│ └── my-function/
│ ├── config.jsonc
│ └── index.js
-├── src/ # Your frontend code
+├── src/
+│ ├── base44/ # Generated types (from `base44 types`)
+│ │ ├── entities.ts
+│ │ ├── client.ts
+│ │ └── index.ts
+│ └── ... # Your frontend code
├── dist/ # Built site files (for deployment)
└── package.json
```
diff --git a/docs/types-command.md b/docs/types-command.md
new file mode 100644
index 00000000..0d39e0ee
--- /dev/null
+++ b/docs/types-command.md
@@ -0,0 +1,307 @@
+# `base44 types` Command
+
+Generate TypeScript declaration files from entity JSON schemas for type-safe SDK usage.
+
+## Overview
+
+The `base44 types` command reads your local entity schemas and generates `.d.ts` files that augment the `@base44/sdk` types. This enables full TypeScript support with **zero changes to your code**.
+
+## Command
+
+```bash
+base44 types # Generate types for Node.js and Deno
+base44 types --node-only # Skip Deno types
+base44 types -o ./custom # Custom output directory
+```
+
+## Generated Files
+
+| File | Target | Module Augmentation |
+|------|--------|---------------------|
+| `base44/types.d.ts` | Node.js | `declare module '@base44/sdk'` |
+| `base44/types.deno.d.ts` | Deno | `declare module 'npm:@base44/sdk'` |
+
+Both files should be **gitignored** (they are generated artifacts).
+
+## Setup
+
+### Node.js
+
+Add to `tsconfig.json`:
+```json
+{
+ "include": ["src", "base44/types.d.ts"]
+}
+```
+
+Add to `.gitignore`:
+```gitignore
+base44/types.d.ts
+base44/types.deno.d.ts
+```
+
+### Deno
+
+Add to `deno.json`:
+```json
+{
+ "compilerOptions": {
+ "types": ["./base44/types.deno.d.ts"]
+ }
+}
+```
+
+Or add a reference in your entry file:
+```typescript
+///
+```
+
+## User Experience
+
+### 1. Define Entities
+
+```jsonc
+// base44/entities/task.jsonc
+{
+ "name": "Task",
+ "type": "object",
+ "properties": {
+ "title": { "type": "string", "description": "Task title" },
+ "completed": { "type": "boolean", "default": false },
+ "priority": { "type": "string", "enum": ["low", "medium", "high"] },
+ "dueDate": { "type": ["string", "null"] },
+ "tags": { "type": "array", "items": { "type": "string" } }
+ },
+ "required": ["title"]
+}
+```
+
+### 2. Generate Types
+
+```bash
+base44 types
+```
+
+Output:
+```
+┌ Base 44
+│
+◇ Types generated successfully
+│
+● Generated types for 1 entities
+● Output directory: /my-project/base44
+● Files:
+│ - base44/types.d.ts
+│ - base44/types.deno.d.ts
+│
+└ Types generated successfully!
+```
+
+### 3. Use in Code (Unchanged)
+
+```typescript
+import { createClient } from '@base44/sdk';
+
+const base44 = createClient({ appId: 'my-app' });
+
+// ✅ Fully typed - no casting, no generics, no type imports
+const { items } = await base44.entities.Task.list();
+
+// ✅ Autocomplete works
+items.forEach(task => {
+ console.log(task.title); // string
+ console.log(task.completed); // boolean | undefined
+ console.log(task.priority); // "low" | "medium" | "high" | undefined
+});
+
+// ✅ Type-safe create
+await base44.entities.Task.create({
+ title: 'Buy groceries',
+ priority: 'high'
+});
+
+// ❌ Compile error: 'titel' does not exist
+await base44.entities.Task.create({ titel: 'typo' });
+
+// ❌ Compile error: 'urgent' is not assignable to 'low' | 'medium' | 'high'
+await base44.entities.Task.create({ title: 'Test', priority: 'urgent' });
+```
+
+## Example Generated Types
+
+### Input: `base44/entities/task.jsonc`
+
+```jsonc
+{
+ "name": "Task",
+ "type": "object",
+ "properties": {
+ "title": { "type": "string", "description": "Task title" },
+ "completed": { "type": "boolean", "default": false },
+ "priority": { "type": "string", "enum": ["low", "medium", "high"] },
+ "dueDate": { "type": ["string", "null"] },
+ "tags": { "type": "array", "items": { "type": "string" } }
+ },
+ "required": ["title"]
+}
+```
+
+### Output: `base44/types.d.ts` (Node.js)
+
+```typescript
+// Auto-generated by Base44 CLI - DO NOT EDIT
+// Regenerate with: base44 types
+//
+// Setup: Add to tsconfig.json:
+// { "include": ["src", "base44/types.d.ts"] }
+
+// ─── Entity Types ────────────────────────────────────────────────
+
+export interface Task {
+ /**
+ * Task title
+ */
+ title: string;
+ completed?: boolean;
+ priority?: "low" | "medium" | "high";
+ dueDate?: string | null;
+ tags?: string[];
+}
+
+// ─── SDK Type Augmentation ───────────────────────────────────────
+
+declare module '@base44/sdk' {
+ interface EntityTypeRegistry {
+ Task: Task;
+ }
+}
+```
+
+### Output: `base44/types.deno.d.ts` (Deno)
+
+```typescript
+// Auto-generated by Base44 CLI - DO NOT EDIT
+// Regenerate with: base44 types
+//
+// Setup: Add to deno.json:
+// { "compilerOptions": { "types": ["./base44/types.deno.d.ts"] } }
+//
+// Or add to your entry file:
+// ///
+
+// ─── Entity Types ────────────────────────────────────────────────
+
+export interface Task {
+ /**
+ * Task title
+ */
+ title: string;
+ completed?: boolean;
+ priority?: "low" | "medium" | "high";
+ dueDate?: string | null;
+ tags?: string[];
+}
+
+// ─── SDK Type Augmentation ───────────────────────────────────────
+
+declare module 'npm:@base44/sdk' {
+ interface EntityTypeRegistry {
+ Task: Task;
+ }
+}
+```
+
+## How It Works
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ 1. Entity Schemas │
+│ base44/entities/task.jsonc │
+│ base44/entities/product.jsonc │
+└─────────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────────┐
+│ 2. CLI Command │
+│ $ base44 types │
+│ │
+│ Uses json-schema-to-typescript library │
+└─────────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────────┐
+│ 3. Generated .d.ts Files │
+│ base44/types.d.ts (Node.js) │
+│ base44/types.deno.d.ts (Deno) │
+│ │
+│ Contains: │
+│ - Entity interfaces │
+│ - declare module '@base44/sdk' { ... } │
+└─────────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────────┐
+│ 4. TypeScript Picks Up Types │
+│ Via tsconfig.json include or deno.json types │
+│ │
+│ base44.entities.Task → EntityHandler │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+## JSON Schema to TypeScript Mapping
+
+| JSON Schema | TypeScript |
+|-------------|------------|
+| `"type": "string"` | `string` |
+| `"type": "number"` | `number` |
+| `"type": "integer"` | `number` |
+| `"type": "boolean"` | `boolean` |
+| `"type": "null"` | `null` |
+| `"type": "array", "items": {...}` | `T[]` |
+| `"type": "object", "properties": {...}` | `{ ... }` |
+| `"type": ["string", "null"]` | `string \| null` |
+| `"enum": ["a", "b", "c"]` | `"a" \| "b" \| "c"` |
+
+## SDK Requirements
+
+For module augmentation to work, the SDK needs these additions:
+
+```typescript
+/**
+ * Base fields present on all entities.
+ */
+export interface BaseEntity {
+ id: string;
+ created_date: string;
+ updated_date: string;
+}
+
+/**
+ * Entity type registry - augmented by generated .d.ts files.
+ */
+export interface EntityTypeRegistry {}
+
+/**
+ * Check if registry has been augmented.
+ */
+type HasRegistry = keyof EntityTypeRegistry extends never ? false : true;
+
+/**
+ * Entities module - typed when registry is augmented, dynamic otherwise.
+ */
+export type EntitiesModule = HasRegistry extends true
+ ? { [K in keyof EntityTypeRegistry]: EntityHandler }
+ : { [entityName: string]: EntityHandler };
+```
+
+These SDK changes are **backward compatible** - existing code continues to work, and types "light up" once users generate and include the `.d.ts` files.
+
+## Workflow
+
+| Step | Action | Frequency |
+|------|--------|-----------|
+| 1 | Define entities in `base44/entities/*.jsonc` | When schema changes |
+| 2 | Run `base44 types` | After schema changes |
+| 3 | Add `.d.ts` to tsconfig/deno.json | Once |
+| 4 | Add `.d.ts` to .gitignore | Once |
+| 5 | Write code with full type support | Always |
diff --git a/package-lock.json b/package-lock.json
index a37a8861..260cf39a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,9 @@
"name": "base44",
"version": "0.0.15",
"license": "ISC",
+ "dependencies": {
+ "json-schema-to-typescript": "^15.0.4"
+ },
"bin": {
"base44": "dist/cli/index.js"
},
@@ -46,6 +49,23 @@
"node": ">=20.19.0"
}
},
+ "node_modules/@apidevtools/json-schema-ref-parser": {
+ "version": "11.9.3",
+ "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz",
+ "integrity": "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jsdevtools/ono": "^7.1.3",
+ "@types/json-schema": "^7.0.15",
+ "js-yaml": "^4.1.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/philsturgeon"
+ }
+ },
"node_modules/@babel/generator": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
@@ -918,6 +938,12 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@jsdevtools/ono": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
+ "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==",
+ "license": "MIT"
+ },
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz",
@@ -1676,7 +1702,6 @@
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
- "dev": true,
"license": "MIT"
},
"node_modules/@types/json5": {
@@ -1690,7 +1715,6 @@
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==",
- "dev": true,
"license": "MIT"
},
"node_modules/@types/lodash.kebabcase": {
@@ -2135,7 +2159,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true,
"license": "Python-2.0"
},
"node_modules/array-buffer-byte-length": {
@@ -3671,7 +3694,6 @@
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
@@ -4428,7 +4450,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -4474,7 +4495,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
@@ -4827,7 +4847,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -4856,6 +4875,29 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/json-schema-to-typescript": {
+ "version": "15.0.4",
+ "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.4.tgz",
+ "integrity": "sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@apidevtools/json-schema-ref-parser": "^11.5.5",
+ "@types/json-schema": "^7.0.15",
+ "@types/lodash": "^4.17.7",
+ "is-glob": "^4.0.3",
+ "js-yaml": "^4.1.0",
+ "lodash": "^4.17.21",
+ "minimist": "^1.2.8",
+ "prettier": "^3.2.5",
+ "tinyglobby": "^0.2.9"
+ },
+ "bin": {
+ "json2ts": "dist/src/cli.js"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -4936,6 +4978,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/lodash": {
+ "version": "4.17.23",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
+ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
+ "license": "MIT"
+ },
"node_modules/lodash.kebabcase": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
@@ -5027,7 +5075,6 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
- "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -5420,7 +5467,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -5501,6 +5547,21 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prettier": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz",
+ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
"node_modules/pretty-ms": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz",
@@ -6325,7 +6386,6 @@
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
diff --git a/package.json b/package.json
index 73cd9ccf..7ad3a61f 100644
--- a/package.json
+++ b/package.json
@@ -53,6 +53,7 @@
"json5": "^2.2.3",
"ky": "^1.14.2",
"lodash.kebabcase": "^4.1.1",
+ "open": "^11.0.0",
"p-wait-for": "^6.0.0",
"tar": "^7.5.4",
"tsdown": "^0.12.4",
@@ -60,10 +61,12 @@
"typescript": "^5.7.2",
"typescript-eslint": "^8.52.0",
"vitest": "^4.0.16",
- "zod": "^4.3.5",
- "open": "^11.0.0"
+ "zod": "^4.3.5"
},
"engines": {
"node": ">=20.19.0"
+ },
+ "dependencies": {
+ "json-schema-to-typescript": "^15.0.4"
}
}
diff --git a/src/cli/commands/types/generate.ts b/src/cli/commands/types/generate.ts
new file mode 100644
index 00000000..e1258e3a
--- /dev/null
+++ b/src/cli/commands/types/generate.ts
@@ -0,0 +1,55 @@
+import { Command } from "commander";
+import { log } from "@clack/prompts";
+import { generateTypes } from "@core/types/index.js";
+import { runCommand, runTask } from "../../utils/index.js";
+import type { RunCommandResult } from "../../utils/runCommand.js";
+import { relative } from "node:path";
+
+interface GenerateTypesOptions {
+ output?: string;
+ nodeOnly?: boolean;
+}
+
+async function generateTypesAction(
+ options: GenerateTypesOptions
+): Promise {
+ const result = await runTask(
+ "Generating TypeScript types",
+ async () => {
+ return await generateTypes({
+ output: options.output,
+ nodeOnly: options.nodeOnly,
+ });
+ },
+ {
+ successMessage: "Types generated successfully",
+ errorMessage: "Failed to generate types",
+ }
+ );
+
+ if (result.entityCount === 0) {
+ log.warn("No entities found in project");
+ return { outroMessage: "No types generated" };
+ }
+
+ log.info(`Generated types for ${result.entityCount} entities`);
+ log.info(`Output directory: ${result.outputDir}`);
+
+ const fileList = result.files
+ .map((f) => ` - ${relative(process.cwd(), f)}`)
+ .join("\n");
+ log.info(`Files:\n${fileList}`);
+
+ return { outroMessage: "Types generated successfully!" };
+}
+
+export const typesGenerateCommand = new Command("types")
+ .description("Generate TypeScript declaration files from entity schemas")
+ .option("-o, --output ", "Output directory (default: base44)")
+ .option("--node-only", "Only generate Node.js types, skip Deno types")
+ .action(async (options: GenerateTypesOptions) => {
+ await runCommand(() => generateTypesAction(options), {
+ requireAuth: false,
+ requireAppConfig: false,
+ });
+ });
diff --git a/src/cli/index.ts b/src/cli/index.ts
index d234fb33..982aa454 100644
--- a/src/cli/index.ts
+++ b/src/cli/index.ts
@@ -6,6 +6,7 @@ import { whoamiCommand } from "./commands/auth/whoami.js";
import { logoutCommand } from "./commands/auth/logout.js";
import { entitiesPushCommand } from "./commands/entities/push.js";
import { functionsDeployCommand } from "./commands/functions/deploy.js";
+import { typesGenerateCommand } from "./commands/types/generate.js";
import { createCommand } from "./commands/project/create.js";
import { dashboardCommand } from "./commands/project/dashboard.js";
import { deployCommand } from "./commands/project/deploy.js";
@@ -43,6 +44,9 @@ program.addCommand(entitiesPushCommand);
// Register functions commands
program.addCommand(functionsDeployCommand);
+// Register types commands
+program.addCommand(typesGenerateCommand);
+
// Register site commands
program.addCommand(siteDeployCommand);
diff --git a/src/core/index.ts b/src/core/index.ts
index 8f600d4c..1e5cf6eb 100644
--- a/src/core/index.ts
+++ b/src/core/index.ts
@@ -3,6 +3,7 @@ export * from "./resources/index.js";
export * from "./project/index.js";
export * from "./clients/index.js";
export * from "./site/index.js";
+export * from "./types/index.js";
export * from "./utils/index.js";
export * from "./errors.js";
export * from "./consts.js";
diff --git a/src/core/types/generator.ts b/src/core/types/generator.ts
new file mode 100644
index 00000000..c125e732
--- /dev/null
+++ b/src/core/types/generator.ts
@@ -0,0 +1,105 @@
+import { compile } from "json-schema-to-typescript";
+import type { EntityDefinition } from "./schema.js";
+
+const HEADER_NODE = `// Auto-generated by Base44 CLI - DO NOT EDIT
+// Regenerate with: base44 types
+//
+// Setup: Add to tsconfig.json:
+// { "include": ["src", "base44/types.d.ts"] }
+`;
+
+const HEADER_DENO = `// Auto-generated by Base44 CLI - DO NOT EDIT
+// Regenerate with: base44 types
+//
+// Setup: Add to deno.json:
+// { "compilerOptions": { "types": ["./base44/types.deno.d.ts"] } }
+//
+// Or add to your entry file:
+// ///
+`;
+
+/** Options for json-schema-to-typescript */
+const COMPILE_OPTIONS = {
+ bannerComment: "",
+ additionalProperties: false,
+ strictIndexSignatures: true,
+ enableConstEnums: true,
+ declareExternallyReferenced: false,
+};
+
+/**
+ * Generate TypeScript interface from JSON Schema
+ */
+async function generateEntityInterface(entity: EntityDefinition): Promise {
+ const schema = {
+ ...entity,
+ title: entity.name,
+ $schema: "http://json-schema.org/draft-07/schema#",
+ };
+
+ return await compile(schema, entity.name, COMPILE_OPTIONS);
+}
+
+/**
+ * Generate all entity interfaces
+ */
+async function generateEntityInterfaces(entities: EntityDefinition[]): Promise {
+ const interfaces = await Promise.all(entities.map(generateEntityInterface));
+ return interfaces.join("\n");
+}
+
+/**
+ * Generate the module augmentation block
+ */
+function generateModuleAugmentation(
+ entities: EntityDefinition[],
+ moduleName: string
+): string {
+ if (entities.length === 0) {
+ return "";
+ }
+
+ const registryEntries = entities
+ .map((e) => ` ${e.name}: ${e.name};`)
+ .join("\n");
+
+ return `
+// ─── SDK Type Augmentation ───────────────────────────────────────
+
+declare module '${moduleName}' {
+ interface EntityTypeRegistry {
+${registryEntries}
+ }
+}
+`;
+}
+
+/**
+ * Generate types.d.ts for Node.js
+ * Uses: declare module '@base44/sdk'
+ */
+export async function generateNodeTypes(entities: EntityDefinition[]): Promise {
+ const entityInterfaces = await generateEntityInterfaces(entities);
+ const moduleAugmentation = generateModuleAugmentation(entities, "@base44/sdk");
+
+ return `${HEADER_NODE}
+// ─── Entity Types ────────────────────────────────────────────────
+
+${entityInterfaces}
+${moduleAugmentation}`;
+}
+
+/**
+ * Generate types.deno.d.ts for Deno
+ * Uses: declare module 'npm:@base44/sdk'
+ */
+export async function generateDenoTypes(entities: EntityDefinition[]): Promise {
+ const entityInterfaces = await generateEntityInterfaces(entities);
+ const moduleAugmentation = generateModuleAugmentation(entities, "npm:@base44/sdk");
+
+ return `${HEADER_DENO}
+// ─── Entity Types ────────────────────────────────────────────────
+
+${entityInterfaces}
+${moduleAugmentation}`;
+}
diff --git a/src/core/types/index.ts b/src/core/types/index.ts
new file mode 100644
index 00000000..4484f023
--- /dev/null
+++ b/src/core/types/index.ts
@@ -0,0 +1,3 @@
+export { generateTypes, type GenerateOptions, type GenerateResult } from "./types.js";
+export { generateNodeTypes, generateDenoTypes } from "./generator.js";
+export { EntityDefinitionSchema, type EntityDefinition, type EntityField } from "./schema.js";
diff --git a/src/core/types/schema.ts b/src/core/types/schema.ts
new file mode 100644
index 00000000..c7519b37
--- /dev/null
+++ b/src/core/types/schema.ts
@@ -0,0 +1,47 @@
+import { z } from "zod";
+
+/** JSON Schema primitive types */
+export type JsonSchemaType = "string" | "number" | "integer" | "boolean" | "array" | "object" | "null";
+
+/**
+ * Entity field type definition (JSON Schema compatible)
+ * Supports both single types and union types (e.g., ["string", "null"])
+ */
+export interface EntityField {
+ type: JsonSchemaType | JsonSchemaType[];
+ description?: string;
+ default?: unknown;
+ /** Enum values - can be strings, numbers, or mixed */
+ enum?: (string | number | boolean | null)[];
+ /** For array types - schema of array items */
+ items?: EntityField;
+ /** For object types - nested property schemas */
+ properties?: Record;
+ /** For object types - list of required property names */
+ required?: string[];
+ /** JSON Schema format hint (date, date-time, email, uri, etc.) */
+ format?: string;
+ [key: string]: unknown;
+}
+
+/**
+ * Entity definition type
+ */
+export interface EntityDefinition {
+ name: string;
+ type?: "object";
+ properties?: Record;
+ required?: string[];
+ [key: string]: unknown;
+}
+
+/**
+ * Schema for a full entity definition
+ * Uses loose validation to allow JSON Schema flexibility
+ */
+export const EntityDefinitionSchema = z.object({
+ name: z.string().min(1, "Entity name cannot be empty"),
+ type: z.literal("object").optional(),
+ properties: z.record(z.string(), z.unknown()).optional(),
+ required: z.array(z.string()).optional(),
+}).passthrough();
diff --git a/src/core/types/types.ts b/src/core/types/types.ts
new file mode 100644
index 00000000..a7914c74
--- /dev/null
+++ b/src/core/types/types.ts
@@ -0,0 +1,98 @@
+import { join, dirname } from "node:path";
+import { mkdir, writeFile } from "node:fs/promises";
+import { readProjectConfig, findProjectRoot } from "../project/config.js";
+import { EntityDefinitionSchema, type EntityDefinition } from "./schema.js";
+import { generateNodeTypes, generateDenoTypes } from "./generator.js";
+
+export interface GenerateOptions {
+ /** Output directory relative to project root (default: "base44") */
+ output?: string;
+ /** Skip generating Deno types */
+ nodeOnly?: boolean;
+}
+
+export interface GenerateResult {
+ /** Number of entities processed */
+ entityCount: number;
+ /** Files that were generated */
+ files: string[];
+ /** Output directory path */
+ outputDir: string;
+}
+
+/**
+ * Parse entities into EntityDefinition format for type generation
+ */
+function parseEntities(entities: Record[]): EntityDefinition[] {
+ return entities
+ .map((entity) => {
+ const result = EntityDefinitionSchema.safeParse(entity);
+ if (result.success) {
+ return result.data;
+ }
+ // Skip invalid entities but log warning with error details
+ const entityName = (entity as { name?: string }).name ?? "unknown";
+ console.warn(
+ `Skipping invalid entity "${entityName}": ${result.error.issues
+ .map((i) => `${i.path.join(".")}: ${i.message}`)
+ .join(", ")}`
+ );
+ return null;
+ })
+ .filter((e): e is EntityDefinition => e !== null);
+}
+
+/**
+ * Generate TypeScript declaration files from local entity schemas.
+ *
+ * Generates:
+ * - types.d.ts - For Node.js (declare module '@base44/sdk')
+ * - types.deno.d.ts - For Deno (declare module 'npm:@base44/sdk')
+ */
+export async function generateTypes(
+ options: GenerateOptions = {}
+): Promise {
+ const { output = "base44", nodeOnly = false } = options;
+
+ // Find project root
+ const projectRoot = await findProjectRoot();
+ if (!projectRoot) {
+ throw new Error(
+ "Project root not found. Please run this command from within a Base44 project."
+ );
+ }
+
+ // Read project config and entities
+ const { entities } = await readProjectConfig(projectRoot.root);
+
+ // Parse entities into our schema format
+ const parsedEntities = parseEntities(entities);
+
+ // Determine output directory (base44/ by default)
+ const outputDir = join(projectRoot.root, output);
+
+ // Ensure output directory exists
+ await mkdir(outputDir, { recursive: true });
+
+ const files: string[] = [];
+
+ // Generate Node.js types.d.ts
+ const nodeTypesContent = await generateNodeTypes(parsedEntities);
+ const nodeTypesPath = join(outputDir, "types.d.ts");
+ await writeFile(nodeTypesPath, nodeTypesContent, "utf-8");
+ files.push(nodeTypesPath);
+
+ // Generate Deno types.deno.d.ts (unless node-only)
+ if (!nodeOnly) {
+ const denoTypesContent = await generateDenoTypes(parsedEntities);
+ const denoTypesPath = join(outputDir, "types.deno.d.ts");
+ await writeFile(denoTypesPath, denoTypesContent, "utf-8");
+ files.push(denoTypesPath);
+ }
+
+ return {
+ entityCount: parsedEntities.length,
+ files,
+ outputDir,
+ };
+}