@imagekit-developer/search-assets

Reference for ImageKit's Lucene-like searchQuery syntax — operators, field reference, and examples for filtering files and folders by name, tags, date, size, format, path, or custom/embedded metadata, plus how to narrow the returned results. Use when calling client.assets.list() with a searchQuery or when searching, filtering, or listing assets.

View in AI SkillSafe app
Scanned · no findings
0 downloads
0 stars
0 demos
SKILL.md
namesearch-assets
descriptionReference for ImageKit's Lucene-like searchQuery syntax — operators, field reference, and examples for filtering files and folders by name, tags, date, size, format, path, or custom/embedded metadata, plus how to narrow the returned results. Use when calling client.assets.list() with a searchQuery or when searching, filtering, or listing assets.

ImageKit Search Queries (searchQuery)

client.assets.list({ searchQuery }) takes a Lucene-like filter string. A good query is the difference between one precise API call and paging through everything. This skill is the cheatsheet for building that string.

When user requests "find Nike brand images", here brand could be a custom metadata field, i.e. "customMetadata.brand". So it is always recommended to first list the custom metadata fields available so that correct searchQuery can be constructed. You can use client.customMetadataFields.list() to get the list of custom metadata fields and their types.

Syntax

  • Operators: = : < <= > >= IN NOT IN NOT = HAS EXISTS NOT EXISTS
  • Combine: AND, OR. Group: ( ... )
  • Quoting: string values in "double quotes"; numbers and booleans (true/false) unquoted; customMetadata/embeddedMetadata field names must be quoted, e.g. "customMetadata.brand".
  • : = begins-with / within-subfolders. = = exact. HAS = case-insensitive full-text (tokenized).

Field reference

Field Operators Value notes
name = : IN NOT = NOT IN HAS String. = exact (case-sensitive), : begins-with, HAS case-insensitive full-text
tags IN NOT IN HAS EXISTS NOT EXISTS Array. Matches both tags and AITags
type = IN NOT = NOT IN "file" | "file-version" | "folder"
id = IN NOT = NOT IN fileId or folderId string
createdAt / updatedAt = < <= > >= IN NOT = NOT IN ISO 8601 ("2020-01-01", "2020-01-01T12:12:12") or relative ("1h" "2d" "3w" "4m" "1y")
width / height = < <= > >= IN NOT = NOT IN Numeric px. Images only
size = < <= > >= IN NOT = NOT IN Bytes (1024) or string ("1mb", "10kb")
format = IN "jpg" "png" "webp" "gif" "svg" "avif" "pdf" "mp4"
private / published / transparency = Boolean, unquoted: true / false
createdBy = IN NOT = NOT IN Uploader email string
path = : IN NOT = NOT IN = exact folder only; : folder + subfolders
"customMetadata.<field>" type-dependent (= : IN HAS < > EXISTS …) Quote the field name. Operators follow the field's schema type
"embeddedMetadata.<field>" type-dependent Quote the field name. Keywords uses IN/EXISTS; DateTimeOriginal uses date ops; LocationTaken supports geo ("40,100 5km")

Query examples

name = "red-dress.jpg"                                  exact name (case-sensitive)
name : "red-dress"                                      name begins with red-dress
name HAS "red dress"                                    full-text: contains red AND dress (any case)
id = "64fb1c2d8a7b4c1234567890"                         single file/folder by id

createdAt > "7d"                                        uploaded in last 7 days
createdAt < 2020-01-01                                  uploaded before Jan 1 2020 (00:00 UTC)
updatedAt >= "2024-06-01T00:00:00"                      modified on/after a timestamp
createdAt > "7d" AND size > "2mb"                       recent AND large

size <= "1mb"                                           1MB or smaller
width > 500 AND height > 500                            at least 500x500 (images)
format = "png"                                          PNG files
format IN ["jpg", "webp"]                               JPG or WebP

tags IN ["sale", "summer"]                              has sale OR summer (tags or AITags)
tags NOT IN ["draft"]                                   excludes draft
tags HAS "sale"                                         full-text token match (sales, summer-sale…)
tags EXISTS                                             has at least one tag
tags NOT EXISTS                                         untagged files

type = "file"                                           files only
private = true                                          private files
published = false                                       drafts / unpublished
transparency = true                                     images with an alpha layer

path = "/sales-banner/"                                 exactly this folder, no subfolders
path : "/sales-banner/"                                 this folder AND its subfolders
format = "png" AND path : "/sales-banner/"              PNGs under a folder tree

"customMetadata.category" IN ["clothing", "accessories"]
"customMetadata.rating" > 4.3
"customMetadata.active" = true
"customMetadata.description" HAS "red"
"embeddedMetadata.DateTimeOriginal" > "1y"
"embeddedMetadata.LocationTaken" = "40,100 5km"

(size < "1mb" AND width > 500) OR (tags IN ["summer-sale", "banner"])

Custom metadata — discover field names first

"customMetadata.<field>" only matches fields defined in your media library's custom-metadata schema, and the allowed operators depend on the field's type (Text/Textarea → = : IN HAS; Number/Date → < <= > >=; Boolean → =; Single/MultiSelect → IN). If the user's request mentions a custom attribute (brand, category, rating, SKU, season…), list the schema first to get the exact field name and type, then build the query:

