How to Configure Embeddable Property Widgets with Domain Restrictions in PropertyWebBuilder

PropertyWebBuilder lets you restrict widget embedding to specific domains by populating the allowed_domains array on a Pwb::WidgetConfig model, which the public API controller validates against the request's Origin header before serving widget data.

PropertyWebBuilder is an open-source real estate CMS that supports embeddable property listing widgets for external websites. To prevent unauthorized use of these widgets on competitor or malicious sites, the codebase implements a domain restriction system using PostgreSQL arrays and request origin validation. This guide explains how to configure and enforce these restrictions using the actual implementation found in the etewiah/property_web_builder repository.

Understanding the Domain Restriction Architecture

The domain restriction feature operates across three layers: the database model that stores allowed domains, the admin interface that captures user input, and the public API that validates incoming requests.

The WidgetConfig Model and Database Schema

In app/models/pwb/widget_config.rb, the allowed_domains attribute is defined as a PostgreSQL array column. The migration at db/migrate/20251228100000_create_pwb_widget_configs.rb (line 54) declares this as string[] with a default empty array, as reflected in db/schema.rb (line 1502). An empty array signifies that the widget allows embedding from any domain, while a populated array enforces the restriction list.

The Public API Controller Validation

Every public widget request routes through app/controllers/api_public/v1/widgets_controller.rb. Before returning data, the validate_origin method (lines 84-90) extracts the Origin or Referer header, parses the host, and invokes domain_allowed? on the widget configuration instance. If the domain is not allowed, the controller logs a warning (lines 92-95) and can optionally reject the request.

Configuring Allowed Domains

You can populate the domain restriction list either through the administrative interface or programmatically via the Rails console.

Via the Admin Interface

In the Widget edit screen, administrators enter one domain per line in a textarea. The SiteAdmin::WidgetsController processes this input at lines 80-82:


# app/controllers/site_admin/widgets_controller.rb

if permitted[:allowed_domains].is_a?(String)
  permitted[:allowed_domains] = permitted[:allowed_domains]
                            .split("\n")
                            .map(&:strip)
                            .reject(&:blank?)
end

This transformation converts the newline-separated string into a clean array before persistence.

Via Rails Console

For automated provisioning or testing, create restricted widgets programmatically:


# Create a widget restricted to specific partners

widget = Pwb::WidgetConfig.create!(
  website: my_website,
  name: 'Partner Widget',
  allowed_domains: ['example.com', '*.partner.com']
)

How Domain Validation Works

The domain_allowed? method in app/models/pwb/widget_config.rb (lines 35-51) implements the authorization logic with support for wildcard patterns.

Wildcard Pattern Matching

The validation normalizes incoming hosts by converting to lowercase and stripping a leading www. prefix. It then checks against stored patterns using the following rules:

  • Exact match: example.com matches only example.com
  • Wildcard match: *.example.com matches sub.example.com, deep.sub.example.com, and example.com itself (the code checks for exact match after stripping the *. prefix)

This allows flexible partner agreements while maintaining strict security boundaries.

Request Evaluation Flow

When a browser requests widget data from GET /api_public/v1/widgets/:widget_key, the controller executes this validation sequence:

  1. Extract the Origin or Referer header from the request
  2. Parse the host component using URI parsing
  3. Call domain_allowed?(host) on the widget configuration
  4. If false, log the unauthorized access attempt and optionally render a 403 Forbidden response

Embedding and Testing Restrictions

Once configured, embedding the widget requires placing the script tag on an authorized domain.

Authorized Domain Embedding

Place the following HTML on a page served from an allowed domain:

<div id="pwb-widget-abc123"></div>
<script src="https://your-property-site.com/widget.js"
        data-widget-id="abc123"
        async></script>

The browser sends the parent page's Origin header with the API request, triggering the validation logic.

Handling Unauthorized Requests

Currently, unauthorized requests are logged but not blocked by default. The controller at app/controllers/api_public/v1/widgets_controller.rb includes this safety logging:

Rails.logger.warn "Widget #{ @widget_config.widget_key } accessed from unauthorized domain: #{ domain }"

# render json: { error: 'Origin not allowed' }, status: :forbidden   # Enable to actively block

Uncomment the render line to enforce hard rejection of unauthorized domains.

Automated Testing with RSpec

Verify your domain logic using the model specs in spec/models/pwb/widget_config_spec.rb (lines 206-243):

describe '#domain_allowed?' do
  let(:widget) { create(:pwb_widget_config, allowed_domains: ['example.com', '*.trusted.com']) }

  it { expect(widget.domain_allowed?('example.com')).to be true }
  it { expect(widget.domain_allowed?('sub.trusted.com')).to be true }
  it { expect(widget.domain_allowed?('other.com')).to be false }
end

Summary

  • PropertyWebBuilder stores domain restrictions in the allowed_domains PostgreSQL array column on Pwb::WidgetConfig
  • Admin configuration accepts newline-separated domains in a textarea, converted to an array by site_admin/widgets_controller.rb
  • Validation occurs in api_public/v1/widgets_controller.rb via the validate_origin method before serving widget data
  • Wildcard support allows patterns like *.partner.com to match subdomains automatically
  • Empty arrays implicitly allow all domains, while populated arrays enforce explicit whitelists

Frequently Asked Questions

What database type is required for the allowed_domains array?

The allowed_domains column requires PostgreSQL and is defined as a string[] array type. The migration 20251228100000_create_pwb_widget_configs.rb explicitly uses PostgreSQL array syntax, and db/schema.rb confirms this structure. Other database adapters would require modifying the storage strategy to serialize the list as JSON or a delimited string.

Does PropertyWebBuilder support wildcard domains in restrictions?

Yes, the domain_allowed? method in app/models/pwb/widget_config.rb supports wildcard patterns such as *.example.com. When evaluating a request, the code normalizes the incoming host and checks it against both exact matches and wildcard patterns by stripping the leading *. and verifying subdomain relationships.

How does the widget handle requests from unauthorized domains?

By default, the public API controller logs a warning message identifying the unauthorized domain and widget key, but still serves the request. You can enable active blocking by uncommenting the render json: { error: 'Origin not allowed' }, status: :forbidden line in app/controllers/api_public/v1/widgets_controller.rb to return a 403 status instead.

Can I restrict a widget to multiple specific partner domains?

Yes, the allowed_domains attribute accepts an array of strings, allowing you to whitelist multiple exact domains or wildcard patterns simultaneously. For example, ['example.com', '*.partner-a.com', '*.partner-b.com'] permits embedding on the main site and any subdomain of two different partners.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →