# Filters & Branding

A filter (or *check*) is a constraint attached to a schema that a value must
satisfy *after* it has the right type. Effect ships a large set of built-in
checks — length, range, pattern, format — and lets you write your own. Attach
them with `.check(...)`.

```ts
import { Schema } from "effect"

// A registration form. Each field carries the constraints it must satisfy.
const Registration = Schema.Struct({
  // String must be non-empty and at most 50 characters.
  username: Schema.String.check(
    Schema.isMinLength(1),
    Schema.isMaxLength(50)
  ),
  // Number must be an integer in a valid age range.
  age: Schema.Number.check(
    Schema.isInt(),
    Schema.isBetween({ minimum: 13, maximum: 120 })
  ),
  // String must match an email-ish pattern.
  email: Schema.String.check(Schema.isPattern(/^[^@\s]+@[^@\s]+$/))
})

const user = Schema.decodeUnknownSync(Registration)({
  username: "alice",
  age: 30,
  email: "alice@example.com"
})

console.log(user.username) // => "alice"
```

## Attaching checks

There are two equivalent ways to attach checks: the `.check(...)` method on a
schema, or the standalone `Schema.check(...)` combinator for use in a pipe. Both
accept one or more checks and run them in order.

```ts
import { Schema } from "effect"

// Method form
const A = Schema.Number.check(
  Schema.isGreaterThanOrEqualTo(0),
  Schema.isLessThanOrEqualTo(120)
)

// Pipe form — identical result
const B = Schema.Number.pipe(
  Schema.check(
    Schema.isGreaterThanOrEqualTo(0),
    Schema.isLessThanOrEqualTo(120)
  )
)
```

By default, when multiple checks fail only the first is reported. Pass
`{ errors: "all" }` to the [decoding runner](https://effect.plants.sh/schema/basic-usage/) to collect
every failure. A failed check produces a `SchemaIssue.Filter` wrapping the
input, the failed filter, and the inner issue — see
[error formatting](https://effect.plants.sh/schema/error-formatting/) for how to read and reshape these.
**Note:** Checks constrain values but do **not** change the TypeScript type — a
`Schema.Number.check(Schema.isInt())` is still typed `number`. To narrow the
type as well, use [`Schema.refine`](#narrowing-the-type) or
[`Schema.brand`](#branding) (below).

Every check accepts a trailing `annotations` argument (a `message`, `title`,
`description`, `identifier`, etc.) used when the check fails and when deriving
JSON Schema / Arbitrary metadata.

```ts
import { Schema } from "effect"

const Username = Schema.String.check(
  Schema.isMinLength(3, { message: "username must be at least 3 characters" })
)
```

## Built-in checks

All built-in checks live on the `Schema` namespace. They are grouped below by
the value they constrain. Each gets a one-line description and a tiny example.

### String checks

These operate on `string` values.

#### isMinLength

At least `minLength` characters. `isNonEmpty()` is the `isMinLength(1)` shortcut.

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.String.check(Schema.isMinLength(3)))("abc") // => "abc"
```

#### isMaxLength

At most `maxLength` characters.

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.String.check(Schema.isMaxLength(5)))("abc") // => "abc"
```

#### isLengthBetween

Length within `[minimum, maximum]` (inclusive). `Schema.Char` is
`isLengthBetween(1, 1)`.

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.String.check(Schema.isLengthBetween(2, 4)))("abc") // => "abc"
```

#### isNonEmpty

At least one character. Equivalent to `isMinLength(1)`. `Schema.NonEmptyString`
bundles this.

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.String.check(Schema.isNonEmpty()))("x") // => "x"
```

#### isPattern

Matches a `RegExp`. The foundation for `isUUID`, `isULID`, `isBase64`, etc.

```ts
import { Schema } from "effect"

const Slug = Schema.String.check(Schema.isPattern(/^[a-z0-9-]+$/))
Schema.decodeUnknownSync(Slug)("my-slug") // => "my-slug"
```

#### isStartsWith

Begins with the given prefix.

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.String.check(Schema.isStartsWith("https://")))("https://x") // => "https://x"
```

#### isEndsWith

Ends with the given suffix.

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.String.check(Schema.isEndsWith(".json")))("a.json") // => "a.json"
```

#### isIncludes

Contains the given substring anywhere.

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.String.check(Schema.isIncludes("@")))("a@b") // => "a@b"
```

#### isTrimmed

No leading or trailing whitespace. `Schema.Trimmed` bundles this.

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.String.check(Schema.isTrimmed()))("hi") // => "hi"
```

