How DrawDB Supports Custom Types for PostgreSQL: User-Defined Data Types Explained
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. 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.
Resolving Custom PostgreSQL Types in the Editor
The resolution logic lives in src/utils/customTypes.js within the resolveType(database, typeName) function.
The resolution flow follows this priority:
- Built-in lookup: The function first searches the native type map
dbToTypes[database]for the requested type name. - Custom fallback: If the type is not found in the built-in map, the function queries the custom type list returned by
getCustomTypesForDb("POSTGRES"). - Safe default: If neither source contains the type name, the system defaults to the generic
BLOBtype, 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 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
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
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
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
localStorageunder thecustom_typeskey and validated against a JSON Schema. - Seamless Resolution: The
resolveType()function insrc/utils/customTypes.jstreats custom types as first-class citizens, falling back toBLOBonly 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 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, 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →