Light
Dark
System

EdgeQL

EdgeQL is the query language of EdgeDB. It’s intended as a spiritual successor to SQL that solves some of its biggest design limitations. This page is indended as a rapid-fire overview so you can hit the ground running with EdgeDB. Refer to the linked pages for more in-depth documentation.

Want to follow along with the queries below? Open the Interactive Tutorial in a separate tab. Copy and paste the queries below and execute them directly from the browser.

The examples below also demonstrate how to express the query with the TypeScript query builder, which lets you express arbitrary EdgeQL queries in a code-first, typesafe way.

EdgeDB has a rich primitive typesystem consisting of the following data types.

Strings

str

Booleans

bool

Numbers

int32 int64 float32 float64 bigint decimal

UUID

uuid

JSON

json

Dates and times

datetime cal::local_datetime cal::local_date cal::local_time

Durations

duration cal::relative_duration cal::date_duration

Binary data

bytes

Basic literals can be declared using familiar syntax.

EdgeQL
TypeScript
Copy
db> 
select "i ❤️ edgedb"; # str
{'i ❤️ edgedb'}
Copy
db> 
select false; # bool
{false}
Copy
db> 
select 42; # int64
{42}
Copy
db> 
select 3.14; # float64
{3.14}
Copy
db> 
select 12345678n; # bigint
{12345678n}
Copy
db> 
select 15.0e+100n;  # decimal
{15.0e+100n}
Copy
db> 
select b'bina\\x01ry'; # bytes
{b'bina\\x01ry'}
Copy
e.str("i ❤️ edgedb")
// string
e.bool(false)
// boolean
e.int64(42)
// number
e.float64(3.14)
// number
e.bigint(BigInt(12345678))
// bigint
e.decimal("1234.4567")
// n/a (not supported by JS clients)
e.bytes(Buffer.from("bina\\x01ry"))
// Buffer

Other type literals are declared by casting an appropriately structured string.

EdgeQL
TypeScript
Copy
db> 
select <uuid>'a5ea6360-75bd-4c20-b69c-8f317b0d2857';
{a5ea6360-75bd-4c20-b69c-8f317b0d2857}
Copy
db> 
select <datetime>'1999-03-31T15:17:00Z';
{<datetime>'1999-03-31T15:17:00Z'}
Copy
db> 
select <duration>'5 hours 4 minutes 3 seconds';
{<duration>'5:04:03'}
Copy
db> 
select <cal::relative_duration>'2 years 18 days';
{<cal::relative_duration>'P2Y18D'}
Copy
e.uuid("a5ea6360-75bd-4c20-b69c-8f317b0d2857")
// string
e.datetime("1999-03-31T15:17:00Z")
// Date
e.duration("5 hours 4 minutes 3 seconds")
// edgedb.Duration (custom class)
e.cal.relative_duration("2 years 18 days")
// edgedb.RelativeDuration (custom class)

Primitive data can be composed into arrays and tuples, which can themselves be nested.

EdgeQL
TypeScript
Copy
db> 
select ['hello', 'world'];
{['hello', 'world']}
Copy
db> 
select ('Apple', 7, true);
{('Apple', 7, true)} # unnamed tuple
Copy
db> 
select (fruit := 'Apple', quantity := 3.14, fresh := true);
{(fruit := 'Apple', quantity := 3.14, fresh := true)} # unnamed tuple
Copy
db> 
select <json>["this", "is", "an", "array"];
{"[\"this\", \"is\", \"an\", \"array\"]"}
Copy
e.array(["hello", "world"]);
// string[]
e.tuple(["Apple", 7, true]);
// [string, number, boolean]
e.tuple({fruit: "Apple", quantity: 3.14, fresh: true});
// {fruit: string; quantity: number; fresh: boolean}
e.json(["this", "is", "an", "array"]);
// unknown

EdgeDB also supports a special json type for representing unstructured data. Primitive data structures can be converted to json using a type cast (<json>). Alternatively, a properly JSON-encoded string can be converted to json with the built-in to_json function. Indexing a json value returns another json value.