const fields = await client.customMetadataFields.list();
// each: { id, name, label, schema: { type: 'Text' | 'Number' | 'Date' | 'Boolean' | 'SingleSelect' | 'MultiSelect', ... } }
const brand = fields.find((f) => f.name === 'brand' || f.label === 'Brand');
if (!brand) throw new Error('No "brand" custom metadata field is defined');

const result = await client.assets.list({
  searchQuery: 'type = "file" AND "customMetadata.brand" = "nike"',
  limit: 100,
});

Quote the whole "customMetadata.<field>" token. "embeddedMetadata.<field>" (e.g. Keywords, DateTimeOriginal, LocationTaken) works the same way, but embedded fields are read from the file itself and are not user-defined — no schema lookup needed.

TypeScript usage

client.assets.list() returns (File | Folder)[]. How you get a typed result depends on whether you use searchQuery:

No searchQuery — pass type to get a typed array directly (no narrowing):

const files = await client.assets.list({ type: 'file', path: '/uploads', limit: 100 });
//    ^ File[] — fileId, size, url are all available without narrowing

With searchQuery — the API IGNORES the type/tags/name params, so the result stays the (File | Folder)[] union. Put the type filter inside the query string and narrow each item:

const result = await client.assets.list({
  searchQuery: 'type = "file" AND createdAt >= "7d" AND size > "2mb"',
  limit: 100,
});
const files = [];
for (const item of result) {
  if (item.type === 'file') {
    files.push({ name: item.name, fileId: item.fileId, size: item.size });
  }
}

Accessing ANY member-specific property. On the bare File | Folder union you can only read properties common to both (name, type, createdAt, updatedAt, customMetadata). Every other property — filePath, url, fileId, size, width, tags… (File-only) and folderPath (Folder-only) — exists on just one member and won't compile until you narrow item to a single concrete type. Narrow once with an if/else, then access anything inside that block. Key subtlety: File.type is 'file' | 'file-version' while Folder.type is only 'folder', so branch on type === 'folder' — its else branch is guaranteed File (covers plain files and file-versions):

const rows = result.map((item) => {
  if (item.type === 'folder') {
    return { name: item.name, kind: 'folder', path: item.folderPath }; // item: Folder
  }
  return { name: item.name, kind: 'file', path: item.filePath, size: item.size, url: item.url }; // item: File
});

Only reading File fields? A single if (item.type === 'file') { … } guard is enough — inside it item is a fully-typed File, so any File property is available.

Full-text search — always pair HAS with DESC_RELEVANCE:

const result = await client.assets.list({
  searchQuery: 'name HAS "red dress"',
  sort: 'DESC_RELEVANCE',
  limit: 20,
});

Paginate with skip/limit (max 1000 per page):

for (let skip = 0; ; skip += 100) {
  const page = await client.assets.list({
    searchQuery: 'type = "file" AND tags IN ["sale", "summer"]',
    skip,
    limit: 100,
  });
  if (!page.length) break;
  for (const item of page) {
    if (item.type === 'file') {
      // item narrowed to File
    }
  }
}

Gotchas

  • searchQuery overrides params. When searchQuery is present, the top-level type, tags, and name params have NO effect — express those filters inside the query string instead (e.g. type = "file").
  • Narrow with for...of + if, not .filter((i): i is File => ...). In the Deno/MCP sandbox the global File (Web API) shadows the SDK's File type, so the type predicate fails to compile. Use for (const item of result) { if (item.type === 'file') { … } }.
  • Only common fields exist on the bare File | Folder union. name, type, createdAt, updatedAt, customMetadata are shared; everything else (filePath, url, fileId, size, width, tags, folderPath…) is member-specific and needs narrowing first. Never reach for a property before narrowing.
  • Branch on type === 'folder' to split the union cleanly. File.type is 'file' | 'file-version'; Folder.type is only 'folder'. So type === 'file' catches plain files but leaves the else branch as File | Folder, whereas type === 'folder' gives Folder in the if and a guaranteed File in the else.
  • name =/: are case-sensitive; use HAS for case-insensitive matching.
  • tags queries search both tags and AITags.
  • HAS tokenizes on spaces/punctuation; the last token matches as a prefix ("red" matches redwoods). Pair with sort: 'DESC_RELEVANCE'.
  • Booleans (private, published, transparency) are unquoted; everything string-valued is quoted.
  • width/height/transparency apply to images only.

Embed badges

Add these to your README to show the skill's verification status.

SkillSafe verified badge
Verified badge
[![SkillSafe verified badge](https://api.skillsafe.ai/v1/badge/@imagekit-developer/search-assets/verified)](https://skillsafe.ai/skill/@imagekit-developer/search-assets/)
Installs badge
Installs badge
[![Installs badge](https://api.skillsafe.ai/v1/badge/@imagekit-developer/search-assets/installs)](https://skillsafe.ai/skill/@imagekit-developer/search-assets/)
Scan badge
Scan badge
[![Scan badge](https://api.skillsafe.ai/v1/badge/@imagekit-developer/search-assets/scan)](https://skillsafe.ai/skill/@imagekit-developer/search-assets/)
Eval pass rate badge
Eval pass rate
[![Eval pass rate badge](https://api.skillsafe.ai/v1/badge/@imagekit-developer/search-assets/eval)](https://skillsafe.ai/skill/@imagekit-developer/search-assets/)