#### isLowercased

All characters are lowercase.

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.String.check(Schema.isLowercased()))("abc") // => "abc"
```

#### isUppercased

All characters are uppercase.

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.String.check(Schema.isUppercased()))("ABC") // => "ABC"
```

#### isCapitalized

First character is uppercase.

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.String.check(Schema.isCapitalized()))("Hello") // => "Hello"
```

#### isUncapitalized

First character is lowercase.

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.String.check(Schema.isUncapitalized()))("hello") // => "hello"
```

#### isUUID

An RFC 4122 UUID. Pass a version (`1`–`8`) to require a specific one, or omit it
to accept any version.

```ts
import { Schema } from "effect"

const Id = Schema.String.check(Schema.isUUID(4))
Schema.decodeUnknownSync(Id)("f47ac10b-58cc-4372-a567-0e02b2c3d479") // => "f47ac10b-..."
```

#### isULID

A Universally Unique Lexicographically Sortable Identifier (26 Crockford base-32
characters).

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.String.check(Schema.isULID()))("01ARZ3NDEKTSV4RRFFQ69G5FAV")
// => "01ARZ3NDEKTSV4RRFFQ69G5FAV"
```

#### isBase64

A standard base64-encoded string.

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.String.check(Schema.isBase64()))("aGVsbG8=") // => "aGVsbG8="
```

#### isBase64Url

A URL-safe base64 string (`-` and `_` instead of `+` and `/`).

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.String.check(Schema.isBase64Url()))("aGVsbG8") // => "aGVsbG8"
```

### Number checks

These operate on `number` values. The comparison checks accept the boundary as
their first argument; `isBetween` takes an options object.

#### isGreaterThan

Strictly greater than (`> exclusiveMinimum`).

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.Number.check(Schema.isGreaterThan(0)))(1) // => 1
```

#### isGreaterThanOrEqualTo

Greater than or equal (`>= minimum`).

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.Number.check(Schema.isGreaterThanOrEqualTo(0)))(0) // => 0
```

#### isLessThan

Strictly less than (`< exclusiveMaximum`).

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.Number.check(Schema.isLessThan(10)))(9) // => 9
```

#### isLessThanOrEqualTo

Less than or equal (`<= maximum`).

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.Number.check(Schema.isLessThanOrEqualTo(10)))(10) // => 10
```

#### isBetween

Within a range; boundaries inclusive unless `exclusiveMinimum`/`exclusiveMaximum`
are set.

```ts
import { Schema } from "effect"

const Port = Schema.Number.check(Schema.isBetween({ minimum: 1, maximum: 65535 }))
Schema.decodeUnknownSync(Port)(8080) // => 8080
```

#### isMultipleOf

Evenly divisible by `divisor`.

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.Number.check(Schema.isMultipleOf(5)))(15) // => 15
```

#### isInt

A safe integer.

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.Number.check(Schema.isInt()))(42) // => 42
```

#### isInt32

A 32-bit signed integer (`-2147483648`–`2147483647`). A filter group of `isInt`
plus a range check.

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.Number.check(Schema.isInt32()))(1000) // => 1000
```

#### isUint32

A 32-bit unsigned integer (`0`–`4294967295`).

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.Number.check(Schema.isUint32()))(1000) // => 1000
```

#### isFinite

A finite number (rejects `NaN`, `Infinity`, `-Infinity`).

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.Number.check(Schema.isFinite()))(3.14) // => 3.14
```

### BigInt checks

These operate on `bigint` values; boundaries are `bigint` literals.

#### isGreaterThanBigInt

Strictly greater than (`> exclusiveMinimum`).

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.BigInt.check(Schema.isGreaterThanBigInt(0n)))(1n) // => 1n
```

#### isGreaterThanOrEqualToBigInt

Greater than or equal (`>= minimum`).

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.BigInt.check(Schema.isGreaterThanOrEqualToBigInt(0n)))(0n) // => 0n
```

#### isLessThanBigInt

Strictly less than (`< exclusiveMaximum`).

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.BigInt.check(Schema.isLessThanBigInt(10n)))(9n) // => 9n
```

#### isLessThanOrEqualToBigInt

Less than or equal (`<= maximum`).

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.BigInt.check(Schema.isLessThanOrEqualToBigInt(10n)))(10n) // => 10n
```

#### isBetweenBigInt

Within a `bigint` range, inclusive unless boundaries are marked exclusive.

```ts
import { Schema } from "effect"

const Big = Schema.BigInt.check(Schema.isBetweenBigInt({ minimum: 0n, maximum: 100n }))
Schema.decodeUnknownSync(Big)(50n) // => 50n
```

### BigDecimal checks

These operate on `BigDecimal` values from the `BigDecimal` module.

#### isGreaterThanBigDecimal

Strictly greater than the given `BigDecimal`.

```ts
import { Schema, BigDecimal } from "effect"

const D = Schema.BigDecimal.check(Schema.isGreaterThanBigDecimal(BigDecimal.fromStringUnsafe("1.5")))
Schema.decodeUnknownSync(D)(BigDecimal.fromStringUnsafe("2.0")) // => BigDecimal(2.0)
```

#### isGreaterThanOrEqualToBigDecimal

Greater than or equal to the given `BigDecimal`.

```ts
import { Schema, BigDecimal } from "effect"

const D = Schema.BigDecimal.check(Schema.isGreaterThanOrEqualToBigDecimal(BigDecimal.fromStringUnsafe("1.5")))
Schema.decodeUnknownSync(D)(BigDecimal.fromStringUnsafe("1.5")) // => BigDecimal(1.5)
```

#### isLessThanBigDecimal

Strictly less than the given `BigDecimal`.

```ts
import { Schema, BigDecimal } from "effect"

const D = Schema.BigDecimal.check(Schema.isLessThanBigDecimal(BigDecimal.fromStringUnsafe("10")))
Schema.decodeUnknownSync(D)(BigDecimal.fromStringUnsafe("9")) // => BigDecimal(9)
```

#### isLessThanOrEqualToBigDecimal

Less than or equal to the given `BigDecimal`.

```ts
import { Schema, BigDecimal } from "effect"

const D = Schema.BigDecimal.check(Schema.isLessThanOrEqualToBigDecimal(BigDecimal.fromStringUnsafe("10")))
Schema.decodeUnknownSync(D)(BigDecimal.fromStringUnsafe("10")) // => BigDecimal(10)
```

#### isBetweenBigDecimal

Within a `BigDecimal` range, inclusive unless boundaries are marked exclusive.

```ts
import { Schema, BigDecimal } from "effect"

const D = Schema.BigDecimal.check(
  Schema.isBetweenBigDecimal({
    minimum: BigDecimal.fromStringUnsafe("0"),
    maximum: BigDecimal.fromStringUnsafe("1")
  })
)
Schema.decodeUnknownSync(D)(BigDecimal.fromStringUnsafe("0.5")) // => BigDecimal(0.5)
```

### Date checks

These operate on `Date` objects; boundaries are `Date` values.

#### isGreaterThanDate

Strictly after the given date.

