CMS:SELECT
Searches the database using the given (filter) criteria and returns the records found.
(CMS:SELECT resulttemplate [parameter=value …])
Method: Cms::cmsSelect()
Without key: the first value without = is the resulttemplate
At least one of: filter, relation
PHP fragments: push_result (before) · include, pop_result (after)
Parameters
| Parameter | Value | Default | Required | Description |
|---|---|---|---|---|
resulttemplate ¹ |
templatename |
– | yes | List template that is included to process the results. |
noresulttemplate |
templatename |
– | Detail template that is included if the query returns no results. | |
noresult |
text |
– | Text that is output directly if the query returns no results. | |
filter[…][…] |
value or EMPTY |
– | ² | In the key: name of the field to search (relation_any for any link field; area for the name of the content area, a number there counts as area_id). In the value: the value to search for. |
filter_mode[…] |
AND / OR |
OR |
Mode in which several values of the same field are combined (filter_mode[field]=AND). | |
filter_op[…][…] |
=, ==, !=, !!, >, <, >>, <<, >=, <= |
= |
Comparison operator for the given field (filter_op[field]=">"). Alternatively the operator stands between key and value (filter[price] > 100) or directly before the value (filter[price]=">100"). | |
skip |
number >=0 |
0 |
Number of records skipped in the result set. Only works together with limit. | |
limit |
number >0 |
– | Maximum number of records included in the result set – at the same time the page size for CMS:JUMP_LIST, CMS:JUMP_PREV and CMS:JUMP_NEXT. Without a value or 0: unlimited, no page navigation. | |
sort[…] |
field name(s), comma-separated |
changed_at |
Field to sort by (see order); several comma-separated or as sort[]=…; area sorts by the name of the content area, pointerfield.field by a field of the linked record. | |
order[…] |
desc / asc |
asc |
Order in which to sort (see sort). Without sort, changed_at desc applies. | |
lang |
language code |
(current language) |
Code of the language version whose contents are requested (affects multilingual fields). Not yet implemented – currently always the language of the call applies. | |
relation |
field name(s) comma-separated or 'ANY' |
– | ² | Field names of relation/pointer fields in which the sought records are stored (with src_id); with dest_id the fields via which the sought records point to dest_id. ANY: any pointer field. |
src_id |
record ID(s) |
current record |
Source record whose links are evaluated (without a value: current record). | |
dest_id |
record ID |
– | Reverse direction to relation: the records that point to this record via relation (relation=ANY: via any pointer field). Without src_id, no current record is then used as the source. | |
nextof |
record ID |
– | Only with relation: neighbors of the given record in the link list – the entries after it (order=asc) or before it (order=desc), in the order of the list; with limit=1 the next or previous record. | |
count |
true / false |
false |
true: The number of records in the result set is determined, regardless of "limit" and "skip" (in the resulttemplate in $this->db->num_rows_unlimited; basis of the JUMP commands). | |
hits |
true / false |
true |
false: Only counts (includes count=true); no records are loaded and no resulttemplate is included. The total is then available in the calling template in $this->db->num_rows_unlimited; noresult/noresulttemplate apply if it is 0. | |
archive |
true / false |
false |
true: Expired records (offline_at reached) are included in the query as well. | |
future |
true / false |
false |
true: Pre-dated records (online_at not yet reached) are included in the query as well. | |
debug |
true / false |
false |
true: The generated database query is output for support analysis. | |
sql |
– | – | Raw SQL addition in the WHERE clause (caution: for support only!). | |
state |
status ID or any |
0 |
Without a value only released records (status_id 0). A status ID > 0 finds only records with this status; "any" finds records of every status. | |
request |
comma list |
– | Takes filter and sort values from the URL – comma list or array of the permitted keys: filter[field], filter_op[field], filter_mode[field], skip, limit, sort, order (e.g. "filter[title],sort,order,skip"). filter only with a field name. Values set in the command always take precedence; for a field filtered in the command, the URL adds nothing. | |
set |
array index of $set |
– | The entire output (including the included template) is not output, but stored in the variable $set. |
¹ can be given without a key as the first value.
² at least one of the parameters marked with ² must be given.
[…] – the parameter accepts subkeys, for example name[key]=value.
Usage
Queries records and includes the resulttemplate for the hits; in it,
CMS:RESULT_START … CMS:RESULT_END runs over the rows. The command is the core of every
list.
Only published records are found: released (status_id 0), publication date (online_at)
reached, end (offline_at) not reached. The parameters future, archive and state relax
this per query – also in the preview from the back end, which shows an unpublished record but
shows the lists in it the way the website shows them.
<h2>Latest articles</h2>
(CMS:SELECT article_teaser
filter[area]=articles
sort=online_at order=desc
limit=10
noresult="<p>No articles yet.</p>"
)
List template article_teaser:
<ul>
(CMS:RESULT_START)
<li><a href="(CMS:URL)">(CMS:TEXT title)</a> – (CMS:DATE online_at)</li>
(CMS:RESULT_END)
</ul>
Common patterns:
A record by slug: (CMS:SELECT detail filter[slug]=imprint limit=1)
Only one topic: (CMS:SELECT list filter[area]=articles filter[topics]=42)
Search in the title: (CMS:SELECT list filter[area]=articles filter[title]="*{$_GET['q']}*")
Time period: (CMS:SELECT list filter[area]=articles filter[online_at] >= 2026-01-01)
Linked records: (CMS:SELECT topic_badge relation=topics)
Children in the tree: (CMS:SELECT nav filter[parent_id]="{$cms['record_id']}" filter[depth]=1 sort=position order=asc)
Pageable list: (CMS:SELECT list filter[area]=articles limit=20 skip="{$_REQUEST['skip']}" count=true)
Search form with sorting: (CMS:SELECT list filter[area]=articles limit=20 count=true request="filter[title],sort,order,skip")
Several values for the same field are combined with OR (filter_mode[field]=AND switches
this); different fields are always combined with AND. Comparison operators stand between
key and value (filter[price] > 100, filter[color] != red) or directly before the value
(">100"); wildcards and (empty) belong in the value (Hen*). The complete filter syntax is
in the Basics.
A CMS:SELECT may stand inside a list template (nested lists); the outer result is set aside
automatically and resumed afterwards.
Parameters in detail
resulttemplate
List template for the hits; may stand without a key. Name or area.name:
(CMS:SELECT resulttemplate=article_teaser filter[area]=articles)
(CMS:SELECT article_teaser filter[area]=articles)
(CMS:SELECT articletemplates.article_teaser filter[area]=articles)
noresulttemplate
Template for no hits – for example with a search form or a note:
(CMS:SELECT search_result filter[area]=articles filter[title]="*{$_GET['q']}*" noresulttemplate=search_empty)
noresult
Short text for no hits, without its own template:
(CMS:SELECT list filter[area]=articles noresult="<p>No entries.</p>")
filter
Key = field name (own fields, meta fields such as record_id, status_id, online_at,
parent_id, user_id, the virtual slug, _default for the main field, area for the area
name); value = search value with an optional operator:
filter[area]=articles area by name (number = area_id)
filter[area]=articles,news two areas (OR)
filter[title]="Hen*" begins with
filter[price] > 100 greater than
filter[price]=">100" the same, operator in the value
filter[price]=">100 AND <500" expression in one field
filter[online_at]=2026 the whole year
filter[date] >= today from today on (0:00, time zone of the call)
filter[online_at] > utc_now-7d the last seven days, calculated in UTC
filter[teaser]="(empty)" empty / not translated
filter[image]="(empty)" file field without a file
filter[topics]=42 link field points to record 42
filter[topics]="(empty)" link field without a link
filter[relation_any]=42 any link field points to 42
filter[topics.title]="Iceland" field of the linked record
filter[tags]=a filter[tags]=b two values, OR (see filter_mode)
filter[parent_id]=17 filter[depth]=1 direct children of 17
filter[parent_id]=null filter[depth]=1 top level (records without a parent record)
filter[depth] is not a filter on a record’s indentation; it states how many levels
below filter[parent_id] are searched: 1 = direct children only, 2 = down to the
grandchildren, omitted = all descendants. Without filter[parent_id] it has no effect (a note when saving the template, or with debug=true);
filter[depth]=0 returns no records. The value under “Indent levels” in the record info
(0 = top level), by contrast, is absolute – you find the top level with
filter[parent_id]=null filter[depth]=1.
Text fields are compared and sorted according to the rules of the language in which
you search: upper and lower case and accents do not matter, ß equals ss; in German, in
addition, ä equals ae (filter[title]="Mue*" finds “Müller”); in Swedish, by contrast,
ö is a letter of its own (o does not find ö). The rules follow from the language code;
see Basics.
An empty value cancels the filter – handy for optional filters from the request, risky
with typos: filter[category]="{$cms['id']}" (there is no key id; the correct key is
record_id) returns all records.
filter_mode
Combination of several values of the same field (default OR):
(CMS:SELECT list filter[area]=articles filter[tags]=travel filter[tags]=iceland filter_mode[tags]=AND)
Finds articles that carry both tags.
filter_op
Comparison operator separate from the value – equivalent to writing it in the value and to the operator between key and value:
(CMS:SELECT list filter[area]=articles filter[price]=100 filter_op[price]=">")
(CMS:SELECT list filter[area]=articles filter[price]=">100")
(CMS:SELECT list filter[area]=articles filter[price] > 100)
skip
Offset for paging; usually from the request. skip only works together with limit – without
limit it is ignored, and the query then returns all hits from the first one on:
(CMS:SELECT list filter[area]=articles limit=20 skip="{$_REQUEST['skip']}" count=true)
The page navigation (CMS:JUMP_LIST) stands in the resulttemplate list.
limit
(CMS:SELECT teaser filter[area]=articles limit=3)
At most three hits. Without limit (or with 0) the query returns all hits – for large
areas, a limit therefore always belongs. limit is at the same time the page size of the
paging commands CMS:JUMP_LIST, CMS:JUMP_PREV and
CMS:JUMP_NEXT in the resulttemplate; without limit there is no page
navigation.
sort
Sort field; direction via order or directly in the value:
sort=online_at order=desc
sort="title asc"
sort=position order=asc order from the back end (tree/list)
sort=_default main field of the area
sort=area by area name
sort=author.name field of the linked record
sort[]=status_id sort[]=title order[]=asc order[]=asc
order
Direction for sort: asc (default as soon as sort is given) or desc. Without sort, the
command sorts by changed_at desc – the most recently changed first.
(CMS:SELECT list filter[area]=articles sort=title order=asc)
lang
Not yet implemented – the language of the call applies.
relation
Hits are the records to which the link field of the source record points – without src_id those
of the current record. The order of the link field is given by sort=_custom; without sort, the
default sorting applies (changed_at desc):
(CMS:SELECT topic_badge relation=topics)
(CMS:SELECT gallery relation=images sort="_custom asc")
Several field names, comma-separated, count as OR; ANY means any link field.
sort=_custom and nextof require exactly one field name.
(CMS:SELECT teaser relation="topics,tags")
(CMS:SELECT teaser relation=any)
The reverse direction – records that point to the current one:
(CMS:SELECT product_list relation=rel_products)
(CMS:SELECT product_list filter[area]=products filter[category]="{$cms['record_id']}")
(CMS:SELECT reference_list relation=any dest_id="{$cms['record_id']}")
The first line applies to relation fields (relation): they have a counterpart field
rel_{areaname} in the target area. Pointer fields (pointer) have none – their counterpart is
the filter on the pointer field (second line). See
Every record has a page.
src_id
Source record for relation, if it is not the current record that is meant:
(CMS:SELECT topic_badge relation=topics src_id=1977)
(CMS:SELECT topic_badge relation=topics src_id="{$set['user_id']}")
An empty value or 0 also means the current record. If there is none – for example in a
standalone template without a record – the query finds nothing (and not all records of the area).
dest_id
The reverse direction to relation: hits are the records that point to dest_id via the
pointer field – with relation=any via any of them – for example “Where is this image used?”:
(CMS:SELECT reference_list relation=any dest_id="{$cms['record_id']}")
(CMS:SELECT article_list relation=topics dest_id=1977)
With dest_id, the command does not use a current record as the source without src_id.
The same as a filter value, also for several targets: filter[relation_any]="1977,1978".
nextof
Only together with relation: instead of all linked records, the neighbors of the record
nextof in the same link list – the entries after it with order=asc (default), those before it
with order=desc, in each case in the order of the list (the sorting is thereby fixed, sort is
ignored). With limit=1 this is the next or previous record – for example for “next”/“back” in an
image gallery whose images hang on article 2422:
(CMS:SELECT gallery_next relation=images src_id=2422 nextof="{$cms['record_id']}" limit=1)
(CMS:SELECT gallery_back relation=images src_id=2422 nextof="{$cms['record_id']}" order=desc limit=1)
If nextof is not in the list, there are no hits.
count
Determine the total regardless of limit/skip – the basis for CMS:JUMP_* and for information
such as “hits 21–40 of 137”:
(CMS:SELECT list filter[area]=articles limit=20 skip="{$_REQUEST['skip']}" count=true)
In the resulttemplate list there are then two numbers: $this->db->num_rows are the hits of
this page (at most limit), $this->db->num_rows_unlimited are all hits of the query:
(CMS:NUMBER useval="{$this->db->num_rows_unlimited}") hits
(CMS:RESULT_START) … (CMS:RESULT_END)
(CMS:JUMP_LIST)
Both numbers apply only in the resulttemplate: after the CMS:SELECT, the surrounding
result is active again – the same rule that makes nested lists possible. If only the number is
needed, count in the calling template with hits=false.
hits
hits=false only counts (count=true is thereby included): no records are loaded and no
resulttemplate is included – the name must nevertheless be given. The total is then available
in the calling template in $this->db->num_rows_unlimited:
(CMS:SELECT x filter[area]=articles filter[topics]=42 hits=false)
(CMS:NUMBER useval="{$this->db->num_rows_unlimited}") articles on this topic
If the number is 0, noresult or noresulttemplate apply as usual:
(CMS:SELECT x filter[area]=articles filter[topics]=42 hits=false noresult="No articles on this topic yet.")
If the counting CMS:SELECT stands in a resulttemplate, the number there replaces the total of
the surrounding result; $this->db->num_rows and the running result loop remain untouched. A page
navigation (CMS:JUMP_LIST) of the surrounding result then belongs before the
counting CMS:SELECT.
archive
Include expired records (offline_at reached) – for example for an archive of past events:
(CMS:SELECT archive filter[area]=events archive=true sort=online_at order=desc)
An expired record remains reachable at its own address, by the way; it is only missing from lists
without archive.
future
Include pre-dated records (online_at not yet reached) – for example a preview of upcoming posts:
(CMS:SELECT upcoming filter[area]=articles future=true filter[online_at] > now)
debug
Output the SQL and bind values of the query (for development only). Above them, dropped
filters are listed with the reason – for example an unknown field name or filter[depth]
without filter[parent_id]:
(CMS:SELECT list filter[area]=articles debug=true)
sql
Raw SQL for the WHERE clause – for support only; do not pass user input into it:
(CMS:SELECT list filter[area]=articles sql="AND `record`.`record_id` % 2 = 0")
set
The entire output – the list template with all hits, for noresult its text – ends up in
$set instead of on the page. In this way, an address from a query can be reused in another command:
(CMS:SELECT url_only filter[area]=pages filter[identifier]=cart limit=1 set=cart_url)
<a class="cart" href="<?php echo $set['cart_url']; ?>">…</a>
(CMS:TEXT title prefix="<a href='{$set['cart_url']}'>" suffix="</a>")
The output of debug=true and PHP notices from the list template also end up in $set and not on
the page – to investigate, temporarily leave out set. If the page ends within the query (for
example through a success template), what has been generated up to that point is output normally.
state
Without a value only released records (status_id 0). A status ID finds only records with exactly
this status; any removes the status filter:
(CMS:SELECT list filter[area]=articles state=2)
(CMS:SELECT list filter[area]=articles state=any)
request
Takes filters, sorting and paging from the URL (GET or POST) – for search forms and sortable lists, without writing every value into the command. You state what may be taken over, as a comma list or an array:
request="sort,order,skip" only sorting and paging
request="filter[title],filter[price],filter_op[price]" search in title and price
request[]=sort request[]="filter[title]" the same as an array
Permitted are skip, limit, sort, order as well as filter, filter_op and
filter_mode only with a field name (filter[title]). A filter without a field
name and all are ignored and reported as errors – otherwise a visitor could search by any
field of the records found, including fields the page does not even show, such as an
internal note.
In the URL the keys are named as in the command, only filterop and filtermode without
the underscore: ?filter[title]=Hen*&sort=title&order=desc&skip=20.
(CMS:SELECT list filter[area]=articles limit=20 count=true request="filter[title],sort,order,skip")
The URL can only narrow the query, never widen it:
- Values in the command take precedence. For a field the command filters on,
requesttakes over neither values norfilter_op/filter_mode–?filter[area]=internalor?filterop[area]=!=change nothing aboutfilter[area]=articles. Fields of a linked record count as the reference field (author.namebelongs toauthor);areaandarea_idcount as the same field. skip,limit,sortandorderfrom the command replace the URL value completely.limitfrom the URL only counts as a positive number – “unlimited” cannot be requested.- Encrypted fields only find the exact value; wildcards (
*) and greater/less yield no hits. - Password fields can never be filtered or sorted.
As of camindo CMS 9.1.3