Selectors are simple strings of text that specify fields and values.

For example, "name=ryan" is a simple selector that says: "find items that have the name ryan." These selectors are used throughout ProcessWire to get and find pages (and other types of data). Selectors in ProcessWire are loosely based around the idea and syntax of attribute selectors in jQuery.

The  components of a selector

An individual selector consists of three parts: the field you are looking for, an operator (like an equals '=' sign), and the value you want to match. For example, a basic selector might be:

title=products

In this example, "title" is the field, the equals sign "=" is the operator, and "products" is the value you want to match. This basic selector would match all pages having a title field of "products", regardless of where they exist in the site. You may also specify multiple selectors by separating each with a comma.

Where do you use selectors?

Selectors are used throughout ProcessWire, typically for getting and finding pages with the $page and $pages variables. Below is a list of the most common functions where selectors are used:

$pages->find("selector"); 
$pages->get("selector, path or ID");
$page->children("selector"); 
$page->siblings("selector");
$page->find("selector");  

Any function that accepts a selector will accept multiple selectors split by a comma.

While you are less likely to use them, most of the other utility objects in ProcessWire, all of the array types, and several field types also accept selectors:

$matches = $templates->find("selector"); 
$matches = $users->find("selector"); 
$matches = $fields->find("selector"); 
$matches = $page->images->find("selector")->not("selector"); 
// ...and so on...

Below we examine the 3 parts of a selector in more detail: fields, operators and values.

Selector fields

The field portion of a selector may refer to any field of a page. If you want to know what fields you can use for matching, see "Setup > Fields" in your ProcessWire admin. All custom fields are typically optimized for fast matches.

If you want to match a value in one field or another, you may specify multiple fields separated by a pipe "|" symbol, i.e.

title|name|headline=products

Using the above syntax, the selector will match any pages that have a title, name, or headline field of "products" or "Products". Selector values are not case sensitive unless you configure your MySQL with a case sensitive collation.

Selector operators

The operator portion of a selector may be one of the following:

=   Equal to
!=  Not equal to
<   Less than
>   Greater than
<=  Less than or equal to
>=  Greater than or equal to
*=  Contains the exact word or phrase
~=  Contains all the words 
%=  Contains the exact word or phrase (using slower SQL LIKE) [v2.1]
^=  Contains the exact word or phrase at the beginning of the field [v2.1]
$=  Contains the exact word or phrase at the end of the field [v2.1]

Operators above with [v2.1] are only implemented in ProcessWire 2.1 or newer.

These are fairly self explanatory, but the "contains" operators need some explanation. Lets start with an example:

title|body~=sushi tobiko

The above example would match all pages that have the words "sushi" and "tobiko" somewhere in either their title or body fields. The words don't need to be next to one another in order to match. Whereas this selector:

title|body*=sushi tobiko

...would match only those pages that had the exact phrase "sushi tobiko" present in either their title or body fields. As it's a phrase match, the words must be next to each other in that order. Here's an alternate method to do the same thing (in ProcessWire 2.1 or newer), but with some compromises and benefits:

title|body%=sushi tobiko

This works exactly the same way as the *= except that it uses a different (slower, non-indexed SQL 'LIKE') method with the database engine. While slower, this operator has an advantage over the *= operator when you may need to match very short words or stopwords. As a result, it can match some things that *= and ~= can't.

The *= and ~= rely upon MySQL fulltext indexes, which only index words of at least a certain length (configurable, but typically 4 characters). They also don't index common English words called stopwords. So while it's preferable to use *= and ~= for their speed, if you aren't getting the results you need, you should consider using %= instead (if you can handle the speed hit).

The ^= and $= operators use a method similar to the %= operator, so they are also slower, but can also do something unique. They can match text at the beginning (^=) or end ($=) of a field.

Negative operators

You'll notice we have a != "not equal to" operator. That's great for numbers and fields where we know the entire value, but not particularly handy for large text fields. For example, this would not be useful:

body!=sushi tobiko

That would likely match all the pages in the site, unless you had a page that contained only the phrase "sushi tobiko" as the entire body field with nothing else in it. So that selector isn't useful, whereas, we can take a different approach to negate the result of any selector by simply preceding the field with an exclamation point "!", i.e.

!body*=sushi tobiko

This would match all pages that didn't contain the phrase "sushi tobiko" somewhere in their body field. Now that is useful.

Selector values

After the operator comes the selector value that we want to match. At the basic level, little explanation is needed and the examples in the above section (operators) make that clear. But because selector values can contain nearly anything (including text submitted from a search engine, for example), we need to take some special care with string-based selector values. If your selector value needs to contain a comma, you should surround your selector value in quotes, i.e.

body*="sushi, tobiko"

If you don't surround such a selector in quotes, then ProcessWire will assume the comma is starting another selector (see specifying multiple selectors, below). Selector values may not contain double quotes as part of the value to match, unless you escape them with a backslash character "\". Because the need to match double quotes is rare, a simpler approach is just to disallow double quotes from appearing in your selector values by filtering them out of user input.

Selector values may contain single quotes or apostrophes. For that reason, single quotes aren't interchangeable with double quotes for surrounding selector values.

You may also specify an either/or value, by separating each of the values that may match with a pipe character "|". More details and examples can be found in the "OR selectors" section below.

Specifying multiple selectors

A selector string can match more than one field. You may specify multiple selectors together by separating each with a comma. For example, the following selector would match all pages with a year of 2010 and the word "Hanna" appearing somewhere in the body:

year=2010, body*=Hanna

You may specify as many selectors as you want as long as each is separated by a comma.

OR selectors: matching one value or another

In instances where you need to match values in a single field with an either-or expression, the values should be split with the "or" operator, which is the pipe character "|". The following examples demonstrates its usage:

firstname=Mike|Steve
id=123|124|125
title*=Red|Blue|Green

Each of the above selectors matches pages that have either of the values specified between the pipe "|".

OR selectors, matching one field or another

This was already described in the selector fields section above, but is repeated here for reference. Field names may be separated by a pipe "|" to indicate an OR condition:

body|sidebar*=carbonated

The above selector matches pages that have the word "carbonated" in either the body or sidebar fields.

AND selectors: matching more than one value in the same field

There will be instances where you need to say that a specific field matches more than one selector. This is simple, just specify all the conditions you need to match as separate selectors in the same string. For example:

height>500, height<=1000

This AND selector matches all pages that have a "height" field greater than 500, and less than or equal to 1000.

Sorting the results of a selector

There is a reserved-word for selectors called "sort" and it may be used to specify what order should be returned by the matches. Here is an example of it's usage:

sort=title

That essentially says to sort the results by their "title" field, A-Z. If we want to reverse the sort, we just precede "title" with a minus sign, like this:

sort=-title

That would return results sorted by the "title" field, Z-A.

You may specify multiple fields to sort by. For example, lets say that you wanted to sort first by date descending (newest to oldest) and then by title ascending, you would do this:

sort=-date, sort=title

That way if two results had the same date, the secondary sort would be alphabetical.

Only fields that have their "autojoin" checkbox checked in Setup > Fields can be used for sorting pages. So if you need to be able to sort pages by particular fields, check to make sure they are "autojoin". If they aren't already, you can set them to be.

How results are sorted if you don't specify a "sort" in your selector
In $page->children() and $page->siblings() the results are automatically sorted by the page's default sort field that you specify in the admin. If not specified in the admin, the pages will be sorted by the order they are placed in the admin. This behavior can be overridden by specifying your own "sort=[property]". With $pages->find() and $page->find(), if you don't specify your own "sort=[property]", the results are sorted according to MySQL's text searching relevance. If no text searches are performed in your find(), the results are unsorted. As a result, it is generally a good idea to include a "sort=[property]" when using $pages->find(), especially if you care about the order and your find() operation is not text/relevance related.

How to force pages to sort by their admin order with $pages->find()
Unlike $page->children(), the $pages->find() function does not automatically sort by the order they appear in the site tree. This is because $pages->find() is not limited to finding pages specific to one parent, so it may be pulling pages from multiple places (according to your selector). If your parent page(s) are not already sorting by a specific field, you may still tell the $pages->find() to sort by the parent's order by specifying "sort=sort" in your selector.  This is the same as saying "sort by whatever order I dragged the pages to in the admin." But note that if the results have multiple parents, the resulting order isn't likely to be useful.

Limiting the number of results returned by a selector

If you are dealing with a lot of potential results, you may want to limit the number of returned results for pagination or other reasons. In order to specify a limit, use the reserved word "limit" as a selector, i.e.

limit=50 

That selector tells ProcessWire to return 50 (or fewer) results. The starting result is always the first, unless you use the "start" reserved word, i.e.

start=50, limit=50

This tells ProcessWire to return 50 results starting at the 50th (results 50–100).

If you are using a limit selector with pages, and your template has page-numbers turned on, ProcessWire will automatically set the "start" selector according to the current page number. So when it comes to pagination, you don't usually have to think about anything other than the "limit" selector.

Finally, there is the "end" reserved word, which specifies the actual result number to stop the results at. It would be used instead of the limit selector. An example is:

start=50, end=60

That would return 10 results, 50–60. Use of this selector is not common at present, but is is documented here for reference. 

Count selectors – finding matches by quantity

In ProcessWire 2.1 or later, you can match pages with multiple-value fields based on the number of values they contain. For instance, you can find all pages with a given number of children, images, page references, files, comments, etc. You do this by appending ".count" to the field name. This is best demonstrated by an example. The following example would find pages that have at least 1 child and between 3 and 5 images: 

children.count>0, images.count>=3, images.count<=5  

In this next example, we'll assume you have a page reference field called 'categories', and you want to match all pages that have zero (0) categories:

categories.count=0

Note that a selector like the above will literally match all pages that have a 'categories' field without any selected. It will not match pages that don't have a 'categories' field. 

The 'children' field is automatically built-in to all ProcessWire pages. It is also the only countable field where you can only specify one count operation per query. To clarify, the first example above demonstrates matching 3 to 5 'images'. You can't do this with children.count since it can only be specified once per query. This limitation may be changed at some point in the future. 

Subfield selectors

