Skip to content

Feature Specification: Trademark & Reserved Names Portal

1. Overview

Currently, reserved names (like admin, google, stripe) are stored as a flat JSON array on individual TLD records in the database. This is difficult to manage at scale. The goal of this feature is to build a centralized Trademark Portal. This will move reserved names into a dedicated relational database table, providing a global blocklist across all TLDs, and offering a Web UI for administrators (and eventually brands) to manage these protections.

2. Database Schema Changes (models.py)

2.1 New Table: ReservedName

This table acts as the global or per-TLD blocklist.

class ReservedName(db.Model):
    __tablename__ = 'reserved_names'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False, index=True) # e.g., 'google', 'admin'
    reason = db.Column(db.String(50), default='trademark') # 'trademark', 'system', 'premium'
    is_global = db.Column(db.Boolean, default=True) # If True, blocks across ALL TLDs
    tld_id = db.Column(db.Integer, db.ForeignKey('tlds.id'), nullable=True) # If not global, blocks on specific TLD
    owner_id = db.Column(db.String(36), db.ForeignKey('users.id'), nullable=True) # The verified owner (if claimed)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

2.2 Data Migration Strategy

  • A script will read the existing JSON reserved_names array from the TLD table.
  • It will insert those names into the new ReservedName table with is_global=True.
  • The old JSON column can be deprecated or removed.

3. Core Logic Changes (routes/domains.py)

Update the search and registration logic to query the new ReservedName table instead of the JSON array.

Search/Register Check Logic:

# Check if the SLD exists in the ReservedName table
reserved = ReservedName.query.filter_by(name=sld_name).filter(
    (ReservedName.is_global == True) | (ReservedName.tld_id == tld.id)
).first()

if reserved:
    # Check if the current user is the verified owner of this trademark
    if user and reserved.owner_id == user.id:
        # Allow registration (they own the trademark)
        pass 
    else:
        # Block registration
        flash(f'This domain name is reserved ({reserved.reason}).', 'error')

4. Web UI for Administration

4.1 Admin Dashboard (/admin/trademarks)

Create a new route and template restricted to system administrators (e.g., users with an is_admin flag or specific user IDs). - View: A paginated table of all reserved names, showing Name, Reason, Scope (Global vs Specific TLD), and Owner (if claimed). - Add: A form to manually add a single reserved name or bulk-add via comma-separated list. - Edit/Delete: Buttons to remove a block or assign an owner_id to a specific brand account.

5. Phase 2: Brand Self-Service Portal (Future Scope)

(Note: To be built after the Admin UI is stable) - Claim Route (/claim-trademark): A brand can search for their blocked name and click "Claim". - Verification: The system generates a unique TXT record (e.g., headless-verify=12345). The user must place this on their existing .com domain. - Automated Unlock: Once the system verifies the TXT record, it automatically updates the owner_id in the ReservedName table, allowing the brand to register their domains across the ecosystem.

6. Implementation Steps for MVP

  1. Models: Create the ReservedName table and migration script. (Completed)
  2. Logic Update: Update the search/register endpoints to query the new table. (Completed)
  3. Admin UI: Build a secure admin page (/admin/trademarks) to view, add, and delete records from this table.
  4. CSV Upload: Add a feature to the Admin UI to allow the admin to upload a CSV file (e.g., the Tranco Top 10k list) and bulk-insert them into the ReservedName table.