# How DrawDB Supports Custom Types for PostgreSQL: User-Defined Data Types Explained

> Discover how DrawDB enables custom PostgreSQL types. It stores, validates, and integrates user-defined types seamlessly for consistent editor and SQL generation.

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

---

**DrawDB persists user-defined PostgreSQL data types in the browser's `localStorage`, validates them against a JSON Schema, and integrates them into the type resolution pipeline via `resolveType()` so they behave identically to built-in types throughout the editor and SQL generation.**

DrawDB is an open-source database schema design tool that allows users to extend PostgreSQL type definitions beyond built-in primitives. The custom type system stores definitions locally in the browser, validates their structure against strict schemas, and seamlessly merges them into the diagram editing workflow without requiring backend storage.

## Storage and Validation of Custom Types

Custom type definitions are serialized and saved under the **`custom_types`** key in the browser's `localStorage`.

When the application initializes, the raw JSON is parsed and validated using a `jsonschema.Validator` against the schema defined in [`src/data/schemas.js`](https://github.com/drawdb-io/drawdb/blob/main/src/data/schemas.js). Only entries that conform to the expected structure are retained, ensuring that malformed custom types cannot corrupt the diagram state.

Per-database isolation is maintained through the helper function `getCustomTypesForDb(database)`. When called with `"POSTGRES"`, this function extracts the relevant custom type entries and decorates each with metadata flags including `isCustom: true` and `hasCheck: false`, matching the shape of built-in type objects found in [`src/data/datatypes.js`](https://github.com/drawdb-io/drawdb/blob/main/src/data/datatypes.js).

## Resolving Custom PostgreSQL Types in the Editor

The resolution logic lives in [`src/utils/customTypes.js`](https://github.com/drawdb-io/drawdb/blob/main/src/utils/customTypes.js) within the `resolveType(database, typeName)` function.

The resolution flow follows this priority:

1. **Built-in lookup**: The function first searches the native type map `dbToTypes[database]` for the requested type name.
2. **Custom fallback**: If the type is not found in the built-in map, the function queries the custom type list returned by `getCustomTypesForDb("POSTGRES")`.
3. **Safe default**: If neither source contains the type name, the system defaults to the generic `BLOB` type, ensuring the diagram remains functional even when encountering unrecognized type definitions.

This ensures that custom PostgreSQL types participate in the same resolution path as native types like `INTEGER` or `VARCHAR`.

## Integrating Custom Types with Import, Export, and SQL Generation

When importing diagrams that contain custom type definitions, DrawDB uses the `mergeCustomTypes(incoming)` function to combine external type definitions with the user's existing local storage.

The merged type objects are consumed by the DBML parser at [`src/utils/dbml/types.js`](https://github.com/drawdb-io/drawdb/blob/main/src/utils/dbml/types.js) and the SQL generator. Because custom type objects carry identical properties to built-in types—including `type`, `color`, `isCustom`, and validation flags—they render in the UI and generate SQL DDL statements without special-case handling.

This architecture allows imported diagrams to bring their own PostgreSQL domain types or composite types while maintaining full compatibility with the editor's validation and migration workflows.

## Working with Custom PostgreSQL Types: Code Examples

### Defining and Saving a Custom Type

```javascript
import { getCustomTypes, saveCustomTypes } from "./utils/customTypes";

// Retrieve current custom types
const pgTypes = getCustomTypes();

// Add a new PostgreSQL domain type
pgTypes.POSTGRES = {
  ...pgTypes.POSTGRES,
  CURRENCY_CODE: { 
    type: "CURRENCY_CODE", 
    color: "#ffcc00",
    isCustom: true 
  },
};

// Persist to localStorage with schema validation
saveCustomTypes(pgTypes);

```

### Resolving Types in Diagram Components

```javascript
import { resolveType } from "./utils/customTypes";

// Resolve a custom type for a PostgreSQL column
const columnType = resolveType("POSTGRES", "CURRENCY_CODE");

console.log(columnType.isCustom); // true
console.log(columnType.color);    // "#ffcc00"
// Returns: { type: "CURRENCY_CODE", color: "#ffcc00", ... }

```

### Merging Types from Imported Diagrams

```javascript
import { mergeCustomTypes } from "./utils/customTypes";

// Custom types from an external DBML or JSON import
const importedDefinitions = {
  POSTGRES: {
    GEO_COORDINATE: { type: "GEO_COORDINATE", color: "#aabbcc" },
  },
};

// Merge into local storage; now available via resolveType()
mergeCustomTypes(importedDefinitions);

```

## Summary

- **Persistent Storage**: Custom PostgreSQL types are stored in the browser's `localStorage` under the `custom_types` key and validated against a JSON Schema.
- **Seamless Resolution**: The `resolveType()` function in [`src/utils/customTypes.js`](https://github.com/drawdb-io/drawdb/blob/main/src/utils/customTypes.js) treats custom types as first-class citizens, falling back to `BLOB` only when a type is unrecognized.
- **Database-Specific Mapping**: `getCustomTypesForDb()` ensures PostgreSQL custom types are isolated and properly decorated with metadata flags.
- **Import/Export Support**: `mergeCustomTypes()` allows diagrams to share custom type definitions, which integrate directly into the DBML parser and SQL generator without special handling.

## Frequently Asked Questions

### How does DrawDB validate custom PostgreSQL type definitions?

DrawDA validates custom types using a `jsonschema.Validator` against the schema defined in [`src/data/schemas.js`](https://github.com/drawdb-io/drawdb/blob/main/src/data/schemas.js) immediately after parsing from `localStorage`. Only objects that match the required structure—including valid type names and color properties—are retained in memory, preventing corrupted data from affecting the diagram editor.

### Can custom types be shared between different diagrams or users?

Yes. Custom types are stored in `localStorage` and can be exported as part of a diagram's JSON structure. When importing a diagram, the `mergeCustomTypes()` function combines the incoming type definitions with existing local storage, making those types immediately available via `resolveType()` for PostgreSQL columns.

### What happens if I reference a custom type that doesn't exist?

According to the source code in [`src/utils/customTypes.js`](https://github.com/drawdb-io/drawdb/blob/main/src/utils/customTypes.js), the `resolveType()` function defaults to the generic `BLOB` type when it cannot find a requested type in either the built-in `dbToTypes` map or the custom type registry. This ensures diagrams remain editable and exportable even when type definitions are missing.

### Where are custom types used in the SQL generation pipeline?

Custom types flow through [`src/utils/dbml/types.js`](https://github.com/drawdb-io/drawdb/blob/main/src/utils/dbml/types.js) and the SQL generator, which consume the resolved type objects from `resolveType()`. Because custom types mirror the structure of built-in types—including the `type` and `color` properties—they generate appropriate DDL statements without requiring database-specific conditional logic.