# How DrawDB Implements Custom Data Types: A Technical Deep Dive into the Type System

> Discover how DrawDB implements custom data types using a proxy catalog system. Learn to bypass built-in validation for user-defined types in this technical deep dive.

- Repository: [drawDB/drawdb](https://github.com/drawdb-io/drawdb)
- Tags: deep-dive
- Published: 2026-08-11

---

**DrawDB implements custom data types through a proxy-based catalog system in [`src/data/datatypes.js`](https://github.com/drawdb-io/drawdb/blob/main/src/data/datatypes.js) that returns `false` for unknown types, effectively bypassing built-in validation and allowing user-defined types to pass through to the target database.**

DrawDB is an open-source database diagramming tool that supports multiple SQL dialects through a flexible type-definition architecture. The system handles both built-in types and custom data types by cataloging type metadata in JavaScript base objects and exposing them through Proxy wrappers. This design allows the UI to validate standard types while gracefully falling back to permissive mode for any user-defined or database-specific types not present in the internal registry.

## The Core Type Registry in [`src/data/datatypes.js`](https://github.com/drawdb-io/drawdb/blob/main/src/data/datatypes.js)

The foundation of DrawDB's type system lives in **[`src/data/datatypes.js`](https://github.com/drawdb-io/drawdb/blob/main/src/data/datatypes.js)**, which declares comprehensive catalogs of database-specific types and wraps them in JavaScript Proxies for safe access.

### Base Type Catalogs

The file defines several base objects that enumerate supported types for each database engine:

- `defaultTypesBase`
- `mysqlTypesBase`
- `postgresTypesBase`
- `sqliteTypesBase`
- `mssqlTypesBase`
- `oraclesqlTypesBase`

Each entry in these catalogs is a structured object describing a data type's properties. A typical type definition includes:

- **`type`**: The string identifier (e.g., `"INTEGER"`, `"VARCHAR"`)
- **`color`**: Display color used in the UI diagram
- **`checkDefault`**: Validation function for default values
- **`isSized`**: Boolean indicating if the type supports length specifications
- **`defaultSize`**: Default length value when applicable
- **`hasPrecision`**: Boolean for numeric precision support
- **`hasQuotes`**: Boolean indicating if values require quote wrapping
- **`noDefault`**: Boolean flag for types that cannot have default values

### Proxy Wrapper Pattern

Each base catalog is wrapped in a JavaScript `Proxy` that standardizes access patterns:

```javascript
export const postgresTypes = new Proxy(postgresTypesBase, {
  get: (target, prop) => (prop in target ? target[prop] : false),
});

```

The proxy's getter returns the full type definition if the property exists in the base object, or **`false`** if the type is unknown. This eliminates undefined checks throughout the codebase and provides a consistent signal for custom type detection.

## Runtime Type Resolution with `useTypes`

The **`useTypes`** hook in [`src/hooks/useTypes.js`](https://github.com/drawdb-io/drawdb/blob/main/src/hooks/useTypes.js) bridges the static type catalogs with the dynamic UI state, selecting the appropriate proxy based on the user's selected database engine.

### Engine-Specific Selection

When a user creates a diagram, the application stores the selected database engine (e.g., `"mysql"`, `"postgres"`). The `useTypes` hook consumes this state and returns the corresponding proxy object:

```javascript
import { useTypes } from "./hooks/useTypes";

const { dbEngine } = useSettings();           // e.g., "postgres"
const types = useTypes(dbEngine);             // returns postgresTypes proxy

// Accessing a built-in type
const intDef = types["INTEGER"];              // → type definition object

```

### Custom Type Detection Logic

When a column references a type not present in the catalog, the proxy returns `false`:

```javascript
const customDef = types["MY_CUSTOM_TYPE"];    // → false

if (!customDef) {
  // UI treats this as a custom data type
  // Skip built-in validation; delegate to target database
}

```

This behavior allows DrawDB to distinguish between validated built-in types and arbitrary user-defined types without maintaining an exhaustive list of every possible database-specific extension.

## Validation Behavior and Fallbacks

The type system uses the proxy's return value to control validation logic throughout the application.

### Bypassing Built-in Validation

Most UI components that edit column properties call `type.checkDefault(field)` before persisting default values. Because the proxy returns `false` for unknown types, calling code can detect custom types and skip validation:

```javascript
// Inside column editing components
const typeDef = types[column.type];

if (typeDef && typeDef.checkDefault) {
  // Validate against built-in rules
  const isValid = typeDef.checkDefault(field);
} else {
  // Custom data type detected
  // Allow any default value; database will validate on execution
}

```

This **validation fallback** ensures that users can specify any type name—such as PostgreSQL's domain types or MySQL's spatial extensions—without DrawDB blocking the schema definition.

## Extending the Type System

While the proxy system handles ad-hoc custom types gracefully, developers can also extend the built-in catalogs permanently.

### Adding New Types to the Catalog

To add a new custom type to the internal registry, extend the appropriate base object in [`src/data/datatypes.js`](https://github.com/drawdb-io/drawdb/blob/main/src/data/datatypes.js) with a definition following the established schema:

```javascript
// In src/data/datatypes.js
postgresTypesBase["JSONB"] = {
  type: "JSONB",
  color: documentColor,
  checkDefault: (field) => true,    // Always valid
  hasCheck: false,
  isSized: false,
  hasPrecision: false,
  hasQuotes: true,
  noDefault: true,
};

export const postgresTypes = new Proxy(postgresTypesBase, {
  get: (target, prop) => (prop in target ? target[prop] : false),
});

```

Once added to the base object, the proxy automatically exposes the new type, and the UI treats it as a first-class citizen with full validation support.

## Summary

- **Proxy-based catalogs**: DrawDB wraps type definitions in JavaScript Proxies that return `false` for unknown types, creating a clear distinction between built-in and custom data types.
- **Engine-specific resolution**: The `useTypes` hook in [`src/hooks/useTypes.js`](https://github.com/drawdb-io/drawdb/blob/main/src/hooks/useTypes.js) dynamically selects the appropriate type catalog based on the active database engine.
- **Validation bypass**: When the proxy returns `false`, UI components skip `checkDefault` validation, allowing user-defined types to pass through unchecked.
- **Simple extensibility**: Developers can extend base objects like `postgresTypesBase` to add permanent custom types with full validation logic.

## Frequently Asked Questions

### What happens when DrawDB encounters an unknown data type?

When a type name is not found in the engine-specific base catalog, the Proxy in [`src/data/datatypes.js`](https://github.com/drawdb-io/drawdb/blob/main/src/data/datatypes.js) returns `false`. The UI interprets this as a custom data type and bypasses built-in validation for that column, allowing the target database to handle type checking when the SQL is executed.

### How does the `useTypes` hook determine which type catalog to use?

The `useTypes` hook reads the current database engine from the application settings (e.g., `"mysql"`, `"postgres"`) and returns the corresponding pre-configured Proxy object (`mysqlTypes`, `postgresTypes`, etc.). This ensures that type validation and UI colors match the specific SQL dialect being diagrammed.

### Can I add my own custom types to DrawDB's internal registry?

Yes. You can extend any base object in [`src/data/datatypes.js`](https://github.com/drawdb-io/drawdb/blob/main/src/data/datatypes.js) (such as `postgresTypesBase` or `mysqlTypesBase`) with a new type definition object containing properties like `type`, `color`, `checkDefault`, and `isSized`. After rebuilding, the Proxy will expose your new type as a built-in option with full validation support.

### Why does the type proxy return `false` instead of `undefined`?

Returning `false` instead of `undefined` provides a falsy value that explicitly indicates "unknown type" while avoiding accidental undefined reference errors. This allows the UI to use simple truthiness checks (`if (typeDef)`) to determine whether to apply built-in validation or treat the column as a custom data type requiring no client-side checks.