> For the complete documentation index, see [llms.txt](https://swu-forge.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://swu-forge.gitbook.io/docs/cards-and-search/searching-cards/search-syntax.md).

# Forge Syntax Reference

Forge Syntax lets you find cards using structured **function-call** filter expressions like `aspect(red) cost(>4)`. It is available to **all users** as an alternative to Plain Language Search (which requires a Foundry Apprentice subscription).

> **Two dialects.** SWU Forge also supports [Scryfall Syntax](/docs/cards-and-search/searching-cards/scryfall-syntax.md) — a prefix style (`c:r p>4`) familiar from Scryfall/SWUDB. The search box **auto-detects** which you're typing and remembers your choice; you can also set it from the search-mode dropdown on the Browse page or from [Settings](/docs/account/settings.md). This page documents the **Forge** dialect, which is a superset of the card-property filters.

## How It Works

Type filter expressions directly into the search bar. Filters use the format `filterName(value)`. Any text that is not part of a filter expression is treated as a **card name search**.

Multiple filters combine with **AND** logic -- a card must match every filter to appear in results.

## Recipes (quick start)

Copy any of these into the search bar to get a feel for the syntax. Each is explained in the [Filter Reference](#filter-reference) below.

| Query                                              | Finds                                         |
| -------------------------------------------------- | --------------------------------------------- |
| `power(>cost) type(unit)`                          | Units that hit above their cost               |
| `hp(>power) type(unit)`                            | Tanky units (more HP than power)              |
| `cost(>=2, <=5, !=3)`                              | Costs 2–5, but never exactly 3                |
| `aspect(only(blue, black))`                        | Cards playable in a Vigilance/Villainy deck   |
| `aspect(exactly(red))`                             | Mono-Aggression only                          |
| `aspect(more(red))`                                | Multi-aspect cards that include red           |
| `aspect(count(3))`                                 | Triple-aspect cards (`count(0)` = colourless) |
| `aspect(doubled(blue))`                            | Cards printing Vigilance twice                |
| `upgradePower(>=2)`                                | Upgrades/pilots granting +2 power or more     |
| `pilotingCost(<=1) trait(pilot)`                   | Cheap-to-deploy pilots                        |
| `number(196)`                                      | Whatever card is #196 in each loaded set      |
| `vanilla() type(unit)`                             | Plain units with no ability text              |
| `or(aspect(red), aspect(blue))`                    | Red **or** blue                               |
| `type(unit) or(aspect(green), aspect(villainy))`   | Units that are green **or** villainy          |
| `or(keyword(sentinel), keyword(ambush)) cost(<=3)` | Cheap Sentinel or Ambush cards                |
| `aspect(only(red)) trait(trooper) cost(<=2)`       | Cheap mono-red Troopers                       |

## Filter Reference

### aspect()

Filter by card aspects. Supports several sub-modes and bare values:

* `any(...)` -- card has **at least one** of the listed aspects (OR)
* `all(...)` -- card has **all** of the listed aspects, and may have others (AND / superset)
* `not(...)` -- card does **not** have any of the listed aspects (exclude)
* `exactly(...)` -- card has **exactly** these aspects, no more and no fewer
* `only(...)` -- card's aspects are a **subset** of the listed set (nothing off-colour); colourless cards still match -- "playable in"
* `more(...)` -- card has **all** the listed aspects **plus at least one other**
* `count(n)` -- card has a specific **number** of distinct aspects; accepts comparisons like `count(>=2)` or `count(0)` for colourless
* `doubled(...)` -- card prints the listed aspect **twice** (e.g. double Vigilance)
* Bare values (no sub-mode) are treated as `any`

**Aspect aliases:**

| Aspect     | Aliases                        |
| ---------- | ------------------------------ |
| Aggression | `aggression`, `red`            |
| Vigilance  | `vigilance`, `blue`            |
| Command    | `command`, `green`             |
| Cunning    | `cunning`, `yellow`            |
| Heroism    | `heroism`, `white`, `hero`     |
| Villainy   | `villainy`, `black`, `villain` |

**Examples:**

* `aspect(red)` -- cards with the Aggression aspect
* `aspect(any(red, blue))` -- cards with Aggression or Vigilance
* `aspect(all(green, hero))` -- cards with both Command and Heroism (may also have others)
* `aspect(exactly(red, black))` -- cards that are exactly Aggression + Villainy, nothing else
* `aspect(only(blue, black))` -- cards playable in a Vigilance/Villainy deck (no off-colour aspects)
* `aspect(more(red))` -- multi-aspect cards that include Aggression
* `aspect(count(2))` -- dual-aspect cards; `aspect(count(0))` -- colourless cards
* `aspect(doubled(blue))` -- cards printing Vigilance twice
* `aspect(not(villain), any(red, blue))` -- Aggression or Vigilance cards, excluding Villainy

### cost()

Filter by card cost. Accepts exact numbers and comparison expressions using the operators `=`, `!=`, `<`, `<=`, `>`, `>=`.

Multiple expressions separated by commas use **AND** logic -- a card must satisfy **all** conditions. This is how you define ranges.

**Examples:**

* `cost(3)` -- cards that cost exactly 3
* `cost(>4)` -- cards that cost more than 4
* `cost(<=2)` -- cards that cost 2 or less
* `cost(!=3)` -- cards that cost anything except 3
* `cost(>=3, <=6)` -- cards that cost between 3 and 6 (AND: must be >= 3 **and** <= 6)
* `cost(>=2, <=8, !=5)` -- cards costing 2-8 but not exactly 5

> **AND vs OR:** `cost(>=3, <=6)` means "3 through 6" because both conditions must be true. If you want cards that are either cheap **or** expensive (OR logic), use two separate cost filters or exact values -- e.g., `cost(1, 2, 7, 8)` for exact costs.

#### Comparing stats to each other

Instead of a number, the operand can be another stat field -- `cost`, `power`, or `hp` -- to compare two of a card's own stats. This works inside `cost()`, `power()`, and `hp()`.

* `power(>cost)` -- units whose power is greater than their cost
* `cost(<power)` -- same result, written from the cost side
* `hp(!=power)` -- cards whose HP differs from their power
* `power(>=cost, <hp)` -- power at least the cost **and** less than the HP

### power()

Filter by card power. Same syntax and operators as `cost()` (including `!=` and stat-to-stat comparisons). Multiple expressions use AND logic.

**Examples:**

* `power(5)` -- cards with exactly 5 power
* `power(>=4)` -- cards with 4 or more power
* `power(!=0)` -- cards with any nonzero power
* `power(>=3, <=5)` -- cards with power between 3 and 5

### hp()

Filter by card HP. Same syntax and operators as `cost()`. Multiple expressions use AND logic.

**Examples:**

* `hp(>6)` -- cards with more than 6 HP
* `hp(1)` -- cards with exactly 1 HP
* `hp(>=4, <=7)` -- cards with HP between 4 and 7

### upgradePower() / upgradeHp()

Filter by the **bonus power / HP an Upgrade or Pilot grants** to the unit it attaches to (the `upgradePower` / `upgradeHp` values on Upgrade cards and Pilot Units/Leaders). Only cards that actually have an upgrade bonus are matched.

Accepts exact numbers and the comparison operators `=`, `<`, `<=`, `>`, `>=`. Multiple expressions use AND logic to form ranges. (`!=` is not supported here.)

**Examples:**

* `upgradePower(>=2)` -- upgrades/pilots granting 2 or more power
* `upgradeHp(3)` -- upgrades/pilots granting exactly 3 HP
* `upgradePower(>1, <=4)` -- power bonus between 2 and 4
* `upgradePower(>=2) type(upgrade)` -- Upgrade cards (excluding pilots) with a +2 or better power bonus

### pilotingCost()

Filter by a **Pilot card's piloting cost** -- the resource cost to deploy it as an upgrade onto a Vehicle. Only cards that have a piloting cost match. Accepts exact numbers and the operators `=`, `<`, `<=`, `>`, `>=` (ranges via commas). `!=` is not supported here.

**Examples:**

* `pilotingCost(2)` -- pilots with a piloting cost of exactly 2
* `pilotingCost(<=1)` -- cheap-to-deploy pilots
* `pilotingCost(>=2, <=4)` -- piloting cost between 2 and 4
* `pilotingCost(0) trait(pilot)` -- free-to-deploy pilots

### number()

Filter by **card number** -- a card's base collector number, an alternate number (for cards reprinted across products), or any of its variant collector numbers (hyperspace, showcase, prestige, and the foil / serialized variants). Accepts one or more exact numbers (comma-separated); comparisons are not supported.

Because variant numbers are reused across sets, a number can match one card in **each loaded set** (and a single card may match on more than one of its printings). Pair with `set(...)` to pin down a single printing.

**Examples:**

* `number(196)` -- the card printed as #196 (in each loaded set)
* `number(1, 2, 3)` -- cards numbered 1, 2, or 3
* `number(269) set(SOR)` -- the SOR card whose hyperspace printing is #269
* `number(84) set(SOR)` -- SOR card #84

### trait()

Filter by card traits. Comma-separated, case-insensitive. Traits are matched by closest match (prefix and substring matching), so partial names may work.

Supports three sub-modes (same as `aspect()`), plus bare values:

* `any(...)` -- card has **at least one** of the listed traits (OR — bare default)
* `all(...)` -- card has **all** of the listed traits (AND)
* `not(...)` -- card does **not** have any of the listed traits (exclude)

The literal word `and` between values acts as a separator: `trait(rebel and trooper)` is equivalent to `trait(all(rebel, trooper))`.

**Examples:**

* `trait(rebel)` -- cards with the Rebel trait
* `trait(jedi, force)` -- cards with the Jedi OR Force trait (bare = OR)
* `trait(all(jedi, force))` -- cards with both Jedi AND Force
* `trait(rebel and trooper)` -- cards with both Rebel AND Trooper
* `trait(not(droid))` -- cards without the Droid trait
* `trait(any(rebel, imperial), not(droid))` -- Rebel or Imperial cards that aren't Droids

### keyword()

Filter by card keywords. Comma-separated, case-insensitive. Like traits, keywords support closest-match resolution and the same `any(...)`, `all(...)`, `not(...)` sub-modes.

The literal word `and` between values acts as a separator: `keyword(ambush and overwhelm)` is equivalent to `keyword(all(ambush, overwhelm))`.

**Examples:**

* `keyword(sentinel)` -- cards with the Sentinel keyword
* `keyword(ambush, overwhelm)` -- cards with Ambush OR Overwhelm
* `keyword(all(ambush, overwhelm))` -- cards with both Ambush AND Overwhelm
* `keyword(ambush and overwhelm)` -- same as the previous example
* `keyword(not(restore))` -- cards without Restore
* `keyword(all(sentinel), not(restore))` -- Sentinel cards without Restore

### type()

Filter by card type. Accepted values: `unit`, `event`, `upgrade`, `leader`, `base`, `token`.

The `token` value expands to all token types (Token Unit, Token Upgrade, Credit Token, Force Token).

**Examples:**

* `type(unit)` -- all Unit cards
* `type(leader, base)` -- Leader and Base cards
* `type(token)` -- all token cards

### arena()

Filter by arena. Accepted values: `space`, `ground`.

**Examples:**

* `arena(space)` -- Space arena cards
* `arena(ground)` -- Ground arena cards

### set()

Filter by card set. Accepts set codes or full set names (case-insensitive).

| Code | Set Name                 |
| ---- | ------------------------ |
| SOR  | Spark of Rebellion       |
| SHD  | Shadows of the Galaxy    |
| TWI  | Twilight of the Republic |
| JTL  | Jump to Lightspeed       |
| LOF  | Legends of the Force     |
| SEC  | Secrets of Power         |
| LAW  | A Lawless Time           |
| IBH  | Intro Battle: Hoth       |

**Examples:**

* `set(SOR)` -- cards from Spark of Rebellion
* `set(jtl, shd)` -- cards from Jump to Lightspeed or Shadows of the Galaxy
* `set(shadows of the galaxy)` -- same as `set(SHD)`

### rarity()

Filter by card rarity. Accepted values: `common`, `uncommon`, `rare`, `legendary`, `special` (or `starter`).

**Examples:**

* `rarity(legendary)` -- Legendary cards
* `rarity(common, uncommon)` -- Common or Uncommon cards
* `rarity(special)` -- Special/Starter cards

### ability()

Filter by card ability type. Supports quoted strings for multi-word values, and the same `any(...)`, `all(...)`, `not(...)` sub-modes as trait/keyword/aspect.

Accepted values:

* `when played`
* `on attack`
* `when attack ends`
* `completes attack`
* `epic action`
* `action`
* `when defeated`
* `while`
* `when deployed`

**Examples:**

* `ability(action)` -- cards with an Action ability
* `ability("when played")` -- cards with a When Played ability
* `ability("on attack", "when defeated")` -- cards with On Attack OR When Defeated
* `ability(all("when played", "on attack"))` -- cards with both When Played AND On Attack
* `ability(not("when defeated"))` -- cards without a When Defeated ability

### text()

Match a phrase against the card's **rules text** (the card's ability text, Epic Action text, and deploy box text). Card **name** and **subtitle** are not searched — use free text for name searches.

The entire value is treated as a single phrase. Wrapping quotes are optional and are stripped, so `text(friendly units)` and `text("friendly units")` are equivalent. Matching is case-insensitive and per-sentence (a phrase must appear within a single sentence of the card text).

**Examples:**

* `text(friendly units)` -- cards whose text mentions "friendly units"
* `text("draw a card")` -- cards whose text mentions "draw a card"
* `text(indirect damage) aspect(red)` -- Aggression cards that deal with indirect damage

### vanilla()

Filter for **vanilla** cards -- cards with **no ability text at all** (no rules text, Epic Action, or deploy box). Since keyword lines are part of a card's text, keyworded cards (e.g. a Sentinel unit) are **not** vanilla.

* Bare `vanilla()` (or `vanilla(true)`) matches vanilla cards.
* `vanilla(false)` (or `vanilla(no)`) matches cards that **do** have ability text.

**Examples:**

* `vanilla()` -- all cards with no ability text
* `vanilla() type(unit) aspect(red)` -- plain Aggression units with no text
* `vanilla(false) power(>=6)` -- big-power cards that have some ability text

## Free Text (Card Name Search)

Any text outside of filter expressions is used as a **card name search**. This lets you combine name searching with filters.

**Examples:**

* `luke type(unit)` -- Unit cards with "luke" in the name
* `darth cost(<=4) aspect(black)` -- Villainy cards costing 4 or less with "darth" in the name

## Combining Filters

You can use multiple filters in a single query. Different filter types combine with AND logic -- a card must satisfy all filters to appear.

```
aspect(not(villain), any(red)) cost(>=3, <=5) type(unit) arena(ground) trait(rebel)
```

This finds Ground Unit cards that cost 3-5, have the Aggression aspect, do not have Villainy, and have the Rebel trait.

## OR / Alternatives — or()

Everything above combines with **AND**. To match cards that satisfy **any** of several alternatives, wrap them in `or(...)`. Each comma-separated argument is its own **sub-query** -- a full mini-search that can contain its own filters and free text (which combine with AND inside that branch). A card matches the `or()` if it satisfies **at least one** branch.

**Examples:**

* `or(aspect(red), aspect(blue))` -- cards that are Aggression **or** Vigilance
* `or(hp(>3) cost(>3), luke)` -- cards with both HP > 3 **and** cost > 3, **or** any card named "luke"
* `or(keyword(sentinel), keyword(ambush))` -- cards with Sentinel or Ambush (equivalent to `keyword(sentinel, ambush)`)

**Combining `or()` with outside filters.** Filters written outside the `or()` combine with it using AND, so you can require a group of alternatives *and* another condition:

* `type(unit) or(aspect(green), aspect(villainy))` -- Units that are Command **or** Villainy
* `trait(rebel) or(cost(<=2), power(>=5))` -- Rebels that are either cheap (cost <= 2) **or** hard-hitting (power >= 5)

Notes:

* Commas **inside** a branch's own filters aren't branch separators -- `or(aspect(red, blue), type(unit))` has two branches, the first being `aspect(red, blue)`.
* `or()` does not nest, matching how the rest of the syntax works; keep alternatives in a single flat `or(...)`.

## Validation

The search input validates your query as you type:

* **Unknown values** produce an error with a suggestion when a close match exists (e.g., `trait(reb)` suggests "Rebel")
* **Unknown filter names** are flagged (e.g., `damage(3)` shows "Unknown filter: damage")
* **Unclosed parentheses** are detected and reported
* When errors are present, the search input shows a **red border** and the error message appears below

Click the **?** button next to the search bar for a quick reference of all available filters and their syntax.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://swu-forge.gitbook.io/docs/cards-and-search/searching-cards/search-syntax.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