```ts
import { Schema } from "effect"

const After2000 = Schema.Date.check(Schema.isGreaterThanDate(new Date("2000-01-01")))
Schema.decodeUnknownSync(After2000)(new Date("2020-01-01")) // => Date(2020-...)
```

#### isGreaterThanOrEqualToDate

On or after the given date.

```ts
import { Schema } from "effect"

const D = Schema.Date.check(Schema.isGreaterThanOrEqualToDate(new Date("2000-01-01")))
Schema.decodeUnknownSync(D)(new Date("2000-01-01")) // => Date(2000-...)
```

#### isLessThanDate

Strictly before the given date.

```ts
import { Schema } from "effect"

const D = Schema.Date.check(Schema.isLessThanDate(new Date("2030-01-01")))
Schema.decodeUnknownSync(D)(new Date("2020-01-01")) // => Date(2020-...)
```

#### isLessThanOrEqualToDate

On or before the given date.

```ts
import { Schema } from "effect"

const D = Schema.Date.check(Schema.isLessThanOrEqualToDate(new Date("2030-01-01")))
Schema.decodeUnknownSync(D)(new Date("2030-01-01")) // => Date(2030-...)
```

#### isBetweenDate

Within a date range, inclusive unless boundaries are marked exclusive.

```ts
import { Schema } from "effect"

const InDecade = Schema.Date.check(
  Schema.isBetweenDate({ minimum: new Date("2020-01-01"), maximum: new Date("2029-12-31") })
)
Schema.decodeUnknownSync(InDecade)(new Date("2025-06-01")) // => Date(2025-...)
```

#### isDateValid

A valid `Date` (rejects `new Date("invalid")`, whose time is `NaN`).

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.Date.check(Schema.isDateValid()))(new Date("2025-01-01"))
// => Date(2025-...)
```

### Collection, size, and object checks

`isMinLength` / `isMaxLength` / `isLengthBetween` also apply to arrays. The size
checks work on anything with a `size` property (`Set`, `Map`), and the property
checks operate on plain objects.

#### isMinSize

A `Set`/`Map` (or any `{ size: number }`) with at least `minSize` entries.

```ts
import { Schema } from "effect"

const S = Schema.ReadonlySet(Schema.String).check(Schema.isMinSize(1))
Schema.decodeUnknownSync(S)(new Set(["a"])) // => Set(1) { "a" }
```

#### isMaxSize

At most `maxSize` entries.

```ts
import { Schema } from "effect"

const S = Schema.ReadonlySet(Schema.String).check(Schema.isMaxSize(3))
Schema.decodeUnknownSync(S)(new Set(["a", "b"])) // => Set(2) { "a", "b" }
```

#### isSizeBetween

Size within `[minimum, maximum]` (inclusive).

```ts
import { Schema } from "effect"

const S = Schema.ReadonlySet(Schema.Number).check(Schema.isSizeBetween(1, 2))
Schema.decodeUnknownSync(S)(new Set([1, 2])) // => Set(2) { 1, 2 }
```

#### isUnique

An array whose items are all unique under Effect equality.

```ts
import { Schema } from "effect"

const Tags = Schema.Array(Schema.String).check(Schema.isUnique())
Schema.decodeUnknownSync(Tags)(["a", "b"]) // => ["a", "b"]
```

#### isMinProperties

An object with at least `minProperties` own keys (string and symbol keys count).

```ts
import { Schema } from "effect"

const Rec = Schema.Record(Schema.String, Schema.Number).check(Schema.isMinProperties(1))
Schema.decodeUnknownSync(Rec)({ a: 1 }) // => { a: 1 }
```

#### isMaxProperties

An object with at most `maxProperties` own keys.

```ts
import { Schema } from "effect"

const Rec = Schema.Record(Schema.String, Schema.Number).check(Schema.isMaxProperties(2))
Schema.decodeUnknownSync(Rec)({ a: 1, b: 2 }) // => { a: 1, b: 2 }
```

#### isPropertiesLengthBetween

An object whose own-key count is within `[minimum, maximum]` (inclusive).

```ts
import { Schema } from "effect"

