How to Use the GraphQL API for Property Queries in PropertyWebBuilder
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 |
HTTP entry point that parses variables, builds execution context, and dispatches to the schema. |
StandalonePwbSchema |
app/graphql/standalone_pwb_schema.rb |
Root schema definition wiring Query and Mutation types; enables DataLoader. |
Types::QueryType |
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 |
GraphQL object definition exposing fields like id, title, price_sale_current_cents, and geographic coordinates. |
ListedProperty |
app/models/pwb/listed_property.rb |
Materialized view providing the underlying dataset and filter scopes (with_property_type, with_features, etc.). |
Request Flow
- HTTP POST arrives at
/graphqland is handled byGraphqlController#execute. - The controller calls
prepare_variablesto parse JSON parameters. - A context hash is constructed containing session data and request metadata.
StandalonePwbSchema.executeruns the query against the schema.- Inside
search_properties, the resolver scopes queries to the current website viaPwb::Current.website.listed_properties. - Results are rendered as JSON with
protect_from_forgery with: :null_sessionensuring 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:
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:
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:
{
"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 performs the following operations:
- Starts from
listed_properties.visible(the materialized view). - Applies rental/sale scope via
for_rentorfor_salemethods. - Invokes model scopes
with_property_type,with_property_state, andwith_features(orwith_any_featureswhenfeaturesMatchis "any"). - Converts price strings to cents using
Money::Currency.findbased 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:
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 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:
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
/graphqlhandled byGraphqlControllerwithprotect_from_forgerydisabled for API safety. - Schema:
StandalonePwbSchemadefines the GraphQL structure with Relay conventions and DataLoader enabled. - Query Fields:
searchPropertiessupports complex filtering viapropertyType,propertyState,features, and price ranges;findPropertyretrieves single records by ID or slug. - Data Source: Queries run against the
ListedPropertymaterialized view (app/models/pwb/listed_property.rb), which pre-calculates visibility and joins core property tables. - Multi-tenancy: Always include the
X-Website-Slugheader 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →