# OQL

> **Note:**
> The OpenAlex Query Language is in **alpha**. It may change without warning — build against it at your own risk, and [tell us what you think](mailto:support@openalex.org).

**OQL is the OpenAlex Query Language — a readable way to ask OpenAlex anything.**
Where the classic API uses URL filter strings, OQL lets you write the query out in
something close to plain English:

```
works where title has ("climate change") and year >= (2020) and open access is (true)
```

You can read that aloud and roughly know what it does — that's the whole point. OQL can
also express queries the old URL syntax never could (deep nesting, OR across different
fields, mixed exact-and-stemmed search), while compiling down to the same engine the GUI
and classic URLs already use.

## The three things to know

1. **A query is `<entity> where <filters>`.** The entity is what you get back —
   `works`, `authors`, `institutions`, `sources`, … With no filters, the bare entity
   is a valid query: `works`.
2. **Filter fields with `is` / `>=`; search text with `has` — the value always sits in
   parentheses.** `year is (2020)`, `citation count >= (100)`, `title has (cancer)`.
3. **Combine with `and` / `or`; group filters with parentheses.**
   `title has (cancer) and year >= (2020)`.

That's enough to write most queries. Everything below is detail.

## Where to run it

- **On the search page:** switch to the new **OQL tab** at the top of the page and type
  your query — valid queries run as you type, and you can flip between the
  point-and-click builder and the OQL text. This is the easiest way to experiment.
- **On the API:** `https://api.openalex.org/?oql=<your query>`. Note the query carries its
  own entity (`works where …`), so it goes to the API **root**, not `/works`.

---

## Filtering

A filter is `<field> <operator> (<value>)` — the value always sits in parentheses.
Most filters use `is`, or a numeric comparison:

```
works where type is (review)
works where year is (2020)
works where citation count >= (100)
works where FWCI >= (2.0)
```

**Several values for one field** — put them in the same parentheses, joined with `or`:

```
works where type is (article or review)
```

**Ranges** are just two endpoint filters joined with `and`:

```
works where year >= (2019) and year <= (2023)
```

**Entities** (institutions, authors, funders, sources, topics, …) are referenced by their
OpenAlex ID. You can write a human label after it in square brackets to keep the query
readable — it's ignored on input and auto-filled when the query is shown back to you:

```
works where institution is (I136199984 [Harvard University])
works where institution is (I136199984) or funder is (F4320332161 [National Institutes of Health])
```

**Closed vocabularies** (countries, languages, SDGs, …) take their code or id, not their
name: `language is (en)`, `country is (US)`, `SDG is (3)`.

---

## Searching text — the heart of OQL

Search a text field with **`has`**. The fields are `title`, `abstract`, `title/abstract`
(both at once), `full text`, `raw affiliation`, and `byline`.

**Bare words are stemmed.** `title has (cancer)` also matches *cancers*, *cancerous* — the
everyday default, good recall:

```
works where title has (cancer)
works where title/abstract has (machine learning)
```

(A phrase of 2+ words is matched as a stemmed phrase, ranked higher when the words are
adjacent.)

**Quotes mean exact** — stemming off:

```
works where title has ("climate change")     ← the exact phrase
works where title has ("cat")                 ← excludes "cats"
```

**`stemmed "…"`** is the bridge: an exact-adjacent phrase that *keeps* stemming, when you
want phrase precision without losing recall:

```
works where title has (stemmed "genome editing")
```

**Wildcards** (`*` any characters, `?` exactly one character, as in `"wom?n"` or `"wo??n"`;
neither may start a word) must be **quoted** — they run on the exact, unstemmed text:

```
works where title has ("psoriat*")
```

**Proximity** — terms within N words of each other, in any order — is written *before* the
terms:

```
works where title has (within 3 ("smart", "phone"))
```

**Semantic search** finds works by meaning, not keywords:

```
works where title/abstract is similar to ("ocean acidification effects on coral reefs")
```

---

## Combining and nesting

Join filters with `and` / `or`, and group with parentheses. `and` binds tighter than
`or`, so `a and b or c` means `(a and b) or c` — but the canonical form always adds the
parentheses back so nothing is left to guess:

```
works where title/abstract has ((vape or vaping) and (health or harm))

works where (year < (2000) and title/abstract has ("global warming"))
  or (title/abstract has ("climate change") and year > (2020))
```

This nesting — and OR across *different* fields
(`institution is … or funder is …`) — is what the classic URL syntax can't express.

---

## Excluding — `not`

Put `not` inside the parentheses, directly before the value to exclude:

```
works where country is (not FR)
works where title has (covid) and abstract has (not pediatric)
works where title has (not mouse and cancer)
```

There's just one way to negate — `not` before a value. To exclude a whole group, write
`(not (a or b))`.

## Yes / no flags

Boolean flags read as `is (true)` / `is (false)`:

```
works where open access is (true)
works where has DOI is (true)
works where retracted is (true)
```

## Grouping and sampling

`group by` aggregates results into buckets; `sample` returns a random subset:

```
works where year >= (2020) group by topic
works where year >= (2020) group by topic, year
works where year is (2020) sample 500
```

Sorting results and choosing which columns to show are **not** part of OQL — they're
controls in the results view (`?sort=` / `?select=` on the API). OQL says *which* works
you want, not how to display them.

---

## OQL never guesses

A query that can't do what it appears to do is always a **clear error with a fix-it** —
never a silent wrong answer. If you mistype a field, mix up syntax, or use an old keyword,
OQL tells you exactly what to change:

```
title contains (cancer)     →  "contains" was renamed; use: title has (cancer)
title has (bar*)            →  wildcards need quotes: title has ("bar*")
pub_year is (2020)          →  unknown field "pub_year"; check the field name (it's "year")
type is (article review)    →  two values need a connective: type is (article or review)
```

---

## Going deeper

All of these live under **`/query`**:

- **Cheat sheet** — the one-page summary of everything above.
- **Cases** — a browsable library of worked examples, each with its OQL and the underlying
  query object.
- **Spec** — the formal, normative specification (every rule and edge case).
- **Grammar** — the formal grammar and a railroad diagram, for the syntax-curious.
- **OQO schema** — the canonical query object OQL compiles to, for building tools on top.

OQL is in active development and we'd love your feedback — tell us what's confusing, what's
missing, and what you wish you could express.
