Skip to content

Code Examples

Last updated: 2026-03-22

Practical examples for common use cases with Filament Address Pro.

Table of Contents


Basic Usage

Adding Addresses to a User Model

php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Viewflex\FilamentAddress\Concerns\HasAddresses;

class User extends Authenticatable
{
    use HasAddresses;

    // The trait provides:
    // - addresses() relationship
    // - primaryAddress() method
    // - setPrimaryAddress($address) method
}

Using the Relation Manager in a Resource

php
<?php

namespace App\Filament\Resources;

use App\Models\User;
use Filament\Resources\Resource;
use Viewflex\FilamentAddress\Filament\RelationManagers\AddressesRelationManager;

class UserResource extends Resource
{
    protected static ?string $model = User::class;

    public static function getRelations(): array
    {
        return [
            AddressesRelationManager::class,
        ];
    }

    // ... rest of resource
}

Creating an Address Manually

php
use App\Models\User;

$user = User::find(1);

$address = $user->addresses()->create([
    'country_code' => 'US',
    'address_line_1' => '1600 Pennsylvania Avenue NW',
    'locality' => 'Washington',
    'administrative_area' => 'DC',
    'postal_code' => '20500',
    'is_primary' => true,
]);

Working with Multiple Models

Customer Model with Addresses

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Viewflex\FilamentAddress\Concerns\HasAddresses;

class Customer extends Model
{
    use HasAddresses;

    protected $fillable = [
        'name',
        'email',
        'phone',
    ];

    /**
     * Get the primary shipping address.
     */
    public function shippingAddress()
    {
        return $this->primaryAddress();
    }

    /**
     * Get the billing address (if different from shipping).
     */
    public function billingAddress()
    {
        return $this->addresses()
            ->where('address_type', 'billing')
            ->first();
    }
}

Company Model with Multiple Locations

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Viewflex\FilamentAddress\Concerns\HasAddresses;

class Company extends Model
{
    use HasAddresses;

    protected $fillable = [
        'name',
        'registration_number',
        'industry',
    ];

    /**
     * Get the headquarters address.
     */
    public function headquarters()
    {
        return $this->addresses()
            ->where('is_primary', true)
            ->first();
    }

    /**
     * Get all office locations.
     */
    public function offices()
    {
        return $this->addresses()
            ->where('address_type', 'office')
            ->get();
    }

    /**
     * Get warehouses.
     */
    public function warehouses()
    {
        return $this->addresses()
            ->where('address_type', 'warehouse')
            ->get();
    }
}

Property Listing with Location

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Viewflex\FilamentAddress\Concerns\HasAddresses;

class Property extends Model
{
    use HasAddresses;

    protected $fillable = [
        'title',
        'description',
        'price',
        'bedrooms',
        'bathrooms',
    ];

    protected $casts = [
        'price' => 'decimal:2',
    ];

    /**
     * Get the property address.
     */
    public function location()
    {
        return $this->addresses()->first();
    }

    /**
     * Scope properties near coordinates.
     */
    public function scopeNearby($query, float $lat, float $lon, int $radiusKm = 10)
    {
        // Haversine formula for distance calculation
        return $query->whereHas('addresses', function ($q) use ($lat, $lon, $radiusKm) {
            $q->selectRaw("
                *, (6371 * acos(cos(radians(?))
                * cos(radians(lat))
                * cos(radians(lon) - radians(?))
                + sin(radians(?))
                * sin(radians(lat)))) AS distance
            ", [$lat, $lon, $lat])
            ->havingRaw("distance < ?", [$radiusKm]);
        });
    }
}

Custom Forms

Inline Address Form in a Page

php
<?php

namespace App\Filament\Pages;

use Filament\Forms;
use Filament\Forms\Form;
use Filament\Pages\Page;
use Viewflex\FilamentAddress\Filament\Schemas\AddressForm;

class CreateShipment extends Page
{
    protected static string $view = 'filament.pages.create-shipment';

    public ?array $data = [];

    public function mount(): void
    {
        $this->form->fill();
    }

