LakeQL
Introduction
  • Overview
  • Key Concepts
  • Package Map
Getting Started
  • Prerequisites
  • Quickstart
  • Environment Configuration
  • First Run
Architecture
  • System Overview
  • Data Flow
  • Request Lifecycle
Configuration
  • Environment Variables
  • Authentication
  • Trino Connection
create-app
  • Usage
  • Template Structure
  • Post Creation
Contributing
  • Local Development
  • Contribution Guide
Guides
  • Custom Resolvers
  • Extending Schema
  • Deploying
  • Mutations
  • Load Strategies
GitHub
LakeQL
  1. LakeQL
  2. Guides
  3. Extending Schema

On this page

  1. Extending Generated Schema
  2. Adding Fields to Generated Types
  3. Custom Comparison Types
  4. Computed Fields
  5. Custom Enum Types
  6. Tips for Extension Files

Extending Schema

Extend generated GraphQL types with additional fields, custom comparison operators, and computed values.

Extending Generated Schema #

Generated query schemas provide standard fields, filtering, and pagination. You can extend them with additional fields, custom input types, or computed values without modifying generated files.

Adding Fields to Generated Types #

Create a companion file that adds fields to an existing generated type:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import { builder } from "@lakeql/api/builder"

// Reference the generated type by name
const OrdersType = builder.objectRef<{ id: number; total: number }>("Orders")

// Add a computed field
builder.objectField(OrdersType, "formattedTotal", (t) =>
  t.string({
    resolve: (parent) => `$${parent.total.toFixed(2)}`,
  })
)

// Add a field that fetches from another source
builder.objectField(OrdersType, "customerName", (t) =>
  t.string({
    nullable: true,
    resolve: async (parent) => {
      // Fetch customer name from a secondary source
      return null
    },
  })
)
Extension files must be placed in the schema path directory (e.g. src/schemas/custom/). Never place custom code inside generated directories — it will be overwritten on the next pull.

Custom Comparison Types #

Generated schemas include standard comparison operators (eq, neq, gt, lt, like, etc.). You can define custom input types for more specific filtering:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { builder } from "@lakeql/api/builder"

export const DateRangeInput = builder.inputType("DateRangeInput", {
  fields: (t) => ({
    from: t.string({ required: true }),
    to: t.string({ required: true }),
  }),
})

export const AmountRangeInput = builder.inputType("AmountRangeInput", {
  fields: (t) => ({
    min: t.float({ required: true }),
    max: t.float({ required: true }),
  }),
})

Use these inputs in custom query fields:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { builder } from "@lakeql/api/builder"
import { DateRangeInput, AmountRangeInput } from "../custom/date-range-input"

builder.queryField("ordersByRange", (t) =>
  t.field({
    type: ["Orders"],
    args: {
      dateRange: t.arg({ type: DateRangeInput, required: true }),
      amountRange: t.arg({ type: AmountRangeInput }),
    },
    resolve: async (_root, args, _context) => {
      // Build custom query with range filters
      return []
    },
  })
)

Computed Fields #

Add fields that derive values from existing data at the GraphQL layer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42import { builder } from "@lakeql/api/builder"

const OrdersType = builder.objectRef<{
  total: number
  tax_rate: number
  status: string
  created_at: Date
}>("Orders")

// Tax amount computed from total and tax_rate
builder.objectField(OrdersType, "taxAmount", (t) =>
  t.float({
    resolve: (parent) => parent.total * parent.tax_rate,
  })
)

// Human-readable status
builder.objectField(OrdersType, "statusLabel", (t) =>
  t.string({
    resolve: (parent) => {
      const labels: Record<string, string> = {
        pending: "Pending Review",
        shipped: "Shipped",
        delivered: "Delivered",
        cancelled: "Cancelled",
      }
      return labels[parent.status] ?? parent.status
    },
  })
)

// Age in days
builder.objectField(OrdersType, "ageInDays", (t) =>
  t.int({
    resolve: (parent) => {
      const now = Date.now()
      const created = new Date(parent.created_at).getTime()
      return Math.floor((now - created) / (1000 * 60 * 60 * 24))
    },
  })
)

Custom Enum Types #

Define enum types for fields that have a fixed set of values:

1
2
3
4
5
6
7
8
9
10
11
12
import { builder } from "@lakeql/api/builder"

export const OrderStatus = builder.enumType("OrderStatus", {
  values: [
    "pending",
    "processing",
    "shipped",
    "delivered",
    "cancelled",
  ] as const,
})

Tips for Extension Files #

  • Don't modify generated files — They'll be overwritten on the next pull
  • Use descriptive file names — e.g. orders-extensions.ts , orders-computed.ts
  • Place in schemas/custom/ — Keep custom code separate from generated directories
  • Test independently — Extensions can introduce runtime errors if the parent type changes after a pull

Previous page

Custom Resolvers

Next page

Deploying

src/schemas/custom/orders-extensions.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
src/schemas/custom/date-range-input.ts
src/schemas/custom/computed-fields.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
src/schemas/custom/enums.ts