EdgeQL
TypeScript
Copy
edgedb> 
select <json>5;
{"5"}
Copy
edgedb> 
select <json>[1,2,3];
{"[1, 2, 3]"}
Copy
edgedb> 
select to_json('[{ "name": "Peter Parker" }]');
{"[{\"name\": \"Peter Parker\"}]"}
Copy
edgedb> 
select to_json('[{ "name": "Peter Parker" }]')[0]['name'];
{"\"Peter Parker\""}
Copy
/*
  The result of an query returning `json` is represented
  with `unknown` in TypeScript.
*/
e.json(5);  // => unknown
e.json([1, 2, 3]);  // => unknown
e.to_json('[{ "name": "Peter Parker" }]');  // => unknown
e.to_json('[{ "name": "Peter Parker" }]')[0]["name"];  // => unknown

Refer to Docs > EdgeQL > Literals for complete docs.

EdgeDB provides a rich standard library of functions to operate and manipulate various data types.

EdgeQL
TypeScript
Copy
db> 
select str_upper('oh hi mark');
{'OH HI MARK'}
Copy
db> 
select len('oh hi mark');
{10}
Copy
db> 
select uuid_generate_v1mc();
{c68e3836-0d59-11ed-9379-fb98e50038bb}
Copy
db> 
select contains(['a', 'b', 'c'], 'd');
{false}
Copy
e.str_upper("oh hi mark");
// string
e.len("oh hi mark");
// number
e.uuid_generate_v1mc();
// string
e.contains(["a", "b", "c"], "d");
// boolean

Similarly, it provides a comprehensive set of built-in operators.

EdgeQL
TypeScript
Copy
db> 
select not true;
{false}
Copy
db> 
select exists 'hi';
{true}
Copy
db> 
select 2 + 2;
{4}
Copy
db> 
select 'Hello' ++ ' world!';
{'Hello world!'}
Copy
db> 
select '😄' if true else '😢';
{'😄'}
Copy
db> 
select <duration>'5 minutes' + <duration>'2 hours';
{<duration>'2:05:00'}
Copy
e.op("not", e.bool(true));
// booolean
e.op("exists", e.set("hi"));
// boolean
e.op("exists", e.cast(e.str, e.set()));
// boolean
e.op(e.int64(2), "+", e.int64(2));
// number
e.op(e.str("Hello "), "++", e.str("World!"));
// string
e.op(e.str("😄"), "if", e.bool(true), "else", e.str("😢"));
// string
e.op(e.duration("5 minutes"), "+", e.duration("2 hours"))

See Docs > Standard Library for reference documentation on all built-in types, including the functions and operators that apply to them.

Objects are created using insert. The insert statement relies on developer-friendly syntax like curly braces and the := operator.

EdgeQL
TypeScript
Copy
insert Movie {
  title := 'Doctor Strange 2',
  release_year := 2022
};
Copy
const query = e.insert(e.Movie, {
  title: 'Doctor Strange 2',
  release_year: 2022
});

const result = await query.run(client);
// {id: string}
// by default INSERT only returns
// the id of the new object

See Docs > EdgeQL > Insert.

One of EdgeQL’s greatest features is that it’s easy to compose. Nested inserts are easily achieved with subqueries.

EdgeQL
TypeScript
Copy
insert Movie {
  title := 'Doctor Strange 2',
  release_year := 2022,
  director := (insert Person {
    name := "Sam Raimi"
  })
};
Copy
const query = e.insert(e.Movie, {
  title: 'Doctor Strange 2',
  release_year: 2022
});

const result = await query.run(client);
// {id: string}
// by default INSERT only returns
// the id of the new object

Use a shape to define which properties to select from the given object type.

EdgeQL
TypeScript
Copy
select Movie {
  id,
  title
};
Copy
const query = e.select(e.Movie, () => ({
  id: true,
  title: true
}));
const result = await query.run(client);
// {id: string; title: string; }[]

// To select all properties of an object, use the
// spread operator with the special "*"" property:
const query = e.select(e.Movie, () => ({
  ...e.Movie['*']
}));

Fetch linked objects with a nested shape.

EdgeQL
TypeScript
Copy
select Movie {
  id,
  title,
  actors: {
    name
  }
};
Copy
const query = e.select(e.Movie, () => ({
  id: true,
  title: true,
  actors: {
    name: true,
  }
}));

const result = await query.run(client);
// {id: string; title: string, actors: {name: string}[]}[]

See Docs > EdgeQL > Select > Shapes.

The select statement can be augmented with filter, order by, offset, and limit clauses (in that order).

EdgeQL
TypeScript
Copy
select Movie {
  id,
  title
}
filter .release_year > 2017
order by .title
offset 10
limit 10;
Copy
const query = e.select(e.Movie, (movie) => ({
  id: true,
  title: true,
  filter: e.op(movie.release_year, ">", 1999),
  order_by: movie.title,
  offset: 10,
  limit: 10,
}));

