> 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. Values can be negative, and `power()` also matches the power an Upgrade or Pilot **grants** -- so `power(-2)` finds upgrades that give -2 power.

**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
* `power(-2) type(upgrade)` -- upgrades that grant -2 power

### hp()

Filter by card HP. Same syntax and operators as `cost()`. Multiple expressions use AND logic. Values can be negative, and `hp()` also matches the HP an Upgrade or Pilot **grants** -- so `hp(<0)` finds upgrades that reduce HP.

**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
* `hp(<0) type(upgrade)` -- upgrades that reduce HP

### 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

**Bases carry the planet they're on.** Every base has a trait naming its location, so you can search by world rather than by card name -- `Echo Base` is the title, `Hoth` is the trait. Wrap names with a space in quotes.

* `trait(hoth)` -- everything from Hoth
* `trait(coruscant) type(base)` -- bases on Coruscant
* `trait("cloud city")` -- multi-word locations need quotes
* `trait(any(endor, kashyyyk))` -- bases from either forest world

Aside from Naboo, which also appears on characters and ships, these names turn up on bases only.

### 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

Wrap values in `not(...)` to exclude a type instead:

* `type(not(leader))` -- everything except Leaders
* `type(unit, not(token))` -- Units, but not token Units

### arena()

Filter by arena. Accepted values: `space`, `ground`. `not(...)` excludes an arena.

**Examples:**

* `arena(space)` -- Space arena cards
* `arena(ground)` -- Ground arena cards
* `arena(not(space))` -- everything that isn't a Space card

### 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       |
| ASH  | Ashes of the Empire      |
| HMW  | Homeworlds               |
| TS26 | 2026 Twin Suns           |

**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

Wrap the whole value in `not(...)` to **exclude** cards whose text matches. Because the value is one phrase, `not(...)` is only recognised when it wraps the entire value -- to both require and exclude a phrase, write two calls:

* `text(not(non-leader))` -- cards whose text does **not** mention "non-leader"
* `text(defeat) text(not(non-leader))` -- defeat effects that aren't limited to non-leaders

### title()

Match against the card's **name**. Bare text matches anywhere in the name; `starts(...)` matches only the beginning.

**Examples:**

* `title(luke)` -- cards with "luke" anywhere in the name
* `title(starts(the))` -- cards whose name begins with "the"

Plain [free text](#free-text-card-name-search) does the same thing as bare `title()`, so reach for this when you want the name search stated explicitly alongside other filters.

### rotation()

Filter by **rotation block** -- the groupings that determine Premier legality. Accepts `A`, `B`, or both.

**Examples:**

* `rotation(B)` -- cards from rotation block B only
* `rotation(A, B)` -- cards from either block

### legal()

Filter by format legality. Accepted values: `premier`, `eternal`.

**Examples:**

* `legal(premier)` -- cards legal in the Premier format

### prestige() / showcase()

Filter for cards that have a **prestige** (premium foil) or **showcase** (alternate art) variant available.

* `prestige()` -- cards with a prestige variant; `prestige(false)` for cards without one
* `showcase() set(jtl)` -- Jump to Lightspeed cards with showcase art

### suspended()

Filter by **suspended** (banned) status in the active format.

* `suspended()` -- cards currently suspended
* `suspended(false)` -- exclude suspended cards
* `suspended() legal(premier)` -- cards suspended in Premier

### 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
* **Mixing the two search styles** is caught: if you drop a prefix-style filter (like `health:>=3`) into a Forge query, it won't be applied — you'll get an error telling you the term won't work and how to write it in Forge syntax (here, `hp(>=3)`)
* 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.
