PHPackages                             apcdatanalytics/rets-rabbit - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. apcdatanalytics/rets-rabbit

ActiveCraft-plugin[Utility &amp; Helpers](/categories/utility)

apcdatanalytics/rets-rabbit
===========================

Display real estate listings in your craft site in a simple and intuitive way.

1.1.0(6y ago)21432[2 PRs](https://github.com/APCGitHub/rets-rabbit-v2-craft3/pulls)MITPHP

Since Dec 12Pushed 2y ago4 watchersCompare

[ Source](https://github.com/APCGitHub/rets-rabbit-v2-craft3)[ Packagist](https://packagist.org/packages/apcdatanalytics/rets-rabbit)[ RSS](/packages/apcdatanalytics-rets-rabbit/feed)WikiDiscussions master Synced 3d ago

READMEChangelog (2)Dependencies (2)Versions (4)Used By (0)

Rets Rabbit Craft CMS Plugin
============================

[](#rets-rabbit-craft-cms-plugin)

This plugin allows you to connect to the Rets Rabbit API(v2) in order to display your listings in a clean and intuitive way.

Installation
------------

[](#installation)

```
composer require apcdatanalytics/rets-rabbit

```

### Requirements

[](#requirements)

The Rets Rabbit plugin requires at least php 7.0 in accordance with minimum Craft 3 PHP requirements.

Documentation
-------------

[](#documentation)

You can interact with the Rets Rabbit API through the `PropertiesVariable` &amp; `OpenHousesVariable`.

### Open Houses

[](#open-houses)

1. craft.retsRabbit.openHouses.find - Single open house lookup
2. craft.retsRabbit.openHouses.query - Run a raw RESO query

#### find(*int* $id, *object* $resoParams, *bool* $useCache = false, *int* $cacheDuration)

[](#findint-id-object-resoparams-bool-usecache--false-int-cacheduration)

**$id** - The MLS id of the open house you want to fetch from the API.

**$resoParams** - You may pass valid RESO parameters to help filter the API results for a single open house. This can help speed up the response time if you specifically select the fields you will need from the API by using the `$select` parameter.

**$useCache** - Specify if you want the results cached.

**$cacheDuration** - Specify how long you would like the results cached for in seconds. The default is one hour.

```
{% set viewModel = craft.retsRabbit.openHouses.find('OpenHouseId', {'$select': 'OpenHouseId'}, true) %}

{% if viewModel.hasErrors() %}
    {# An error occurred, let the user know #}
{% elseif not viewModel.hasData() %}
    {# No data returned from request #}
{% else %}
    {% set openHouse = viewModel.data %}
    {{openHouse.OpenHouseId}}
{% endif %}
```

#### query(*object* $resoParams, *bool* $useCache = false, *int* $cacheDuration)

[](#queryobject-resoparams-bool-usecache--false-int-cacheduration)

**$resoParams** - You may pass valid RESO parameters to help filter the API results for a single open house. This can help speed up the response time if you specifically select the fields you will need from the API by using the `$select` parameter.

**$useCache** - Specify if you want the results cached.

**$cacheDuration** - Specify how long you would like the results cached for in seconds. The default is one hour.

```
{% set viewModel = craft.retsRabbit.openHouses.query({
    '$select': 'OpenHouseId, OpenHouseDate, OpenHouseStartTime, OpenHouseEndTime',
    '$top': 12
}) %}

{% if viewModel.hasErrors() %}
    {# An error occurred #}
{% elseif not viewModel.hasData() %}
    {# No data returned in response #}
{% else %}
    {% set openHouses = viewModel.data %}
    {% for openHouse in openHouses %}

                {{openHouse.OpenHouseId}}

                    {{ openHouse.OpenHouseDate }}

                    {{ openHouse.OpenHouseStartTime }}

                    {{ openHouse.OpenHouseEndTime }}

    {% endfor %}
{% endif %}
```

### Properties

[](#properties)

1. craft.retsRabbit.properties.find - Single listing lookup
2. craft.retsRabbit.properties.query - Run a raw RESO query
3. craft.retsRabbit.properties.search - Perform a search using a saved query from a search form.

#### find(*int* $id, *object* $resoParams, *bool* $useCache = false, *int* $cacheDuration)

[](#findint-id-object-resoparams-bool-usecache--false-int-cacheduration-1)

**$id** - The MLS id of the property you want to fetch from the API.

**$resoParams** - You may pass valid RESO parameters to help filter the API results for a single listing. This can help speed up the response time if you specifically select the fields you will need from the API by using the `$select` parameter.

**$useCache** - Specify if you want the results cached.

**$cacheDuration** - Specify how long you would like the results cached for in seconds. The default is one hour.

```
{% set viewModel = craft.retsRabbit.properties.find('123abc', {'$select': 'ListingId, ListPrice'}, true) %}

{% if viewModel.hasErrors() %}
    {# An error occurred, let the user know #}
{% elseif not viewModel.hasData() %}
    {# No data returned from request #}
{% else %}
    {% set listing = viewModel.data %}
    {{listing.ListingId}}
{% endif %}
```

#### query(*object* $resoParams, *bool* $useCache = false, *int* $cacheDuration)

[](#queryobject-resoparams-bool-usecache--false-int-cacheduration-1)

**$resoParams** - You may pass valid RESO parameters to help filter the API results for a single listing. This can help speed up the response time if you specifically select the fields you will need from the API by using the `$select` parameter.

**$useCache** - Specify if you want the results cached.

**$cacheDuration** - Specify how long you would like the results cached for in seconds. The default is one hour.

```
{% set viewModel = craft.retsRabbit.properties.query({
    '$select': 'ListingId, ListPrice, PublicRemarks, StateOrProvince, City',
    '$filter': 'ListPrice ge 150000 and ListPrice le 175000 and BedroomsTotal ge 3',
    '$orderby': 'ListPrice',
    '$top': 12
}) %}

{% if viewModel.hasErrors() %}
    {# An error occurred #}
{% elseif not viewModel.hasData() %}
    {# No data returned in response #}
{% else %}
    {% set listings = viewModel.data %}
    {% for listing in listings %}

                {{listing.ListingId}}

                {{listing.ListPrice}}

    {% endfor %}
{% endif %}
```

#### search(*int* $id, *object* $overrides, *bool* $useCache = false, *int* $cacheDuration)

[](#searchint-id-object-overrides-bool-usecache--false-int-cacheduration)

**$id** - The id of the saved search parameters usually pulled from a url segment.

**$overrides** - You may pass in the following RESO parameters to help tailor your query search: `$select, $orderby, $top`.

**$useCache** - Specify if you want the results cached.

**$cacheDuration** - Specify how long you would like the results cached for in seconds. The default is one hour.

```
{# Results URL (for example): /search/results/4 #}
{% set searchId = craft.app.request.getSegment(3) %}

{% if not craft.retsRabbit.searches.exists(searchId) %}
    {% redirect '404' %}
{% endif %}

{% set perPage = 12 %}

{% set viewModel = craft.retsRabbit.properties.search(searchId, {
    '$top': perPage,
    '$orderby': 'ListPrice desc'
}, true) %}

{% if viewModel.hasErrors() %}
    {# An error occurred #}
{% elseif not viewModel.hasData() %}
    {# Not results returned from request #}
{% else %}
    {% set results = viewModel.data %}
    {% for listing in results %}
        {# Show listing data #}
    {% endfor %}
{% endif %}
```

> **Note:** If you want to paginate your search results you will need to use our special [`rrPaginate` tag](#search-pagination).

#### Search Form

[](#search-form)

At some point your site will need to have a search form where users enter in search criteria. We've created a markup DSL for your search HTML which will allow you to create beautiful forms for your users.

##### Required Fields

[](#required-fields)

Your search form must have the following two inputs.

1. `actionInput("rets-rabbit/properties/search)"`
2. `redirectInput("search/results/{searchId}")`

> **Note:** Your `redirect` input must have the {searchId} term in it so that the controller endpoint which handles the form POST can redirect you to the results page with the saved search's id in the url.

We believe that the following three search types should cover the vast majority of search form use cases.

1. Single field for a single value
2. Single field for multiple values
3. Multiple fields for a single value

##### Search Form DSL

[](#search-form-dsl)

Next, let's dive into creating a search form. In general our markup DSL follows a simple pattern:

``.

##### Single Field - Single Value

[](#single-field---single-value)

```

```

This will create a query clause that looks like the following:

```
$filter = StateOrProvince eq {value}
```

##### Single Field - Multiple Values

[](#single-field---multiple-values)

```
{% set exteriorAmenities = ['Backyard', 'Pond', 'Garden'] %}

Exterior Features
{% for feature in exteriorAmenities %}

            {{feature}}

{% endfor %}
```

This will create a query clause that looks like the following:

```
$filter = (contains(ExteriorFeatures, {value1}) or contains(ExteriorFeatures, {value2})))
```

##### Multiple Fields - Single Value

[](#multiple-fields---single-value)

```

```

This will create a query clause which looks like the following:

```
$filter = (contains(StateOrProvince, {value}) or contains(City, {value}) or contains(PostalCode, {value}))
```

> **Note:** By default, each input is treated as an independent {and} clause which are strung together to create a valid RESO query.

##### Example Search Form

[](#example-search-form)

The following example contains markup which will generate a form having the following capabilities:

- Run a contains search against the fields: StateOrProvince, City, PostalCode
- Run a range search (ge and/or le) against ListPrice
- Run a range search (ge) against the fields: BathroomsFull and BedroomsTotal
- Run a multi value contains search against: ExteriorFeatures
- Run a multi value contains search against: InteriorFeatures

```

    {{csrfInput()}}

                            Min Price
                            {% for price in range(30000, 300000, 10000) %}
                                {{price | currency('USD', true)}}
                            {% endfor %}

                            Max Price
                            {% for price in range(30000, 300000, 10000) %}
                                {{price | currency('USD', true)}}
                            {% endfor %}

                            Bathrooms
                            {% for val in 0..7 %}
                                {{val}}+
                            {% endfor %}

                            Bedrooms
                            {% for val in 0..7 %}
                                {{val}}+
                            {% endfor %}

                Exterior Features
                {% for feature in exteriorAmenities %}

                            {{feature}}

                {% endfor %}

                Interior Features
                {% for feature in interiorAmenities %}

                            {{feature}}

                {% endfor %}

        {{'Submit'|t}}

```

We used [Bulma.io](https://bulma.io/) in this example, but the above markup will generate something like the following.

[Search Form](screenshots/search-form.png "Search Form")

#### Search Pagination

[](#search-pagination)

Because the Rets Rabbit plugin fetches data from an outside data source, it's not possible to use the native Craft pagination tag. We still believe it is very important to have the ability to paginate your results, so we created a special `rrPaginate` tag which works and looks like the native `paginate` tag in many ways.

```
{% rrPaginate searchCriteria as pageInfo, viewModel %}
```

##### Parameters

[](#parameters)

- [searchCriteria](#searchcriteria) - An instance of `SearchCriteriaModel`
- pageInfo - `craft\web\twig\variables\Paginate` just like with the native `pagination` tag
- viewModel - A view model instance containing possible search results or errors from the API

##### SearchCriteria

[](#searchcriteria)

The main difference in our `rrPaginate` tag compared to the native `paginate` tag is that it expects a `SearchCriteriaModel`as the first parameter. You can get an instance of a search criteria model in the following manner.

```
{% set criteriaModel = craft.retsRabbit.searches.criteria() %}
```

Once you have an instance of the criteria model, you can build your query in a fluent way by chaining method calls on that `criteriaModel` object above.

```
{#
# You must call the forId() and limit() methods for the pagination to work correctly.
#}
{% set criteria = criteriaModel
    .forId(searchId)
    .select('ListPrice', 'PublicRemarks', 'BathroomsFull', 'BedroomsTotal', 'ListingId', 'photos')
    .orderBy('ListPrice', 'desc')
    .limit(24)
    .countBy('exact')
%}
```

**Methods:** A `SearchCriteriaModel` has the following methods available for building a paginated search query.

- forId($searchId) - **(required)** Pass in the search id, usually from the url
- select(...$fields) - Pass in a list of fields you specifically want from the API
- limit($limit) - **(required)** How many results per page
- orderBy($field, $dir) - Order the results
- countBy($cacheType) - Specify the type of total results query for the API to run. Valid values are either 'exact' or 'estimated'. Uses 'estimated' by default.

##### Complete Example

[](#complete-example)

```
{% set searchId = craft.app.request.getSegment(3) %}

{% if not craft.retsRabbit.searches.exists(searchId) %}
    {% redirect '404' %}
{% endif %}

{% set criteriaModel = craft.retsRabbit.searches.criteria() %}
{% set criteria = criteriaModel
    .forId(searchId)
    .select('ListPrice', 'StreetNumber', 'StateOrProvince', 'StreetName', 'StreetDirSuffix', 'PublicRemarks', 'BathroomsFull', 'BedroomsTotal', 'ListingId', 'photos')
    .orderBy('ListPrice', 'desc')
    .limit(24)
    .countBy('exact')
%}

{% rrPaginate criteria as pageInfo, viewModel %}

{% if viewModel.hasErrors() %}

            Uh oh...

            We could not process your request. Please try again.

{% elseif not viewModel.hasData() %}

            Hmm..

            We could not find any results for your search. Try changing your search parameters.

{% else %}
    {% set listings = viewModel.data %}

        {% for listing in listings %}

                {% include "properties/includes/_grid-item" %}

        {% endfor %}

    {% if pageInfo.totalPages > 1 %}

            {% if pageInfo.prevUrl %}
                Previous
            {% endif %}
            {% if pageInfo.nextUrl %}
                Next page
            {% endif %}

                {% if pageInfo.currentPage > 2 %}

                        First

                        &hellip;

                {% endif %}
                {% for page, url in pageInfo.getPrevUrls(2) %}

                        {{ page }}

                {% endfor %}

                    {{pageInfo.currentPage}}

                {% for page, url in pageInfo.getNextUrls(2) %}

                        {{ page }}

                {% endfor %}
                {% if pageInfo.nextUrl %}

                        &hellip;

                        Last

                {% endif %}

    {% endif %}
{% endif %}
```

We used [Bulma.io](https://bulma.io/) in this example, but the above markup will generate something like the following.

[Pagination](screenshots/pagination.png "Pagination")

### Other Variables

[](#other-variables)

Aside from `PropertiesVariable` &amp; `OpenHousesVariable`, there are a couple of other variables you have access to in your templates.

- SearchesVariable - `craft.retsRabbit.searches`

#### SearchesVariable

[](#searchesvariable)

This template variable has the following methods:

1. [exists](#bool-existsint-id)

#### *bool* exists(*int* $id)

[](#bool-existsint-id)

This method checks if a given search id exists. This method is useful for checking if a search exists before trying to execute it which will provide more predictable error handling.

###  Health Score

30

—

LowBetter than 64% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity15

Limited adoption so far

Community12

Small or concentrated contributor base

Maturity63

Established project with proven stability

 Bus Factor1

Top contributor holds 91.3% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~286 days

Total

2

Last Release

2424d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/69669ef38f8d0294fb9a63261ef9530937f2541db19ac90e7e69cd22b0bf9d55?d=identicon)[APCData](/maintainers/APCData)

---

Top Contributors

[![panda4man](https://avatars.githubusercontent.com/u/3106756?v=4)](https://github.com/panda4man "panda4man (136 commits)")[![APCGitHub](https://avatars.githubusercontent.com/u/32304863?v=4)](https://github.com/APCGitHub "APCGitHub (13 commits)")

---

Tags

cmsCraftcraftcmscraft-pluginrets rabbit

### Embed Badge

![Health badge](/badges/apcdatanalytics-rets-rabbit/health.svg)

```
[![Health](https://phpackages.com/badges/apcdatanalytics-rets-rabbit/health.svg)](https://phpackages.com/packages/apcdatanalytics-rets-rabbit)
```

###  Alternatives

[verbb/navigation

Create navigation menus for your site.

90683.7k17](/packages/verbb-navigation)[verbb/formie

The most user-friendly forms plugin for Craft.

101372.9k40](/packages/verbb-formie)[verbb/comments

Add comments to your site.

13753.1k](/packages/verbb-comments)[verbb/tablemaker

Create customizable and user-defined table fields.

40168.8k1](/packages/verbb-tablemaker)[supercool/tablemaker

Create customizable and user-defined table fields.

40141.7k](/packages/supercool-tablemaker)[pennebaker/craft-architect

CraftCMS plugin to generate content models from JSON/YAML data.

72148.5k5](/packages/pennebaker-craft-architect)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