const result = await query.run(client);
// {id: string; title: number}[]

Note that you reference properties of the object being select``ed by prepending the property name with a period: ``.release_year. This is known as leading dot notation.

Every new set of curly braces introduces a new scope. You can add filter, limit, and offset clauses to nested shapes.

EdgeQL
TypeScript
Copy
select Movie {
  title,
  actors: {
    name
  } filter .name ilike 'chris%'
}
filter .title ilike '%avengers%';
Copy
e.select(e.Movie, movie => ({
  title: true,
  characters: c => ({
    name: true,
    filter: e.op(c.name, "ilike", "chris%"),
  }),
  filter: e.op(movie.title, "ilike", "%avengers%"),
}));
// => { characters: { name: string; }[]; title: string; }[]

const result = await query.run(client);
// {id: string; title: number}[]

See Filtering, Ordering, and Pagination.

We’ve seen how to insert and select. How do we do both in one query? Answer: query composition. EdgeQL’s syntax is designed to be composable, like any good programming language.

EdgeQL
TypeScript
Copy
select (
  insert Movie { title := 'The Marvels' }
) {
  id,
  title
};
Copy
const newMovie = e.insert({
  title := "The Marvels"
});
const query = e.select(newMovie, () => ({
  id: true,
  title: true
}));

const result = await query.run(client);
// {id: string; title: string}

We can clean up this query by pulling out the insert statement into a with block. A with block is useful for composing complex multi-step queries, like a script.

EdgeQL
TypeScript
Copy
with new_movie := (insert Movie { title := 'The Marvels' })
select new_movie {
  id,
  title
};
Copy
/*
  Same as above.

  In the query builder, explicit ``with`` blocks aren't necessary!
  Just assign your EdgeQL subqueries to variables and compose them as you
  like. The query builder automatically convert your top-level query to an
  EdgeQL expression with proper ``with`` blocks.
*/

Selection shapes can contain computed properties.

EdgeQL
TypeScript
Copy
select Movie {
  title,
  title_upper := str_upper(.title),
  cast_size := count(.actors)
};
Copy
e.select(e.Movie, movie => ({
  title: true,
  title_upper: e.str_upper(movie.title),
  cast_size: e.count(movie.actors)
}))
// {title: string; title_upper: string; cast_size: number}[]

A common use for computed properties is to query a link in reverse; this is known as a backlink and it has special syntax.

EdgeQL
TypeScript
Copy
select Person {
  name,
  acted_in := .<actors[is Content] {
    title
  }
};
Copy
e.select(e.Person, person => ({
  name: true,
  acted_in: e.select(person["<actors[is Content]"], () => ({
    title: true,
  })),
}));
// {name: string; acted_in: {title: string}[];}[]

See Docs > EdgeQL > Select > Computed and Docs > EdgeQL > Select > Backlinks.

The update statement accepts a filter clause upfront, followed by a set shape indicating how the matching objects should be updated.

EdgeQL
TypeScript
Copy
update Movie
filter .title = "Doctor Strange 2"
set {
  title := "Doctor Strange in the Multiverse of Madness"
};
Copy
const query = e.update(e.Movie, (movie) => ({
  filter: e.op(movie.title, '=', 'Doctor Strange 2'),
  set: {
    title: 'Doctor Strange in the Multiverse of Madness',
  },
}));

const result = await query.run(client);
// {id: string}

When updating links, the set of linked objects can be added to with +=, subtracted from with -=, or overridden with :=.

EdgeQL
TypeScript
Copy
update Movie
filter .title = "Doctor Strange 2"
set {
  actors += (select Person filter .name = "Rachel McAdams")
};
Copy
e.update(e.Movie, (movie) => ({
  filter: e.op(movie.title, '=', 'Doctor Strange 2'),
  set: {
    actors: {
      "+=": e.select(e.Person, person => ({
        filter: e.op(person.name, "=", "Rachel McAdams")
      }))
    }
  },
}));

See Docs > EdgeQL > Update.

The delete statement can contain filter, order by, offset, and limit clauses.

EdgeQL
TypeScript
Copy
delete Movie
filter .ilike "the avengers%"
limit 3;
Copy
const query = e.delete(e.Movie, (movie) => ({
  filter: e.op(movie.title, 'ilike', "the avengers%"),
}));

const result = await query.run(client);
// {id: string}[]

