# How to Use the GraphQL API for Property Queries in PropertyWebBuilder

> Learn to use the GraphQL API for property queries in PropertyWebBuilder. Explore searchProperties and findProperty fields for granular filtering via HTTP POST requests.

- Repository: [Ed Tee/property_web_builder](https://github.com/etewiah/property_web_builder)
- Tags: how-to-guide
- Published: 2026-03-01

---

**The PropertyWebBuilder GraphQL API (deprecated but functional) enables external clients to query property listings with granular filters via HTTP POST requests to `/graphql`, using the `searchProperties` and `findProperty` fields defined in `Types::QueryType`.**

PropertyWebBuilder is an open-source real estate platform that ships with a built-in GraphQL API for headless property queries. Although marked as deprecated, this endpoint remains operational and allows developers to retrieve filtered listings across multi-tenant installations. The implementation leverages the **graphql-ruby** gem with Relay-style conventions and DataLoader for efficient batch resolution.

## Architectural Overview of the PropertyWebBuilder GraphQL API

The GraphQL stack consists of five core components that handle request parsing, schema execution, and data resolution.

| Component | File Path | Responsibility |
|-----------|-----------|----------------|
| **`GraphqlController`** | [`app/controllers/graphql_controller.rb`](https://github.com/etewiah/property_web_builder/blob/main/app/controllers/graphql_controller.rb) | HTTP entry point that parses variables, builds execution context, and dispatches to the schema. |
| **`StandalonePwbSchema`** | [`app/graphql/standalone_pwb_schema.rb`](https://github.com/etewiah/property_web_builder/blob/main/app/graphql/standalone_pwb_schema.rb) | Root schema definition wiring Query and Mutation types; enables DataLoader. |
| **`Types::QueryType`** | [`app/graphql/types/query_type.rb`](https://github.com/etewiah/property_web_builder/blob/main/app/graphql/types/query_type.rb) | Contains top-level fields including `search_properties` (lines 57-112) and `find_property` (lines 71-79). |
| **`Types::PropertyType`** | [`app/graphql/types/property_type.rb`](https://github.com/etewiah/property_web_builder/blob/main/app/graphql/types/property_type.rb) | GraphQL object definition exposing fields like `id`, `title`, `price_sale_current_cents`, and geographic coordinates. |
| **`ListedProperty`** | [`app/models/pwb/listed_property.rb`](https://github.com/etewiah/property_web_builder/blob/main/app/models/pwb/listed_property.rb) | Materialized view providing the underlying dataset and filter scopes (`with_property_type`, `with_features`, etc.). |

### Request Flow

1. **HTTP POST** arrives at `/graphql` and is handled by `GraphqlController#execute`.
2. The controller calls `prepare_variables` to parse JSON parameters.
3. A context hash is constructed containing session data and request metadata.
4. `StandalonePwbSchema.execute` runs the query against the schema.
5. Inside `search_properties`, the resolver scopes queries to the current website via `Pwb::Current.website.listed_properties`.
6. Results are rendered as JSON with `protect_from_forgery with: :null_session` ensuring safe external access without Rails sessions.

## Querying Properties with `searchProperties`

The primary entry point for property listings is the `searchProperties` field in `QueryType`. It supports filtering by sale/rental status, price ranges, bedroom/bathroom counts, and custom field-key filters.

### Basic Property Search Query

Send a POST request with an `X-Website-Slug` header to identify the tenant:

```http
POST /graphql HTTP/1.1
Content-Type: application/json
X-Website-Slug: my-demo-site

{
  "query": "query Search($saleOrRental: String){ searchProperties(saleOrRental: $saleOrRental) { id title priceSaleCurrentCents currency } }",
  "variables": { "saleOrRental": "sale" }
}

```

The `X-Website-Slug` header is critical for multi-tenant deployments. If omitted, `GraphqlController#set_current_website` defaults to the first website in the database.

### Advanced Filtering with Field Keys

For granular searches, combine property type, state, features, and price ranges:

```graphql
query PropertySearch(
  $type: String,
  $state: String,
  $features: [String],
  $featuresMatch: String,
  $priceFrom: String,
  $priceTill: String
) {
  searchProperties(
    saleOrRental: "sale",
    propertyType: $type,
    propertyState: $state,
    features: $features,
    featuresMatch: $featuresMatch,
    forSalePriceFrom: $priceFrom,
    forSalePriceTill: $priceTill
  ) {
    id
    title
    priceSaleCurrentCents
    currency
    latitude
    longitude
    extrasForDisplay
  }
}

```

**Variables:**

```json
{
  "type": "types.apartment",
  "state": "states.new_build",
  "features": ["features.garden", "features.pool"],
  "featuresMatch": "any",
  "priceFrom": "200000",
  "priceTill": "500000"
}

```

**Under the hood**, the resolver in [`app/graphql/types/query_type.rb`](https://github.com/etewiah/property_web_builder/blob/main/app/graphql/types/query_type.rb) performs the following operations:
- Starts from `listed_properties.visible` (the materialized view).
- Applies rental/sale scope via `for_rent` or `for_sale` methods.
- Invokes model scopes `with_property_type`, `with_property_state`, and `with_features` (or `with_any_features` when `featuresMatch` is "any").
- Converts price strings to cents using `Money::Currency.find` based on the website's configured currency.

## Fetching Single Properties with `findProperty`

To retrieve a specific property by database ID or URL slug, use the `findProperty` field:

```graphql
query FindProp($id: String!, $locale: String!) {
  findProperty(id: $id, locale: $locale) {
    id
    title
    description
    addressString
    priceSaleCurrentCents
    extrasForDisplay
  }
}

```

As implemented in [`app/graphql/types/query_type.rb`](https://github.com/etewiah/property_web_builder/blob/main/app/graphql/types/query_type.rb) lines 71-79, the resolver first attempts to match by **slug**, then falls back to numeric **ID** if no slug match exists.

## Implementing a Ruby API Client

For server-to-server integration, use HTTParty to consume the endpoint:

```ruby
require 'httparty'
require 'json'

url = 'https://demo.example.com/graphql'
headers = { 
  'Content-Type' => 'application/json',
  'X-Website-Slug' => 'demo-site' 
}

query = <<-GRAPHQL
  query($type: String) {
    searchProperties(propertyType: $type) {
      id 
      title 
      priceSaleCurrentCents 
      currency
    }
  }
GRAPHQL

payload = {
  query: query,
  variables: { type: 'types.apartment' }
}.to_json

response = HTTParty.post(url, body: payload, headers: headers)
puts JSON.pretty_generate(JSON.parse(response.body))

```

## Summary

- **Entry Point**: POST requests to `/graphql` handled by `GraphqlController` with `protect_from_forgery` disabled for API safety.
- **Schema**: `StandalonePwbSchema` defines the GraphQL structure with Relay conventions and DataLoader enabled.
- **Query Fields**: `searchProperties` supports complex filtering via `propertyType`, `propertyState`, `features`, and price ranges; `findProperty` retrieves single records by ID or slug.
- **Data Source**: Queries run against the `ListedProperty` materialized view ([`app/models/pwb/listed_property.rb`](https://github.com/etewiah/property_web_builder/blob/main/app/models/pwb/listed_property.rb)), which pre-calculates visibility and joins core property tables.
- **Multi-tenancy**: Always include the `X-Website-Slug` header to scope queries to the correct tenant.

## Frequently Asked Questions

### Is the PropertyWebBuilder GraphQL API still supported?

The GraphQL API is officially deprecated but remains functional in current versions. According to the repository structure, the endpoint is stable and actively used by internal components, though future development may favor REST endpoints. The implementation in [`app/controllers/graphql_controller.rb`](https://github.com/etewiah/property_web_builder/blob/main/app/controllers/graphql_controller.rb) continues to receive security updates via Rails framework patches.

### How do I specify which website/tenant to query?

Include the `X-Website-Slug` HTTP header in every request. The `GraphqlController#set_current_website` method uses this header to set `Pwb::Current.website`. If the header is missing, the controller defaults to the first website record in the database, which may return incorrect data in multi-tenant installations.

### What filters are available in the `searchProperties` field?

The field accepts arguments for `saleOrRental` (sale/rental flag), `propertyType` and `propertyState` (field-key strings), `features` (array of feature keys), `featuresMatch` ("all" or "any"), and price boundaries (`forSalePriceFrom`, `forSalePriceTill`). These map to scopes in `ListedProperty` such as `with_property_type` and `with_features`.

### Why does the API use a materialized view for property queries?

The `ListedProperty` view (queried via `Pwb::Current.website.listed_properties`) joins core property tables and pre-calculates visibility rules, prices in cents, and feature relationships. This design optimizes GraphQL query performance by reducing complex JOIN operations at request time and ensuring consistent visibility logic across the `search_properties` resolver.