    public function form(Form $form): Form
    {
        return $form
            ->schema([
                Forms\Components\Section::make('Shipment Details')
                    ->schema([
                        Forms\Components\TextInput::make('tracking_number')
                            ->required(),
                        Forms\Components\DatePicker::make('ship_date')
                            ->required(),
                    ]),

                Forms\Components\Section::make('Destination Address')
                    ->description('Where should we ship this package?')
                    ->schema(AddressForm::schema())
                    ->columns(2),
            ])
            ->statePath('data');
    }

    public function create(): void
    {
        $data = $this->form->getState();

        // Process shipment with address data
        // $data contains all address fields from AddressForm
    }
}

Address Form in an Action Modal

Drop the full address form - map search, dynamic dropdowns - into any Filament Action modal with a single spread:

php
<?php

namespace App\Filament\Pages;

use Filament\Actions\Action;
use Filament\Pages\Page;
use Viewflex\FilamentAddress\Concerns\HandlesMapSelection;
use Viewflex\FilamentAddress\Filament\Schemas\AddressForm;

class CustomerProfile extends Page
{
    use HandlesMapSelection;

    // Required for Action forms with live updates (map selection, dynamic dropdowns)
    public array $data = [];

    protected string $view = 'filament.pages.customer-profile';

    protected function getActions(): array
    {
        return [
            Action::make('addAddress')
                ->label('Add Address')
                ->icon('heroicon-o-plus')
                ->color('primary')
                ->modalHeading('Add Address')
                ->modalWidth('5xl')
                ->form([
                    ...AddressForm::schema(),
                ])
                ->action(function (array $data) {
                    $this->record->addresses()->create($data);
                }),
        ];
    }
}

HandlesMapSelection automatically routes map place selections into the active modal form, no extra wiring needed.


Custom Address Form with Additional Fields

php
<?php

namespace App\Filament\Forms;

use Viewflex\FilamentAddress\Filament\Schemas\AddressForm as BaseAddressForm;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\Section;

class ExtendedAddressForm extends BaseAddressForm
{
    public static function schema(): array
    {
        $baseSchema = parent::schema();

        // Add custom fields at the end
        $baseSchema[] = Section::make('Additional Information')
            ->schema([
                TextInput::make('delivery_contact_name')
                    ->label('Contact Name')
                    ->helperText('Person to contact for delivery')
                    ->maxLength(255),

                TextInput::make('delivery_contact_phone')
                    ->label('Contact Phone')
                    ->tel()
                    ->maxLength(50),

                Textarea::make('delivery_instructions')
                    ->label('Delivery Instructions')
                    ->helperText('Gate codes, special instructions, etc.')
                    ->maxLength(500)
                    ->rows(3)
                    ->columnSpanFull(),

                Toggle::make('is_residential')
                    ->label('Residential Address')
                    ->default(true)
                    ->helperText('Uncheck for commercial addresses'),

                Toggle::make('is_po_box')
                    ->label('PO Box Address')
                    ->helperText('Check if this is a PO Box'),
            ])
            ->columns(2);

        return $baseSchema;
    }
}

Programmatic Address Management

Creating Addresses from API Data

php
use App\Models\Customer;
use Viewflex\FilamentAddress\Services\GeocodingService;

class AddressImportService
{
    public function __construct(
        private GeocodingService $geocoder
    ) {}

    public function importFromApi(array $customerData): void
    {
        $customer = Customer::create([
            'name' => $customerData['name'],
            'email' => $customerData['email'],
        ]);

        // Import addresses
        foreach ($customerData['addresses'] as $addressData) {
            $this->createAddress($customer, $addressData);
        }
    }