See Docs > EdgeQL > Delete.

You can reference query parameters in your queries with $<name> notation. Since EdgeQL is a strongly typed language, all query parameters must be prepending with a type cast to indicate the expected type.

Scalars like str, int64, and json are supported. Tuples, arrays, and object types are not.

EdgeQL
TypeScript
Copy
insert Movie {
  title := <str>$title,
  release_year := <int64>$release_year
};
Copy
const query = e.params({ title: e.str, release_year: e.int64 }, ($) => {
  return e.insert(e.Movie, {
    title: $.title,
    release_year: $.release_year,
  }))
};

const result = await query.run(client, {
  title: 'Thor: Love and Thunder',
  release_year: 2022,
});
// {id: string}

All client libraries provide a dedicated API for specifying parameters when executing a query.

Javascript
Python
Go
Copy
import {createClient} from "edgedb";

const client = createClient();
const result = await client.query(`select <str>$param`, {
  param: "Play it, Sam."
});
// => "Play it, Sam."
Copy
import edgedb

client = edgedb.create_async_client()

async def main():

    result = await client.query("select <str>$param", param="Play it, Sam")
    # => "Play it, Sam"
Copy
package main

import (
    "context"
    "log"

    "github.com/edgedb/edgedb-go"
)

func main() {
    ctx := context.Background()
    client, err := edgedb.CreateClient(ctx, edgedb.Options{})
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    var (
        param     string = "Play it, Sam."
        result  string
    )

    query := "select <str>$0"
    err = client.Query(ctx, query, &result, param)
    // ...
}

See Docs > EdgeQL > Parameters.

Unlike SQL, EdgeQL is composable; queries can be naturally nested. This is useful, for instance, when performing nested mutations.

EdgeQL
TypeScript
Copy
with
  dr_strange := (select Movie filter .title = "Doctor Strange"),
  benedicts := (select Person filter .name in {
    'Benedict Cumberbatch',
    'Benedict Wong'
  })
update dr_strange
set {
  actors += benedicts
};
Copy
// select Doctor Strange
const drStrange = e.select(e.Movie, movie => ({
  filter: e.op(movie.title, '=', "Doctor Strange")
}));

// select actors
const actors = e.select(e.Person, person => ({
  filter: e.op(person.name, 'in', e.set(
    'Benedict Cumberbatch',
    'Benedict Wong'
  ))
}));

// add actors to cast of drStrange
const query = e.update(drStrange, ()=>({
  actors: { "+=": actors }
}));

We can also use subqueries to fetch properties of an object we just inserted.

EdgeQL
TypeScript
Copy
 with new_movie := (insert Movie {
   title := "Avengers: The Kang Dynasty",
   release_year := 2025
 })
 select new_movie {
  title, release_year
};
Copy
// "with" blocks are added automatically
// in the generated query!

const newMovie = e.insert(e.Movie, {
  title: "Avengers: The Kang Dynasty",
  release_year: 2025
});

const query = e.select(newMovie, ()=>({
  title: true,
  release_year: true,
}));

const result = await query.run(client);
// {title: string; release_year: number;}

See Docs > EdgeQL > Select > Subqueries.

Consider the following schema.

Copy
abstract type Content {
  required property title -> str;
}

type Movie extending Content {
  property release_year -> int64;
}

type TVShow extending Content {
  property num_seasons -> int64;
}

We can select the abstract type Content to simultaneously fetch all objects that extend it, and use the [is <type>] syntax to select properties from known subtypes.

EdgeQL
TypeScript
Copy
select Content {
  title,
  [is TVShow].num_seasons,
  [is Movie].release_year
};
Copy
const query = e.select(e.Content, (content) => ({
  title: true,
  ...e.is(e.Movie, {release_year: true}),
  ...e.is(e.TVShow, {num_seasons: true}),
}));
/* {
  title: string;
  release_year: number | null;
  num_seasons: number | null;
}[] */

See Docs > EdgeQL > Select > Polymorphic queries.

Unlike SQL, EdgeQL provides a top-level group statement to compute groupings of objects.

EdgeQL
TypeScript
Copy
group Movie { title, actors: { name }}
by .release_year;
Copy
e.group(e.Movie, (movie) => {
  const release_year = movie.release_year;
  return {
    title: true,
    by: {release_year},
  };
});
/* {
  grouping: string[];
  key: { release_year: number | null };
  elements: { title: string; }[];
}[] */

See Docs > EdgeQL > Group.

Light
Dark
System