Some field types support selection by subfield, where a subfield holds a value independent of the field's regular value. This is only used with field types that contain more complex data types. Examples include Repeaters, Files, Images, Comments and Map Markers, among others. Usage in a selector is quite simple and very similar to the count selectors, mentioned in the previous section. The format goes like this: 

field.subfield=value

Other than the specification of the ".subfield", the selector is no different from any other. You may use any operators or selector features that you would use elsewhere. For our first example, lets say we have a field called "images" and that you want to find all pages that have at least one image containing the word "atrium" in the image description:

images.description*=atrium

Here is an example of using a subfield selector with a repeater field called buildings that contains various subfields describing the building. Lets say we want to locate all pages that have at least one building greater than 1000 feet, built before 1980: 

buildings.feet_high>1000, buildings.year_built<1980

Finding pages that use specific templates

You can specify that your matches should be using a specific template like this (replacing 'name' with the name of your template):

template=name

…or specify that it can have any one of these templates:

template=name1|name2|name3

Finding pages that have specific parents or ancestors

To specify that matches should have a specific parent, specify the parent's path, object or ID. First is an example of using the parent's path: 

parent=/path/to/parent/

An example of using the parent's object (where $parent is an instance of a Page object):

parent=$parent

An example of using the parent's ID (where 123 is the ID of the parent):

parent=123

Of course, you can also do this with any of the above syntaxes if it can be any one of multiple parents:

parent=$parent1|$parent2|$parent3

Now lets say that you want to find pages that include a particular parent as an ancestor. Meaning, the found pages must include the given parent at some point in the hierarchy between them at the homepage, whether as a parent, grandparent, great-great-grandparent, etc. To do this, use has_parent. 

has_parent=$parent
has_parent=123
has_parent=/path/to/parent/  

You can specify a Page object, Page ID, or path, like shown above. Note however that at present, you can only specify one page at a time here (meaning no has_parent=1|2|3).

Lastly, you can always replace the equals sign "=" in any of the above examples with "!=" to reverse the behavior. You might find it useful to find pages that don't have a given parent or ancestor: 

parent!=123
has_parent!=/path/to/parent/

Sanitizing user input in selectors

If you are supplying user input in selector values (like from a submitted form), you should sanitize the input before placing it in a selector. If you are expecting an integer for example, then type cast it as an int before using it in your selector, i.e.

$year = (int) $input->post->year; 
$matches = $pages->find("year=$year"); 

If your selector value needs to contain more arbitrary strings, like for text matching, you must sanitize the value before sending it to a selector. You may want to use ProcessWire's built in $sanitizer for that purpose. The sanitizer will remove any characters from the selector that could be problematic. Here is how to use it from your templates:

$keywords = $sanitizer->selectorValue($input->get->keywords); 
$matches = $pages->find("keywords~=$keywords"); 

Though less common, if you are using a field name provided by user input, you should sanitize it like this:

$field = $sanitizer->selectorField($input->get->field); 
$matches = $pages->find("$field=the value you want to match"); 

Examples of selectors as used in page templates

Find all pages using the skyscraper template

$pages->find("template=skyscraper");

Find all skyscrapers with a height greater than 500 ft, and less than or equal to 1000 ft.

$pages->find("template=skyscraper, height>500, height<=1000");

Find all skyscrapers in Chicago with 60+ floors, sorted by floors ascending

$pages->get("/cities/chicago/")->find("floors>=60, sort=floors");

Find all skyscrapers built before 1950 with 10+ floors, sorted by year descending, then floors descending

$pages->find("template=skyscraper, year<1950, floors>=10, sort=-year, sort=-floors");

Find all skyscrapers by architects David Childs or Renzo Piano, and sort by height descending

$david = $pages->get("/architects/david-childs/");
$renzo = $pages->get("/architects/renzo-piano/");
$pages->find("architect=$david|$renzo, sort=-height");

Find all skyscrapers that mention the words "limestone" and "granite" somewhere in their body copy.

$pages->get("/cities/")->find("template=skyscraper, body~=limestone granite");

Find all skyscrapers that mention the phrase "empire state building" in their body copy.

$pages->get("/cities/")->find("template=skyscraper, body*=empire state building");

Comments

  • Carl

    Posted by Carl on Mar 5, 2013 7:08 PM

    This is the part of PW I find not so easy to follow. Excuse my stupid question:

    I am used to in PHP that the equal operator is always "==" ( and not "=" ).

    Why this is different here?

  • Steve

    Posted by Steve on Mar 23, 2013 6:55 PM

    My guess... In MYSQL the operator is "=". The operator here relates more to the database than logical expressions. In php the logical expression is making a comparison, e.g. is "this" equal to "that" (A == B). That's a little different than saying give me all items from a database that equal "this" or "that" (Find Items = A).

  • ben

    Posted by ben on Apr 22, 2013 8:51 AM

    couldn't find out what the field checkbox output in the documentation here but tested some stuff out and found that:

    $varCheck = $pages->('$ourCheckBoxField=1'); // this works to find pages with a checked checkbox field

    could we add the other field types and what they output here?

Post a Comment

Your e-mail is kept confidential and not included with your comment. Website is optional.