# How to Configure Embeddable Property Widgets with Domain Restrictions in PropertyWebBuilder

> Learn how to configure embeddable property widgets in PropertyWebBuilder and restrict them to specific domains. Secure your widget data by setting allowed domains via the PwbWidgetConfig model.

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

---

**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`](https://github.com/etewiah/property_web_builder/blob/main/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`](https://github.com/etewiah/property_web_builder/blob/main/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`](https://github.com/etewiah/property_web_builder/blob/main/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`](https://github.com/etewiah/property_web_builder/blob/main/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:

```ruby

# 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:

```ruby

# 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`](https://github.com/etewiah/property_web_builder/blob/main/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:

```html
<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`](https://github.com/etewiah/property_web_builder/blob/main/app/controllers/api_public/v1/widgets_controller.rb) includes this safety logging:

```ruby
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`](https://github.com/etewiah/property_web_builder/blob/main/spec/models/pwb/widget_config_spec.rb) (lines 206-243):

```ruby
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`](https://github.com/etewiah/property_web_builder/blob/main/site_admin/widgets_controller.rb)
- **Validation** occurs in [`api_public/v1/widgets_controller.rb`](https://github.com/etewiah/property_web_builder/blob/main/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`](https://github.com/etewiah/property_web_builder/blob/main/20251228100000_create_pwb_widget_configs.rb) explicitly uses PostgreSQL array syntax, and [`db/schema.rb`](https://github.com/etewiah/property_web_builder/blob/main/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`](https://github.com/etewiah/property_web_builder/blob/main/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`](https://github.com/etewiah/property_web_builder/blob/main/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.