    private function createAddress(Customer $customer, array $data): void
    {
        // Geocode if coordinates not provided
        if (empty($data['lat']) || empty($data['lon'])) {
            $fullAddress = "{$data['street']}, {$data['city']}, {$data['state']} {$data['zip']}";
            $result = $this->geocoder->geocode($fullAddress, $data['country_code']);

            if ($result['success']) {
                $data = array_merge($data, $result['components']);
                $data['lat'] = $result['lat'];
                $data['lon'] = $result['lon'];
            }
        }

        $customer->addresses()->create([
            'country_code' => $data['country_code'],
            'address_line_1' => $data['address_line_1'] ?? $data['street'],
            'locality' => $data['city'],
            'administrative_area' => $data['state'],
            'postal_code' => $data['zip'],
            'lat' => $data['lat'] ?? null,
            'lon' => $data['lon'] ?? null,
            'is_primary' => $data['is_primary'] ?? false,
        ]);
    }
}

Setting Primary Address with Validation

php
use App\Models\Customer;

class AddressService
{
    public function setPrimaryAddress(Customer $customer, int $addressId): bool
    {
        $address = $customer->addresses()->find($addressId);

        if (!$address) {
            return false;
        }

        // Unset current primary
        $customer->addresses()->update(['is_primary' => false]);

        // Set new primary
        $address->update(['is_primary' => true]);

        return true;
    }

    public function ensureHasPrimary(Customer $customer): void
    {
        // If no primary address, make the first one primary
        if (!$customer->primaryAddress() && $customer->addresses()->count() > 0) {
            $customer->addresses()->first()->update(['is_primary' => true]);
        }
    }
}

Bulk Address Updates

php
use App\Models\Address;
use Viewflex\FilamentAddress\Models\CountrySubdivision;

class SubdivisionMigrationService
{
    /**
     * Migrate addresses from text-based subdivisions to ID-based.
     */
    public function migrateToSubdivisionIds(): void
    {
        Address::whereNotNull('administrative_area')
            ->whereNull('administrative_area_id')
            ->chunk(100, function ($addresses) {
                foreach ($addresses as $address) {
                    $this->linkSubdivision($address);
                }
            });
    }

    private function linkSubdivision(Address $address): void
    {
        // Find subdivision by name
        $subdivision = CountrySubdivision::where('country_code', $address->country_code)
            ->where('name', $address->administrative_area)
            ->where('level', 1)
            ->first();

        if ($subdivision) {
            $address->update([
                'administrative_area_id' => $subdivision->id,
            ]);
        }
    }
}

Address Verification

Manual Verification

php
use Viewflex\FilamentAddress\Services\Verification\AddressVerificationService;

class ManualVerificationExample
{
    public function __construct(
        private AddressVerificationService $verifier
    ) {}

    public function verifyAddress(array $addressData): array
    {
        $result = $this->verifier->verify([
            'address_line_1' => $addressData['street'],
            'locality' => $addressData['city'],
            'administrative_area' => $addressData['state'],
            'postal_code' => $addressData['zip'],
            'country_code' => 'US',
        ]);

        if ($result->isSuccessful()) {
            return [
                'verified' => true,
                'provider' => $result->getProvider(),
                'address_line_1' => $result->getAddressLine1(),
                'city' => $result->getCity(),
                'state' => $result->getState(),
                'postal_code' => $result->getPostalCode(),
                'metadata' => $result->getMetadata(),
            ];
        }

        return [
            'verified' => false,
            'error' => $result->getError(),
        ];
    }
}

Automatic Verification on Create

php
use App\Models\Address;
use Viewflex\FilamentAddress\Services\Verification\AddressVerificationService;

class AddressObserver
{
    public function __construct(
        private AddressVerificationService $verifier
    ) {}

    public function created(Address $address): void
    {
        // Only verify if enabled in config
        if (!config('addresses.verification.enabled')) {
            return;
        }

        $result = $this->verifier->verify([
            'address_line_1' => $address->address_line_1,
            'address_line_2' => $address->address_line_2,
            'locality' => $address->locality,
            'administrative_area' => $address->administrative_area,
            'postal_code' => $address->postal_code,
            'country_code' => $address->country_code,
        ]);

        if ($result->isSuccessful()) {
            $address->markAsVerified($result->getProvider(), $result->getMetadata());

            // Optionally update with verified data
            $address->update([
                'address_line_1' => $result->getAddressLine1() ?? $address->address_line_1,
                'locality' => $result->getCity() ?? $address->locality,
                'administrative_area' => $result->getState() ?? $address->administrative_area,
                'postal_code' => $result->getPostalCode() ?? $address->postal_code,
            ]);
        }
    }
}

// Register in AppServiceProvider
use App\Models\Address;
use App\Observers\AddressObserver;

public function boot(): void
{
    Address::observe(AddressObserver::class);
}

Batch Verification

php
use App\Models\Address;
use Viewflex\FilamentAddress\Services\Verification\AddressVerificationService;

class BatchVerificationCommand extends Command
{
    protected $signature = 'addresses:verify {--country=US}';
    protected $description = 'Verify unverified addresses';

    public function handle(AddressVerificationService $verifier): void
    {
        $country = $this->option('country');

        $addresses = Address::unverified()
            ->where('country_code', $country)
            ->get();

        $this->info("Verifying {$addresses->count()} addresses...");

        $verified = 0;
        $failed = 0;

        foreach ($addresses as $address) {
            $result = $verifier->verify([
                'address_line_1' => $address->address_line_1,
                'locality' => $address->locality,
                'administrative_area' => $address->administrative_area,
                'postal_code' => $address->postal_code,
                'country_code' => $address->country_code,
            ]);

            if ($result->isSuccessful()) {
                $address->markAsVerified($result->getProvider(), $result->getMetadata());
                $verified++;
                $this->line("✓ Address {$address->id} verified");
            } else {
                $failed++;
                $this->warn("✗ Address {$address->id} failed: {$result->getError()}");
            }
        }

        $this->info("Complete: {$verified} verified, {$failed} failed");
    }
}

Geocoding Integration

Forward Geocoding (Address → Coordinates)

php
use Viewflex\FilamentAddress\Services\GeocodingService;

class PropertyGeocoder
{
    public function __construct(
        private GeocodingService $geocoder
    ) {}

    public function geocodeProperty(Property $property): void
    {
        $address = $property->location();
        if (!$address) {
            return;
        }

        $fullAddress = implode(', ', array_filter([
            $address->address_line_1,
            $address->locality,
            $address->administrative_area,
            $address->postal_code,
        ]));

        $result = $this->geocoder->geocode($fullAddress, $address->country_code);

        if ($result['success']) {
            $address->update([
                'lat' => $result['lat'],
                'lon' => $result['lon'],
            ]);

            // Optionally update with verified components
            if ($result['components']['is_verified']) {
                $address->update([
                    'is_verified' => true,
                    'verification_provider' => $result['components']['verification_provider'],
                    'verified_at' => $result['components']['verified_at'],
                ]);
            }
        }
    }
}

Reverse Geocoding (Coordinates → Address)

php
use Viewflex\FilamentAddress\Services\GeocodingService;

class LocationService
{
    public function __construct(
        private GeocodingService $geocoder
    ) {}

    public function createAddressFromCoordinates(
        Model $model,
        float $lat,
        float $lon
    ): ?Address {
        $result = $this->geocoder->reverseGeocode($lat, $lon);

        if (!$result['success']) {
            return null;
        }

        $components = $result['components'];

        return $model->addresses()->create([
            'country_code' => $components['country_code'],
            'address_line_1' => $components['address_line_1'],
            'locality' => $components['city'],
            'administrative_area' => $components['state'],
            'postal_code' => $components['postal_code'],
            'lat' => $lat,
            'lon' => $lon,
            'is_verified' => $components['is_verified'],
            'verification_provider' => $components['verification_provider'],
        ]);
    }
}

Finding Nearby Locations

php
use App\Models\Store;

class StoreLocatorService
{
    public function findNearestStore(float $lat, float $lon, int $limit = 5): Collection
    {
        return Store::select('stores.*')
            ->join('addresses', function ($join) {
                $join->on('addresses.addressable_id', '=', 'stores.id')
                    ->where('addresses.addressable_type', Store::class);
            })
            ->selectRaw("
                (6371 * acos(
                    cos(radians(?))
                    * cos(radians(addresses.lat))
                    * cos(radians(addresses.lon) - radians(?))
                    + sin(radians(?))
                    * sin(radians(addresses.lat))
                )) AS distance
            ", [$lat, $lon, $lat])
            ->whereNotNull('addresses.lat')
            ->whereNotNull('addresses.lon')
            ->orderBy('distance')
            ->limit($limit)
            ->get();
    }
}

International Addresses

Japanese Address Example

php
use App\Models\Customer;

$customer = Customer::find(1);

$customer->addresses()->create([
    'country_code' => 'JP',
    'address_line_1' => '4-chōme-2-8 Shibakōen',  // Premise number + street
    'dependent_locality' => 'Shibakōen',          // Sublocality level 1
    'locality' => 'Minato City',                   // City
    'administrative_area' => 'Tokyo',              // Prefecture
    'postal_code' => '105-0011',
    'is_primary' => true,
]);

South Korean Address Example

php
use App\Models\Company;

$company = Company::find(1);

$company->addresses()->create([
    'country_code' => 'KR',
    'address_line_1' => '123',                     // Premise
    'dependent_locality' => 'Myeong-dong',         // Sublocality level 4
    'locality' => 'Jung-gu',                       // Sublocality level 3
    'administrative_area' => 'Seoul',              // City
    'postal_code' => '04536',
    'is_primary' => true,
]);

UK Address Example

php
use App\Models\User;

$user = User::find(1);

$user->addresses()->create([
    'country_code' => 'GB',
    'address_line_1' => '10 Downing Street',
    'locality' => 'London',
    'postal_code' => 'SW1A 2AA',
    'is_primary' => true,
]);

Canadian Address Example (Bilingual)

php
use App\Models\Customer;

$customer = Customer::find(1);

$customer->addresses()->create([
    'country_code' => 'CA',
    'address_line_1' => '111 Wellington Street',
    'locality' => 'Ottawa',
    'administrative_area' => 'ON',  // Ontario
    'postal_code' => 'K1A 0A6',
    'is_primary' => true,
]);

Advanced Patterns

Address Factory for Testing

php
<?php

namespace Database\Factories;

use App\Models\Address;
use Illuminate\Database\Eloquent\Factories\Factory;

class AddressFactory extends Factory
{
    protected $model = Address::class;

    public function definition(): array
    {
        return [
            'country_code' => 'US',
            'address_line_1' => $this->faker->streetAddress(),
            'locality' => $this->faker->city(),
            'administrative_area' => $this->faker->stateAbbr(),
            'postal_code' => $this->faker->postcode(),
            'lat' => $this->faker->latitude(),
            'lon' => $this->faker->longitude(),
            'is_primary' => false,
        ];
    }

    public function primary(): static
    {
        return $this->state(fn (array $attributes) => [
            'is_primary' => true,
        ]);
    }

    public function verified(): static
    {
        return $this->state(fn (array $attributes) => [
            'is_verified' => true,
            'verification_provider' => 'usps',
            'verified_at' => now(),
        ]);
    }

    public function japanese(): static
    {
        return $this->state(fn (array $attributes) => [
            'country_code' => 'JP',
            'address_line_1' => '4-chōme-2-8 Shibakōen',
            'dependent_locality' => 'Shibakōen',
            'locality' => 'Minato City',
            'administrative_area' => 'Tokyo',
            'postal_code' => '105-0011',
        ]);
    }
}

Repository Pattern

php
<?php

namespace App\Repositories;

use App\Models\Address;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;

class AddressRepository
{
    public function findByModel(Model $model): Collection
    {
        return $model->addresses;
    }

    public function getPrimaryAddress(Model $model): ?Address
    {
        return $model->primaryAddress();
    }

    public function create(Model $model, array $data): Address
    {
        return $model->addresses()->create($data);
    }

    public function update(Address $address, array $data): bool
    {
        return $address->update($data);
    }

    public function delete(Address $address): bool
    {
        return $address->delete();
    }

    public function setPrimary(Address $address): bool
    {
        // Unset other primary addresses
        $address->addressable->addresses()->update(['is_primary' => false]);

        return $address->update(['is_primary' => true]);
    }

    public function findVerified(Model $model): Collection
    {
        return $model->addresses()->verified()->get();
    }

    public function findUnverified(Model $model): Collection
    {
        return $model->addresses()->unverified()->get();
    }
}

Event-Driven Address Updates

php
<?php

namespace App\Events;

use App\Models\Address;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class AddressVerified
{
    use Dispatchable, SerializesModels;

    public function __construct(
        public Address $address,
        public string $provider,
        public array $metadata
    ) {}
}

// Listener
namespace App\Listeners;

use App\Events\AddressVerified;
use Illuminate\Support\Facades\Notification;
use App\Notifications\AddressVerificationComplete;

class NotifyAddressVerification
{
    public function handle(AddressVerified $event): void
    {
        if ($event->address->addressable instanceof User) {
            $event->address->addressable->notify(
                new AddressVerificationComplete($event->address)
            );
        }
    }
}

Custom Address Management Resource

The package ships AddressResource as part of getResources() - a fully functional standalone table listing all addresses across all entities, with search, filters, and import/export. Register it with two lines in your panel provider (see PANEL-SETUP.md).

If you need a customized version - different navigation group, app-specific entity type labels, extra columns, or a trimmed-down filter set - here's a starting point to build your own:

php
<?php

namespace App\Filament\Resources;

use App\Filament\Resources\AddressResource\Pages;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Filters\Filter;
use Filament\Tables\Actions\EditAction;
use Filament\Tables\Actions\DeleteAction;
use Illuminate\Database\Eloquent\Builder;
use Viewflex\FilamentAddress\Models\Address;
use Viewflex\FilamentAddress\Models\Country;

class AddressResource extends Resource
{
    protected static ?string $model = Address::class;
    protected static ?string $navigationIcon = 'heroicon-o-map-pin';
    protected static ?string $navigationGroup = 'Addressing';

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                TextColumn::make('addressable_type')
                    ->label('Entity Type')
                    ->badge()
                    ->formatStateUsing(fn ($state) => class_basename($state)),

                TextColumn::make('addressable.name')
                    ->label('Owner')
                    ->searchable(),

                TextColumn::make('address_line_1')
                    ->searchable()
                    ->limit(30),

                TextColumn::make('locality')
                    ->label('City')
                    ->searchable(),

                TextColumn::make('administrative_area')
                    ->label('State/Province')
                    ->toggleable(),

                TextColumn::make('country_code')
                    ->badge()
                    ->color('primary'),

                IconColumn::make('is_verified')
                    ->boolean()
                    ->trueIcon('heroicon-o-shield-check')
                    ->falseIcon('heroicon-o-x-circle')
                    ->trueColor('success')
                    ->falseColor('danger'),

                TextColumn::make('verification_provider')
                    ->badge()
                    ->color(fn ($state) => match($state) {
                        'usps' => 'success',
                        'google' => 'info',
                        default => 'gray',
                    })
                    ->toggleable(),

                IconColumn::make('is_primary')
                    ->boolean()
                    ->trueColor('warning')
                    ->toggleable(),

                TextColumn::make('created_at')
                    ->dateTime()
                    ->toggleable(isToggledHiddenByDefault: true),
            ])
            ->filters([
                SelectFilter::make('country_code')
                    ->options(Country::pluck('name', 'country_code'))
                    ->searchable(),

                SelectFilter::make('is_verified')
                    ->label('Verification Status')
                    ->options([
                        '1' => 'Verified',
                        '0' => 'Unverified',
                    ]),

                SelectFilter::make('verification_provider')
                    ->options([
                        'usps' => 'USPS',
                        'google' => 'Google',
                    ]),

                SelectFilter::make('addressable_type')
                    ->label('Entity Type')
                    ->options([
                        'App\\Models\\User' => 'Users',
                        'App\\Models\\Customer' => 'Customers',
                        // Add your own model types here
                    ]),

                Filter::make('is_primary')
                    ->query(fn (Builder $query) => $query->where('is_primary', true))
                    ->label('Primary Only'),
            ])
            ->actions([
                EditAction::make(),
                DeleteAction::make(),
            ]);
    }

    public static function getPages(): array
    {
        return [
            'index' => Pages\ListAddresses::route('/'),
            'edit' => Pages\EditAddresses::route('/{record}/edit'),
        ];
    }
}

Create the corresponding page classes with php artisan make:filament-resource Address --generate then remove the auto-generated form/table and replace with the above, or build the page classes manually.


Next Steps

Released under a commercial license.