const Rec = Schema.Record(Schema.String, Schema.Number).check(
  Schema.isPropertiesLengthBetween(1, 3)
)
Schema.decodeUnknownSync(Rec)({ a: 1, b: 2 }) // => { a: 1, b: 2 }
```

#### isPropertyNames

Every own key validates against the *encoded* side of the given key schema.

```ts
import { Schema } from "effect"

// All keys must be lowercase strings.
const Rec = Schema.Record(Schema.String, Schema.Number).check(
  Schema.isPropertyNames(Schema.String.check(Schema.isLowercased()))
)
Schema.decodeUnknownSync(Rec)({ name: 1 }) // => { name: 1 }
```

### String-to-value assertion checks

These assert that a `string` is parseable as another representation. They keep
the value a `string` (they do not transform it) — for actual conversion use the
codecs in [transformations](https://effect.plants.sh/schema/transformations/).

#### isStringBigInt

A signed base-10 integer literal (pattern `^-?\d+$`) — Effect's `bigint` string
encoding.

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.String.check(Schema.isStringBigInt()))("-42") // => "-42"
```

#### isStringFinite

A string that represents a finite number.

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.String.check(Schema.isStringFinite()))("3.14") // => "3.14"
```

#### isStringSymbol

A string in the `Symbol(description)` format used by Effect's symbol encoding.

```ts
import { Schema } from "effect"

Schema.decodeUnknownSync(Schema.String.check(Schema.isStringSymbol()))("Symbol(id)") // => "Symbol(id)"
```

## Authoring checks

When no built-in fits, build your own. There are three building blocks:
`makeFilter` (a single predicate), `makeFilterGroup` (bundle checks together),
and `refine` (a check that also narrows the type).

### makeFilter

`Schema.makeFilter(predicate, annotations?, abort?)` turns a predicate into a
`Check`. The predicate receives the decoded value and returns a `FilterOutput`:

- `undefined` or `true` — success.
- `false` — generic failure.
- a `string` — failure with that message.
- a `{ path, issue }` object — failure pointed at a nested path.
- a full `SchemaIssue.Issue`, or a `ReadonlyArray` of any of the above to report
  several failures at once.

```ts
import { Schema } from "effect"

// Cross-field validation: confirmPassword must match password.
const PasswordChange = Schema.Struct({
  password: Schema.String,
  confirmPassword: Schema.String
}).check(
  Schema.makeFilter((o) =>
    o.password === o.confirmPassword
      ? undefined // success
      : { path: ["confirmPassword"], issue: "passwords must match" }
  )
)

console.log(
  String(
    Schema.decodeUnknownExit(PasswordChange)({
      password: "hunter2",
      confirmPassword: "typo"
    })
  )
)
// => Failure(Cause([Fail(SchemaError: passwords must match
// =>   at ["confirmPassword"])]))
```

A simple boolean predicate plus an `expected` annotation is the common case:

```ts
import { Schema } from "effect"

const Even = Schema.Number.check(
  Schema.makeFilter((n) => n % 2 === 0, { expected: "an even number" })
)
Schema.decodeUnknownSync(Even)(4) // => 4
```

### makeFilterGroup

`Schema.makeFilterGroup(checks, annotations?)` combines several checks into one,
with shared annotations applied to the group as a whole. This is how `isInt32` is
built from `isInt` plus a range check.

```ts
import { Schema } from "effect"

const SmallEven = Schema.makeFilterGroup(
  [Schema.isMultipleOf(2), Schema.isLessThan(100)],
  { expected: "a small even number" }
)

Schema.decodeUnknownSync(Schema.Number.check(SmallEven))(42) // => 42
```

### refine

`Schema.refine(refinement, annotations?)` attaches a type-guard predicate that
both validates at runtime **and** narrows the decoded type. Use it when the
constraint corresponds to a more specific TypeScript type. It returns a
`refine<T, S>` schema.

```ts
import { Schema } from "effect"

// Narrow `string` to the literal "GET" | "POST" with a guard.
const Method = Schema.String.pipe(
  Schema.refine(
    (s): s is "GET" | "POST" => s === "GET" || s === "POST"
  )
)

type Method = typeof Method.Type // "GET" | "POST"

Schema.decodeUnknownSync(Method)("GET") // => "GET"
```

### makeIs\* factories

The ordered comparison checks (`isGreaterThan`, `isBetween`, …) are derived from
factories that take an `Order.Order` instance. Use the same factories to build
comparison checks for your own ordered types — this is exactly how the BigInt,
BigDecimal, and Date variants are produced.

- **`makeIsGreaterThan`** / **`makeIsGreaterThanOrEqualTo`** — build `>` / `>=`
  checks from `{ order }`.
- **`makeIsLessThan`** / **`makeIsLessThanOrEqualTo`** — build `<` / `<=` checks.
- **`makeIsBetween`** — build a range check; the returned function takes
  `{ minimum, maximum, exclusiveMinimum?, exclusiveMaximum? }`.
- **`makeIsMultipleOf`** — build a divisibility check from `{ remainder, zero }`.

```ts
import { Schema, Order } from "effect"

type Money = { readonly cents: number }

// Order Money by its cents field, then derive comparison checks.
const order = Order.mapInput(Order.Number, (m: Money) => m.cents)

const isCheaperThan = Schema.makeIsLessThan({ order })
const isInPriceRange = Schema.makeIsBetween({ order })

const Affordable = Schema.Struct({ cents: Schema.Number }).check(
  isInPriceRange({ minimum: { cents: 0 }, maximum: { cents: 5000 } })
)

Schema.decodeUnknownSync(Affordable)({ cents: 1999 }) // => { cents: 1999 }
```

When a check fails, the resulting issue is a `SchemaIssue.Filter` that carries
the input value (`actual`), the failed `filter`, and the inner `issue`. See
[error formatting](https://effect.plants.sh/schema/error-formatting/) to inspect or reshape these.

## Branding

`Schema.brand` gives a value a *nominal* type so structurally-identical values
cannot be mixed up — a `UserId` string can no longer be passed where an
`OrderId` string is expected. Branding narrows the type but adds **no** runtime
check on its own, so combine it with the checks that define the brand.

```ts
import { Schema } from "effect"

const UserId = Schema.String.pipe(
  Schema.check(Schema.isNonEmpty()),
  Schema.brand("UserId")
)

type UserId = typeof UserId.Type // string & Brand<"UserId">

const id = Schema.decodeUnknownSync(UserId)("u_1") // typed as UserId
```

### fromBrand

If you already have a nominal type defined with the `Brand` module,
`Schema.fromBrand(identifier, ctor)` applies that constructor's checks *and* its
brand tag to the schema in one step.

```ts
import { Schema, Brand } from "effect"

// A Brand.Constructor with its own validation. The filter returns a
// FilterOutput: `true`/`undefined` for success, a message string for failure.
type Int = number & Brand.Brand<"Int">
const Int = Brand.make<Int>((n) =>
  Number.isInteger(n) ? true : `${n} is not an integer`
)

const IntSchema = Schema.Number.pipe(Schema.fromBrand("Int", Int))

type IntSchema = typeof IntSchema.Type // number & Brand<"Int">
Schema.decodeUnknownSync(IntSchema)(42) // => 42
```

For the broader story on nominal typing — `Brand.nominal`, `Brand.refined`, and
when to reach for branded types — see [branded types](https://effect.plants.sh/code-style/branded-types/).

With values validated and narrowed, you can also convert *between*
representations — covered in [transformations](https://effect.plants.sh/schema/transformations/).