---
title: stories find
description: Searches a Storyblok space for stories that match filters, and describes the syntax of each filter.
url: https://www.storyblok.com/docs/tooling/cli/stories/find
---

# stories find

`stories find` searches a Storyblok space for stories that match a set of filters. The command only reads data and leaves the space unchanged. It prints each match as a line of JSON. In a terminal, it prints a table.

Use `stories find` to query a space and pass the matches to scripts. For example, list stories that use a block or reference another story.

The command combines all filters with AND.

For examples of common audits and fixes for slow runs and missing results, refer to [stories find: Examples and Troubleshoot](/docs/tooling/cli/stories/find/examples).

## Usage

```bash
storyblok stories find [arguments] [flags]
```

## Arguments

| Argument | Type | Description |
| --- | --- | --- |
| Search text | string | A case-insensitive full-text search of story names, slugs, and content, through the Management API [`text_search`](/docs/api/management/stories/retrieve-multiple-stories#query-parameters) parameter. |

## Flags

All flags are optional, except `--space`.

The command lists stories first. The story listing holds story metadata without `content`. The per-story fetch retrieves one story with its `content` through one Management API request.

| Flag | Type | Description |
| --- | --- | --- |
| `--space`, `-s` | integer | _Required._ The ID of the Storyblok space to search. |
| `--entry-type` | string | Restrict results to `story`, `folder`, or `all`. Defaults to `all`. |
| `--starts-with` | string | Restrict results to stories below a path prefix. For example, `en/blog`. Omit the leading slash. |
| `--container-block` | string | Match stories whose root block is this block. A shorthand for [`--query`](/docs/tooling/cli/stories/find#content-field-queries). |
| `--includes-block` | string | Match stories that contain all these [blocks](/docs/concepts/blocks) at any depth. Comma-separated. |
| `--query`, `-q` | string | Filter by root-level content fields with [filter query syntax](/docs/tooling/cli/stories/find#content-field-queries). |
| `--tag` | string | Match stories with any of these [tags](/docs/manuals/stories#tags). Comma-separated. |
| `--workflow-stage` | string | Match stories at any of these [workflow stage IDs](/docs/api/management/workflow-stage/workflow-stage-object). Comma-separated. |
| `--publish-status` | string | Match stories with any of these [publish statuses](/docs/tooling/cli/stories/find#publish-status). Comma-separated. |
| `--created` | string | Match stories created in a [date range](/docs/tooling/cli/stories/find#date-filters). For example, `2024` or `7d`. |
| `--updated` | string | Match stories last updated in a date range. |
| `--last-published` | string | Match stories last published in a date range. |
| `--first-published` | string | Match stories first published in a date range. |
| `--scheduled` | string | Match stories with a pending scheduled publish. Accepts an optional date range. |
| `--where` | string | Match stories with a [JSONPath expression](/docs/tooling/cli/stories/find#jsonpath-expressions). Repeatable. Expressions combine with AND. |
| `--references` | string | Match stories that [reference](/docs/tooling/cli/stories/find#reference-search) all these story UUIDs. Comma-separated. |
| `--check-references` | string | Report stories with [reference issues](/docs/tooling/cli/stories/find#reference-checks). Accepts a list of issue types. |
| `--sort` | string | [Sort results](/docs/tooling/cli/stories/find#sort-order-and-limits) on the server. For example, `updated_at:desc`. |
| `--limit` | integer | Stop the run after this many results. |
| `--skip-content` | boolean | [Skip the per-story fetch](/docs/tooling/cli/stories/find#--skip-content) and print story listing metadata only. |
| `--capi-filter` | boolean | [Evaluate `--where` on content](/docs/tooling/cli/stories/find#--capi-filter) from bulk Content Delivery API reads. |
| `--capi-params` | string | Add [query parameters](/docs/tooling/cli/stories/find#--capi-params) to `--capi-filter` reads. For example, `version=published`. |

> [!WARNING]
> Flags that accept comma-separated lists treat the comma differently: some match all values, and others match any value.
> 
> | Flag | Matching stories |
> | --- | --- |
> | `--includes-block a,b` | Contain both blocks |
> | `--references a,b` | Reference both stories |
> | `--tag a,b` | Use either tag |
> | `--workflow-stage a,b` | Are at either stage |
> 
> To match any block or reference, or every tag, follow the example in [Match any or all values](/docs/tooling/cli/stories/find/examples#match-any-or-all-values).

## Examples

The following examples assume you defined a `space` in the configuration file.

```bash
# Find published stories that mention "pricing"
storyblok stories find "pricing" --publish-status published

# Find stories whose root block is product
storyblok stories find --container-block product

# Find stories that contain both a hero and a pricing_table block
storyblok stories find --includes-block hero,pricing_table

# Find stories updated in the last seven days
storyblok stories find --updated 7d

# List every folder, without fetching content
storyblok stories find --entry-type folder --skip-content
```

## Filters

The following sections describe the syntax of each filter. To learn which filters need story content, refer to [content flags](/docs/tooling/cli/stories/find#content-flags).

### Content field queries

`--query` filters by root-level content fields with the [filter query](/docs/api/content-delivery/v2/filter-queries) syntax `[field][operation]=value`.

```bash
# Match a value
storyblok stories find --query "[category][in]=electronics"

# Match a case-insensitive wildcard
storyblok stories find --query "[title][like]=*Headphones*"

# Combine several conditions with AND
storyblok stories find --query "[featured][is]=true&[price][gt_int]=100"

# Pass a JSON object, which suits generated queries
storyblok stories find --query '{"component":{"in":"product"}}'
```

`--container-block product` is shorthand for `--query "[component][in]=product"`. The command merges both flags into one query.

The Management API drops a filter that contains an unknown operator, so the search returns every story that the other filters match. To prevent this, the command exits with a [usage error](/docs/tooling/cli/stories/find#exit-codes) when `--query` has any of the following problems:

-   An unknown operator, such as `[eq]`
-   The same field and operator as `--container-block`
-   No readable clause

### JSONPath expressions

`--where` covers cases beyond `--query`: nested blocks, any-depth search, regular expressions, `>=` and `<=` comparisons, and story properties. Expressions follow [JSONPath (RFC 9535)](https://datatracker.ietf.org/doc/html/rfc9535), and a story matches when the expression selects at least one node.

Two rules cover most expressions:

-   `@` is the node under test. `$..[?(@.component == "hero")]` tests every block in the story.
-   `$` is the story. Use `$` for story properties: `$[?($.updated_at > "2025-01-01")]` tests the story itself. `$[?(@.updated_at > …)]` tests each property value of the story instead and matches nothing.

A `$..` expression runs without errors over any content, including strings, numbers, and `null`.

Expressions support the function extensions `match()` and `search()` for regular expressions, as well as `length()`, `count()`, and `value()`.

Wrap each expression in single quotes, and use double quotes for strings inside the expression. Inside double quotes, shells such as `bash` and `zsh` expand `$[` before the command receives the expression.

```bash
# Find stories with an asset that has empty alt text
storyblok stories find --where '$..[?(@.fieldtype == "asset" && @.alt == "")]'

# Match a nested field with a regular expression
storyblok stories find --where '$..[?match(@.sku, "SB-[0-9]+")]'

# Narrow with a server-side filter first, then refine with --where
storyblok stories find --includes-block hero --where '$..[?(@.component == "hero" && @.active == false)]'

# Combine several expressions with AND
storyblok stories find \
  --where '$..[?(@.component == "hero" && @.headline != "")]' \
  --where '$..[?(@.component == "pricing_table" && @.currency == "EUR")]'
```

### Publish status

`--publish-status` matches stories by publish status:

| Value | Matches |
| --- | --- |
| `published` | Published stories without unpublished changes |
| `published-with-changes` | Published stories with unpublished changes |
| `draft` | Stories that were never published or that someone unpublished |

Separate values with commas to match any listed status. Folders have a publish status as well. Add `--entry-type story` to exclude them.

```bash
# Find every published story, with or without unpublished changes
storyblok stories find --publish-status published,published-with-changes
```

### Date filters

Five flags match stories by date. Each flag takes a date range.

| Flag | Story property |
| --- | --- |
| `--created` | `created_at` |
| `--updated` | `updated_at` |
| `--last-published` | `published_at`, the latest publish |
| `--first-published` | `first_published_at` |
| `--scheduled` | The date of a pending scheduled publish |

A date range takes one of the following forms:

| Value | Matches |
| --- | --- |
| `30m`, `12h`, `7d`, `2w` | Within the last 30 minutes, 12 hours, 7 days, or 2 weeks |
| `3mo`, `1y` | Within the last three months or the last year |
| `2024`, `2024-06` | That whole year or month |
| `2024-06-15` | That whole day |
| `2024-06-15T09:00+02:00` | That minute, as an ISO 8601 date and time with an optional offset |
| `2024-06-01..2024-06-30` | Between two dates, both included |
| `2024-06-01..` | On or after a date |
| `..2024-06-30` | On or before a date, including that whole day |
| `30d..7d` | Between 30 and 7 days ago, in either order |
| `7d..now` | Between seven days ago and now, in either order |

Each end of a range covers the whole period it names. `2024-01..2024-03` runs from the start of January to the end of March. Durations count back from the start of the run, and `now` stands for that moment. Duration units are lowercase: `m` means minutes, and `mo` means months. A date or time without an offset is in Coordinated Universal Time, like the dates in the output.

```bash
# Find stories created in 2024 below a path prefix
storyblok stories find --starts-with en/blog --created 2024

# Find stories first published in the third quarter of 2024
storyblok stories find --first-published 2024-07..2024-09

# Find published stories whose unpublished changes are older than 90 days
storyblok stories find --publish-status published-with-changes --updated ..90d

# Find stories scheduled to publish in the next seven days
storyblok stories find --scheduled 7d
```

A story matches only if it has the date. For example, a story that was never published has no `published_at`.

Pending publishes are always in the future. Durations in `--scheduled` count forward: `--scheduled 7d` matches publishes within the next seven days and also matches overdue publishes: stories whose scheduled time has passed but that are still unpublished. Without a range, `--scheduled` matches every pending scheduled publish. In that case, place the search text before `--scheduled`, or `--scheduled` takes the text as its range. A story matches when any of its languages has a pending scheduled publish in the range.

### Sort order and limits

`--sort` sorts results on the server. `--limit` returns the top results of the sort order.

```bash
# Sort by most recent update
storyblok stories find --sort updated_at:desc

# Sort by publish date, oldest first, then by slug
storyblok stories find --sort published_at:asc,slug:asc

# Sort by a content field as a number instead of text
storyblok stories find --sort content.price:desc:float

# Print the 10 most recently updated stories
storyblok stories find --sort updated_at:desc --limit 10
```

The format is `field[:direction[:cast[:nulls]]]`, with commas between multiple fields:

| Part | Description |
| --- | --- |
| `field` | A story property, such as `updated_at`, `slug`, or `name`, or a content field with a `content.` prefix, such as `content.price`. |
| `direction` | `asc` or `desc`. Defaults to `asc`. |
| `cast` | `int` or `float`. Without a cast, values sort as text. |
| `nulls` | `nulls_first` or `nulls_last`. Defaults to `nulls_last`. |

`--limit` accepts a positive integer and stops the run once the command prints that many results. A run that stops at the limit exits with code `0`.

### Reference search

`--references` matches every story that references a story UUID. The search covers [reference fields](/docs/concepts/references), [link fields](/docs/concepts/fields#link), and links in [rich text](/docs/concepts/fields#rich-text). The search ignores references from a story to itself. Comma-separated UUIDs combine with AND. A story matches only if it references every one.

```bash
# Find stories that reference a story
storyblok stories find --references "0c4f0f4a-9b5e-4c3a-8e7d-2f1a6b8c9d0e"

# Combine a reference search with other filters
storyblok stories find --references "0c4f0f4a-9b5e-4c3a-8e7d-2f1a6b8c9d0e" --container-block product --publish-status published
```

### Reference checks

`--check-references` checks the references in each story that the other filters return, and prints only the stories with problems. The check includes field-level translations of reference fields, such as `author__i18n__de`.

The check reports three issue types:

| Type | Description |
| --- | --- |
| `broken` | The target UUID is missing from the space. |
| `unpublished` | The target exists but has no published version. Folders are exempt. |
| `stale_url` | The cached URL of a link field differs from the current path of the target. |

A reference can be both `unpublished` and `stale_url`, and the command reports both. To report only some types, pass a comma-separated list. For example, `--check-references broken`.

In a terminal, an `issues` column summarizes the issues of each story. For example, `2 broken, 1 stale_url`. In JSON Lines output, each reported story carries a `_ref_issues` array:

```json
{
  "id": 123,
  "full_slug": "blog/my-post",
  "_ref_issues": [
    {
      "type": "stale_url",
      "ref_type": "multilink",
      "target_uuid": "def-456",
      "cached_url": "/about/old-slug",
      "actual_url": "about/new-slug",
      "field_path": "content.hero.cta_link"
    },
    {
      "type": "broken",
      "ref_type": "relation",
      "target_uuid": "ghi-789",
      "field_path": "content.author"
    }
  ]
}
```

`ref_type` is `multilink`, `richtext`, or `relation`. `actual_url` appears only when the command finds the target, and `cached_url` appears only for reference types that store one.

The command applies `--where` after the check. An expression can filter by `_ref_issues`:

```bash
# Check every reference in the space
storyblok stories find --check-references

# Report issues in link fields only
storyblok stories find --check-references --where '$._ref_issues[?(@.ref_type == "multilink")]'
```

The check reads the content of every story it checks. For a space-wide check, add [`--capi-filter`](/docs/tooling/cli/stories/find#--capi-filter) to read that content in bulk. The command warns about the gaps that this creates.

## Content flags

A filter that must check content costs one per-story fetch, which is the slowest part of a run. Every filter runs without story content, except `--check-references` and a `--where` expression that reads `content`.

Two flags change how a run gets content:

-   `--skip-content` skips the per-story fetch.
-   `--capi-filter` reads content in bulk and fetches only the matching stories.

The flags are independent. A run follows one of four pipelines:

| Flags | `--where` can read content | Output includes `content` | Per-story fetches |
| --- | --- | --- | --- |
| None | Yes | Yes | One per story in scope |
| `--skip-content` | No  | No  | None |
| `--capi-filter` | Yes | Yes | One per match |
| `--capi-filter --skip-content` | Yes | No  | None |

If the search doesn’t read content, use `--skip-content`. If it does, use `--capi-filter`, and add `--skip-content` when the output doesn’t need `content`. Searches that need data that exists only in the Management API story, such as field-level translations or `translated_stories`, use neither flag.

### `--skip-content`

The output is the story object as the listing returns it, without `content`. Use `--skip-content` for inventories, slug and ID lists, and publish status counts.

-   `--where` reads story properties only, such as `full_slug`, `tag_list`, or `stages`. An expression on `content` matches nothing.
-   `--check-references` is unavailable, and the command rejects the combination.
-   The `content` key is absent. A script such as `jq '.content.component'` prints `null`.

### `--capi-filter`

With `--capi-filter`, the command evaluates `--where` on content from bulk Content Delivery API (CAPI) reads and fetches only the matching stories from the Management API (MAPI). The output is always the MAPI story. A selective filter turns minutes into seconds. A filter that keeps almost every story gains nothing.

`--capi-filter` requires at least one `--where` expression. With `--check-references`, `--capi-filter` needs no expression and serves as a bulk content source for the check. To exclude folders, which have no CAPI content and cost a per-story fetch each, add `--entry-type story`.

CAPI content differs from MAPI content in two ways:

-   Field-level translations are absent. The command rejects an expression with an `__i18n__` key. To filter on a translation, request the language with `--capi-params` and match the base field name.
-   Draft content includes `_editable`. An expression on `_editable` matches only with `--capi-filter`.

Folders, unpublished stories (with `version=published`), and stories in a failed batch have no CAPI content. The command fetches these stories from the MAPI and tests them as usual. With `--skip-content`, it tests them on story properties only. An expression on `content` excludes them. The command warns with their count when the run ends.

`--check-references --capi-filter` skips references in field-level translations. For stories without CAPI content, it runs the check without their content and reports no issues for them. The command warns about both gaps.

### `--capi-params`

`--capi-params` adds query parameters to the bulk content reads. For example, `version=published` decides matches against the published version, and `language=de` decides them against the German version. Pass key-value pairs separated by commas: `version=published,language=de`.

-   `--capi-params` requires `--capi-filter`.
-   `version=published` requires `--publish-status published`, `--publish-status published-with-changes`, or both.
-   `--check-references` accepts every parameter except `language`.

## Output

The format depends on the destination of `stdout`. When `stdout` is a pipe or a file, the command prints [JSON Lines](https://jsonlines.org). When `stdout` is a terminal, the command prints a table. In both cases, the command writes warnings and the run summary to `stderr`.

### Piped output

The command prints each match as a complete [story object](/docs/api/management/stories/the-story-object) on a single line. JSON Lines has one self-contained JSON document per line. Every line includes `id`, `uuid`, and `full_slug`.

The command prints each result as it finds it. You can process the first line before the run ends. If the program that reads the output stops, as `head` does, the run stops. A reference check is the exception: it prints its results at the end of the run.

For ways to process results, refer to [process results with other tools](/docs/tooling/cli/stories/find/examples#process-results-with-other-tools).

### Terminal output

In a terminal, the command shows its progress during the run and prints a table when the run ends. The table has `ID`, `NAME`, and `FULL SLUG` columns, and adds an `issues` column with `--check-references`. The full slug of a folder ends in `/`.

```text
ID         NAME              FULL SLUG
123456789  Running shoes     en/products/running-shoes
123456790  Trail shoes       en/products/trail-shoes
```

A table taller than the terminal opens in `less`. To turn the pager off, set `PAGER=cat`. Scripts read the JSON Lines output, which is the stable format. To get JSON Lines in a terminal, pipe the output into another command, such as `| jq .` or `| cat`.

### Annotations

Some flags add keys that start with an underscore to each line. These annotations belong to the command: `--check-references` adds `_ref_issues`. Underscored keys inside `content`, such as `_uid`, belong to the story.

Remove annotations before you send a story back to the Management API:

```bash
storyblok stories find --check-references | jq --compact-output 'with_entries(select(.key | startswith("_") | not))'
```

### Exit codes

| Code | Meaning |
| --- | --- |
| `0` | The run completed. This includes runs that stop at `--limit`, runs that stop when the receiving program closes the pipe, and `--capi-filter` runs that recover from a failed batch. |
| `1` | Part of the run failed, such as a listing page or a per-story fetch. The summary on `stderr` names the failure, and the results may be incomplete. |
| `2` | A usage error, such as an invalid flag value. The command fetched nothing. |

## Pagination

-   [Previous: validate](/docs/tooling/cli/schema/validate)
-   [Next: Examples](/docs/tooling/cli/stories/find/examples)
