Car Lot Website Design is coming soon. Join the wait list for early-access pricing.
CarLots – Better dealer websites. Made simple. Help Wait list

Developer guide

How the Car Lots theme, plugins, data and rules fit together. Section 13 ("Full system rules") wins wherever an older section disagrees.

Source: docs/Developer build spec.md (this page is built from it). For dealer-facing steps see the Dealer guide.

Product: Car Lots (final name, Nov 7). Status: in build – plugin 0.29.48, theme 0.29.x, Feed Importer 1.3.12, Feed Export 1.2.1, Cloud Images 0.2.8.

Source of truth: the approved design files in this project (each page's Notes box has the details) and CLAUDE.md (project rules). This spec ties them together.

1. Scope

  • Theme (WordPress, block theme with server-rendered blocks) + plugin "Car Lots" (setup wizard, Site settings, Forms & leads, Add vehicle, lead/ADF emails, tracking).
  • Cars only. Sold to many dealers; every dealer-specific value comes from settings, never hard-coded.
  • Not in this project: Cloud Images plugin / Cloudflare / hosted mode (separate project), spreadsheet import (on hold: docs/On hold - spreadsheet import.md).

2. Page and part map

Design fileBuilds into
Style GuideTheme tokens: colors A–D, grays, font, radius, buttons, links, focus, icons, type scale
Header A–DHeader template parts (one active, picked in Look & style)
Footer A–DFooter template parts (one active)
Card A–FVehicle card block/partial (one active, picked in Look & style). E: savings line "You save $X" + solid Main payment pill. F: savings strip, payment + down line, Stock# + More details
Filters A–CInventory filter sidebar / drawer (one active)
Pagination A (+ Results Bar A)Inventory archive: results bar, grid, pager
Vehicle Page A–CSingle vehicle template (one active, picked in Look & style). B = Vehicle Page B Part: 10b gallery – desktop thumbnails rail left (104px) + photo, both match the price box height; tablet 5 thumbs under photo; phone swipe + scroll thumbs; no border/padding around Highlights/specs. C = Vehicle Page C Part: 10a mosaic (main + 3 stacked) + dark theater viewer with zoom and thumbnail rail
Hero A–CHomepage hero (one active, picked in Look & style). A = Homepage Hero Part (variant b), B = Homepage Hero Budget Part (search card on photo), C = Homepage Hero C Part (photo/video + search bar below; settings: hero media, video link, video end)
Compare B (A = backup)Compare page
FavoritesFavorites page
Homepage A (+ Hero, Makes, Finance, Blocks parts)Front page (website page, content-locked)
About A, Contact A, Locations AWebsite pages (content-locked)
Finance A, Car Finder AWebsite pages with lead forms (/finance/, /car-finder/)
Privacy Policy, Terms of Use, Terms and Conditions (Legal A Part)Legal pages (/privacy-policy/, /terms-of-use/, /terms-and-conditions/)
Content Editor BHow pages are edited (block editor rules, below)
Setup Wizard APlugin setup wizard (Car Lots > Setup)
Site Settings APlugin settings (Car Lots > Site settings)
Forms APlugin Forms & leads (Car Lots > Forms & leads)
Add Vehicle AStaff add/edit vehicle screen (phone + desktop)
Homepage B, Homepage CDrafts for a later revision (not in first build)

3. Design-to-code conventions

  • Colors: only CSS variables --color-a (main), --color-b (accent), --color-c (dark/text), --color-d (light/background) plus grays --gray-50…--gray-700. Never a raw brand hex in templates. Demo values: A #CF0D0D, B #A10A0A, C #231F20, D #FFFFFF.
  • Part-level overrides: each part also reads its own variables with a fallback (e.g. var(--ft-bottom-text, var(--gray-300))) so Look & style can recolor one area without touching the brand colors.
  • Labels in the designs map to code:
    • data-role="…": which color role an element uses (roles listed in CLAUDE.md and each page's Notes).
    • data-part="…": a piece that can be turned on/off (setting or block toggle).
    • data-text="…": an editable text field (block attribute with a character limit).
    • data-link="…": a link that points to a site page (privacy, terms, conditions, finance, car-finder…).
  • Font: one of Inter, Roboto, Open Sans, Lato, Montserrat, Poppins, Oswald, Source Sans 3 (Look & style). Corners: theme-wide radius setting (Sharp / Soft / Round).
  • Breakpoints by container width (not viewport): <768 phone; 768–1023 tablet (phone-style menu, filter drawer, 2 cards/row); ≥1024 desktop (desktop menu, filter sidebar, 3 cards/row). Use container queries.
  • Accessibility: real buttons/links, visible focus ring (focus-ring role [A], on dark [D]), labeled fields, 44px+ tap targets on phone/tablet, text contrast 4.5:1.
  • Tech: plain HTML/CSS, minimal JS, no frameworks on the public site, no base64 images.

4. Dealer data (settings → site)

All dealer info lives in plugin settings and is rendered everywhere it's used (header, footer, Contact, Locations, forms, legal pages, emails, ADF vendor block).

  • Store info: dealer name, main phone, website, logo + white logo, locations (name, address, phone, sales/service/parts phones, photo).
  • Hours per location and department (Sales / Service / Parts), "today's hours" shown on site.
  • Service: on/off (off hides every service block and "Schedule service" button), phone, scheduling link, photo.
  • Reviews: rating, count, link, up to 3 reviews. Empty = reviews sections hidden.
  • Analytics: GA4 ID, GTM ID (both optional; dashboard reminder while empty).
  • Look & style: header/footer/card/filters choice, colors A–D, font, corners.
  • Social links (6 networks; empty = icon hidden), Team (empty = About team section hidden), Header & footer switches (top bar, header phone, search, contact button, newsletter, social icons, footer hours), Photo storage (image base address, admins only).
  • Placeholders used in designs: dealer_name, (555) 555-0123, 123 Main Street, City, ST 00000. Phone numbers render with dashes; tel: links strip formatting.

5. Setup wizard and Site settings

  • Setup Wizard A (Car Lots > Setup, opens after activation): 9 steps: Get started, Store info, Hours, Leads & CRM, Service, Reviews, Analytics, Look & style, Review & finish. Saves as you go; "Save and exit".
    • Step 4 goes form by form (Vehicle page → Financing → Car Finder → Contact us → Your CRM). "Do you use a CRM?" Yes/No. Per form: recipients + "Also send to CRM?" (Vehicle page, Car Finder only). Last screen: CRM name, ADF email, extra ADF addresses (Zapier etc., up to 3), shopper confirmation on/off, Send a test lead.
    • Skipped optional steps (Reviews, Analytics) show "Skipped · add later"; finish screen and dashboard remind until filled.
    • "Needs a look": missing name/phone, a form with no recipients, CRM on without name/ADF email, GA ID in wrong format.
  • Site Settings A (Car Lots > Site settings): same fields as the wizard (shared code) + Social links, Team, Header & footer switches, Photo storage. Every section shows "Shows on: …". Save bar: Unsaved changes / Undo / Save changes.

6. Page editing (Content Editor B)

  • Website pages (Homepage, About, Contact, Locations, Financing, Car Finder, legal pages): block templates with templateLock: "contentOnly". Dealers change text and images and hide optional sections; they cannot add, move or delete blocks.
  • Your pages (Specials, Careers…): normal pages; inserter limited to Car Lots blocks (Banner, Text and photo, Vehicles, Reviews, Contact form) via allowed_block_types_all.
  • Header/footer, dealer info, vehicles and form questions are not editable in pages (locked, link to Site settings / inventory / Forms).
  • Every text field has a character limit (from the designs). Blocks are server-rendered; no page-builder code on the public site.

7. Forms, leads, ADF and tracking (Forms A)

FormLead alert emailCRM (ADF)GA4 event
Vehicle page formYesYesproduct_submission + inquiry_topic
Car FinderYesYes (placeholder vehicle)form_submission, form_name: car_finder
Financing (credit app)YesNoform_submission, form_name: credit_app
Contact usYesNoform_submission, form_name: contact
  • Every submission: lead alert to recipients set for that form (any number; each set to All forms or specific forms), shopper confirmation (on by default), ADF to CRM + extra ADF addresses where enabled. Phase 2: later follow-up email to shopper.
  • Form questions: name, email, phone and vehicle details always on; first/last name and email always required; other questions have Show and Required switches; up to 3 custom questions per form (answers go to lead alert + ADF comments).
  • ADF 1.0 (checked against the spec/DTD at adfxml.info): tags in DTD order; prospect <id sequence="1" source="{dealer} website"> lead ID, requestdate ISO 8601; vehicle interest = test-drive for test drive topic else buy, status new/used, year, make, model, vin, stock, trim, bodystyle, odometer (units mi), price type asking USD; trade-in = second vehicle interest="trade-in"; customer contact first/last name, email + phone (cellphone) with preferredcontact; vendor = location (id slug, vendorname, contact with address, country US); provider = Car Lots, service = source + form + topic, url, email. Co-buyer in comments (one customer per lead in ADF 1.0).
  • ADF comments: always "Shopper answers: Question: answer | …" for EVERY answer in form order (even if also in tags), then "Tracking: lead_id=…; utm_source=…; utm_medium=…; utm_campaign=…; gclid=…; ga_client_id=…; landing_page=…; form_page=…".
  • Car Finder placeholder vehicle: year = newest picked or 0000 (Any); make = picked or CARFINDER; model = picked or REQUEST; VIN CARFINDER00000000; stock CAR-FINDER; budget in <finance>; timeframe in <timeframe>.
  • Lead ID: DB-YYYYMMDD-NNNN, sent in ADF and as lead_id on the GA4 event. UTM/gclid/client_id/landing page stored first-touch for 30 days; nothing stored if cookie consent is declined.
  • click_to_call GA4 event on every tel: link with a location value per placement (header, footer, contact, vehicle page, compare notice…; prefixed tablet_/phone_ where the design notes say so).
  • Test values and checks: docs/Lead and ADF testing checklist.md.
  • Built (plugin 0.9.0): includes/forms.php = Forms & leads settings + lead engine + ADF; AJAX actions dealerbase_contact_lead, dealerbase_finance_lead, dealerbase_finder_lead, dealerbase_vehicle_lead. Theme: window.DBLead.send() in site.js adds tracking + fires the GA event with lead_id.

8. Add vehicle (Add Vehicle A)

  • Roles: Sales Manager (new role to create) and Administrator only. Phone-first screen; 16px inputs; 44px+ targets.
  • Photos first (details in "Vehicle images" below): camera + library on touch devices / under 1200px, drag-drop + Browse on desktop; drag to reorder (mouse on photo, finger on handle, keyboard arrows on handle); Feed / Added tags.
  • VIN typed (17 chars, no I/O/Q) → "Look up" calls NHTSA vPIC DecodeVinValues/{VIN}?format=json and replaces year, make, model, trim (Trim + Series), body, drivetrain, transmission, fuel, engine. Boxes the decoder can't fill and empty required boxes are highlighted with a count banner.
  • Stock # under the VIN (required, uppercase). Price/mileage/year digits only.
  • Publish requires: photo, VIN, year, make, model, trim, stock, price, mileage. Price-only missing → "No price yet: Add a price / Save as draft". Done screen: Vehicle published / Draft saved / Changes saved.
  • Feed vehicles: feed fields locked ("From feed"); staff can add photos, features, description, store, status.
  • Second revision: "Scan VIN" camera barcode button (space reserved next to Look up).

9. Inventory, vehicle page, compare, favorites

  • Inventory archive: results bar (title, count, Favorites, Sort, quick links), grid using the active card, pager (numbers or load more). First row of cards loads eagerly.
  • Vehicle page: gallery + full-screen viewer, sticky price bar on desktop, contact form with topics (Ask a question, Check availability, Schedule a test drive, I have a trade-in, Ask about financing), sent-form confirmation popup with similar vehicles (3 desktop / 2 tablet / 2 phone).
  • Compare: up to 4 vehicles; win tags; only-differences toggle; "Not listed" for missing data with contact notice; vehicles not being compared listed below.
  • Favorites: saved on the device; "All vehicles" back link.

10. Vehicle images (Cloud Images readiness – Section 1 v2)

Source: uploads/Section1_Cloud_Images_Theme_Readiness_v2.md. Design version of the helper: vehicle-images.js.

One helper, everywhere

  • Templates get vehicle photo addresses ONLY from dealerbase_vehicle_image( $vehicle_id, $index, $size ) and dealerbase_vehicle_images( $vehicle_id, $size ).
  • Applies to: Cards B, C, F (D on hold), Vehicle Page (gallery, thumbnails, sticky bar, sent-form popup, similar vehicles, full-screen viewer), sliders, inventory grid (Pagination A), Favorites, Compare A/B, Finance, lead + shopper emails, feeds, social kit.
  • No template reads image meta or wp_get_attachment_* directly. Vehicle photos are not Media Library attachments.
  • PHP helpers are written in the build phase (not part of the design files).

Sizes

SizeWidthFormatUsed for
thumb~400pxWebPcards, thumbnails
card~800pxWebPcards on larger screens, gallery side photos, photo grid
full~1600pxWebPVehicle Page main photo, full-screen viewer
share~1200pxJPGemails (lead alert + shopper confirmation), ad/marketplace feeds, social posts
  • Web sizes output srcset (thumb/card/full) and sizes. share is a single JPG, no srcset.
  • Emails, feeds and the social kit ALWAYS request share, never WebP (Outlook desktop and some feeds don't show WebP).

Photo list (post meta, one ordered list per vehicle)

{ key, sizes: { thumb, card, full, share }, alt, width, height, source_url, source_timestamp, sort, source, hidden, nn }

  • source: feed | manual. hidden: true/false (helper skips hidden photos). nn: permanent photo number given at upload, never reused, NOT the position.
  • sort is the display order. Alt text {n} = display position, not nn.
  • Address = image base address setting (Site settings) + stored path.
  • The helper never adds or changes a file extension; it outputs the stored path as-is (feed JPG/PNG keep working until converted).

Uploads from Add Vehicle (phone and desktop)

  • Browser, before upload: apply EXIF rotation, shrink to max 2000px long side, re-encode JPG (~0.85 quality). Strips all EXIF incl. GPS (~0.5 MB per photo).
  • Server: build thumb/card/full (WebP) + share (JPG) from that file, send to storage, delete the temp file, re-strip EXIF as a safety net.
  • Requirement: PHP GD or Imagick with WebP support. Show a warning in the theme health/status check if missing; fallback = JPG for all sizes.
  • HEIC: load a HEIC decoder in the browser ONLY when a HEIC file is picked (never on page load), convert to JPG, continue. On failure: "This photo format isn't supported. Export it as JPG and try again."
  • Queue: 2 uploads at a time; auto-retry 3 times (~2s, 5s, 15s), then Retry per photo + "Retry all failed". No resume needed.
  • Keep pending photos in IndexedDB so a dropped connection or reload doesn't lose them. Warn before leaving the page while uploads are pending. Save/Publish while uploading shows "X photos still uploading" and waits.
  • Reordering only changes sort: no renames, no re-uploads.

Feed sync and manual photos

  • Feed sync only touches source=feed photos; it never deletes manual photos.
  • Removing a FEED photo in Add Vehicle sets hidden=true (the next import doesn't bring it back). Removing a MANUAL photo deletes it and all its sizes.
  • Staff photo order is kept on feed sync; new feed photos are added at the end.
  • Add Vehicle shows a "Feed" or "Added" tag on each thumbnail.

Files and folders

  • Folder: /uploads/dealerbase-vehicles/{dealer}/{vin}/ with thumb/, card/, full/, share/.
  • Names: {year}-{make}-{model}-{trim}-{vin}-{nn}.webp (lowercase, hyphens; nn = permanent photo number); share = same name .jpg; unconverted feed files keep their own extension.
  • Drafts use a temporary name. On FIRST publish, photos are renamed once to the standard name. After publish, never renamed.

Loading

  • Lazy-load every vehicle photo except:
    • the first Vehicle Page photo (loading="eager", fetchpriority="high");
    • the first row of cards on EVERY page that shows cards: 3 desktop / 2 tablet / 1 phone. Includes the inventory grid (Pagination A), Favorites, Homepage featured vehicles. Cards take a priority flag.
  • Always output width and height.
  • Full-screen viewer: real <img> with alt, width, height, srcset (full size), created only when a photo is opened.

Alt text

Default: "{Year} {Make} {Model} {Trim} – photo {n}" (stored alt overrides).

Feed photos: remove, reshoot date, placeholders (plugin 0.29.4–0.29.5, Feed Importer 1.3.0)

  • Remove photo (feed): dealerbase_remove_feed_photo() deletes files now, fires dealerbase_photo_files_deleted, keeps a tombstone in _db_photos { hidden, removed, removed_at, source_url, source_timestamp, sizes: [] }. Manual photos: deleted fully.
  • Queue: dealerbase_queue_photos( $id, $urls ) – items are a URL or [ 'url', 'ts' ]. Tombstone + same/older/no ts → skipped; newer ts → new photo (new nn/name).
  • Photo date: importer field photos_ts → row key DB Photos Date → dealerbase_photo_date() (dates, unix, ms). Stored per car in _db_photos_ts. Newer (first date counts as newer) + car has feed photos + setting feed_replace_photos (default 1) → dealerbase_replace_feed_photos(): batch jobs (job[5] = batch id) held in _db_photo_new, _db_photo_replace = { batch, ts, n, started }; dealerbase_replace_feed_photos_swap() when the batch is done: new feed photos in feed order, then manual photos; old feed files + tombstones removed, dealerbase_photo_files_deleted fired; all failed → old kept. Normal queue paused for that car while a batch runs.
  • Placeholders: feed_no_photo_date = placeholder (default) | use; per feed no_photo_date ('' = site setting). Only applies when the feed maps the photo date column (has_photo_date). placeholder_patterns (lines, partial match, filter dealerbase/placeholder_patterns) → dealerbase_is_placeholder_url(): never downloaded, hidden from dealerbase_vehicle_photo_list. Flag _db_photos_placeholder. min_photos counts real photos only. Theme shows dbt_no_photo().
  • Import stats: photos_replaced, l_photos (VINs) → "Photos replaced for N cars (newer photo date)".

Delayed photo removal (plugin 0.29.1–0.29.3)

  • Permanent delete of a vehicle (manual, bulk, feed "delete", sold purge) puts its photo paths on a removal list (option, with date + VIN) instead of deleting at once.
  • Cron removes them after "Remove photos after" days (5–7, default 7; Import feeds › Settings + Vehicle settings) in batches (e.g. 200 files per run), firing ONE dealerbase_photo_files_deleted per batch.
  • Same VIN back before then → its pending paths are dropped from the list.
  • Inventory cache rebuilt once per bulk delete.

Admin photo tools (0.29.2)

  • Trash row action "Delete permanently + photos now" (dealerbase_delete_vehicle_now), Status › Photo tools: "Process photos now", "Remove now (N files)". Photo cron time-based (~45 s).
  • 0.29.3: a returning VIN only protects the exact paths it reuses (dealerbase_photo_removal_cancel( $vin, $paths ), dealerbase_live_vin_paths).

Storage hooks for Cloud Images (plugin 0.29.0)

  • apply_filters( 'dealerbase_photo_stored', $item, $vehicle_id, $files ) – end of dealerbase_photo_from_file; $files = [ size => absolute local path ]. Return the item or a WP_Error.
  • do_action( 'dealerbase_photo_files_deleted', $vehicle_id, $paths ) – in dealerbase_delete_photo_files( $item, $vehicle_id ); relative paths; fires even if local files are gone.
  • do_action( 'dealerbase_photo_files_moved', $vehicle_id, $map ) – one-time rename of manual photos on first publish (dealerbase_photos_final_names); $map = old path => new path; fired before _db_photos is saved; post meta _db_named = 1.
  • apply_filters( 'dealerbase_photo_storage', null ) – return array( 'name' => 'Cloud Images' ) to show "Photo storage: Cloud Images" and make img_base read-only on Site settings.
  • Permanent delete of a db_vehicle (any path) runs before_delete_post → all photo files (feed + manual) via dealerbase_delete_photo_files. Selling alone does not delete photos.
  • Site settings save keeps keys it doesn't own (array_replace), so img_base set by Cloud Images is never wiped.

Not in this project

Cloud Images plugin, Cloudflare setup and hosted mode are built separately (requires Car Lots plugin 0.29.0+).

11. Server requirements and health check

  • PHP GD or Imagick with WebP support (warn in health check if missing; fallback JPG for all sizes).
  • Outbound HTTPS to vpic.nhtsa.dot.gov (VIN look-up) and to the mail service used for lead emails (use a transactional SMTP/API; lead emails must not depend on PHP mail()).
  • Cron / Action Scheduler for feed import and image processing.

12. Later / on hold

  • Second revision: Scan VIN; Homepage B and C; more vehicle page styles.
  • After launch: product rename, preset color schemes in Look & style, screenshots in the dealer guide.
  • Phase 2: follow-up email to shoppers.
  • On hold: spreadsheet import (docs/On hold - spreadsheet import.md).

Default vehicle description (plugin 0.12.2 / theme 0.8.6)

  • Used on the vehicle page when post_content is blank or under 20 characters (after stripping tags).
  • Setting: Car Lots › Vehicle settings › Listings › "Default vehicle description" (vset key desc_template, max 2,000 characters, empty = default text). Filter: dbt/default_description_template.
  • Tokens: [Condition] (new / used / certified pre-owned), [Year] [Make] [Model] [Trim], [Body Type] (lowercase; SUV kept; blank/Other = "vehicle"), [Dealership Name] (dealer_name, else site title), [Dealer Phone Number] (main_phone; the whole sentence is removed when there is no phone), [Stock Number] [VIN]. Missing values are dropped and spacing cleaned.
  • Default text: "Take a closer look at this [Condition] [Year] [Make] [Model] [Trim] for sale at [Dealership Name]. This [Body Type] is a great option … You can also call [Dealership Name] at [Dealer Phone Number] to speak with our team. We're here to help make finding and purchasing your next vehicle easy."
  • Dealer guide: explain where to edit it and list the fields.

Payments: rates by price + Show payments switch (plugin 0.29.48 / theme 0.29.10, Nov 7)

  • Settings: Vehicles › Vehicle settings › Payments. Stored in option dealerbase_vehicle_settings: show_payments (1 default), pay_tiers = up to 3 rules {from, to, apr, term, down} (to 0 = "and up", down '' = the single down %), plus the single payment_apr / payment_term_months / payment_down_percent.
  • dealerbase_pay_defaults( $price = null ): no price or no rows → single rate. With a price → first rule (sorted by from) where from ≤ price ≤ to; no rule fits → single rate. dealerbase_payment( $price ) always uses the car's row. dealerbase_pay_tiers() returns the sorted rows.
  • Vehicle page A/B/C: $pd = dealerbase_pay_defaults( $price ) → calculator starts at that car's APR/term.
  • dbt_pay_note( $price = null ): on a vehicle the car's APR/months; on pages with many cars and 2+ rows, [apr] and [months] show the range (e.g. 8.90–12.90, 48–72).
  • dealerbase_show_payments() false → card data pay/pay_n empty; body class db-no-pay + inline CSS hides [data-part=payment], [data-role=card-payment], [data-part=payment-calculator], homepage budget section, .dbv-pay-row, sticky "Est. /mo", [data-part=filter-pay], .db-pt-star; Search by payment block (dbk_r_budget) renders nothing; "Under $X/mo" popular searches + sitemap links skipped. Financing page itself is not removed (dealer hides it from the menu).
  • To check when building new templates: every payment element needs data-part="payment" (or one of the selectors above) so the switch hides it.

Financing – FUTURE (noted Oct 23, not built)

  • Site settings switch: "Turn off all financing" – hides the Financing page, its menu/footer links, the Home financing block, the vehicle page payment calculator, "Get pre-qualified", the "Financing" topic in the lead pop-up, and Ask about financing buttons. One switch, everything off; each piece can still be turned off on its own later.
  • Later: optional integration with the dealer's own finance service (e.g. their lender / credit-app provider). Car Lots will NOT host credit applications or collect sensitive financial data (SSN, income, bank) – only a link-out: setting "Finance provider link" (URL + button label). When set, finance buttons open that link (new tab, GA event finance_link_click) instead of our basic-contact form.
  • Privacy copy (approved, in the Financing pop-up): "Your privacy is important to us. To help protect your personal information, we don't accept online credit applications or collect sensitive financial information through our website…"

Reviews placement (Oct 24)

  • dealerbase_reviews_pages() → list of keys (p{page ID}, inventory, vehicle). Theme dbt_reviews_here() checks the current page; template-parts/reviews.php renders once per page (built-in spot on Home/About/Contact/Locations, otherwise footer.php above the footer).

Custom fields (Nov 1)

  • Option dealerbase_custom_fields = { cf_N: {label, type, choices, tab, show, filter} }, max 10. Values in post meta db_cf_N (sanitized, 60 chars). Ids never change on rename → safe for feed export mapping (Phase 3). Feed import never writes cf_ meta.
  • Vehicle page: section data-part="dealer-additional-info" (data-text cf-title) under About this vehicle; Yes/No only when Yes.
  • Feed mapping (Nov 1): custom fields are optional targets in the importer ("Custom fields" group); canonical column "CF {key}"; import.php writes only mapped, non-empty values.

Ad tracking (plugin 0.29.48 / theme 0.29.10, Nov 7)

  • Store info fields: gads_id (AW-…), gads_lead, gads_call (conversion labels), meta_pixel, tiktok_pixel. Bad format saves empty.
  • dealer-ready.php wp_head (priority 3) loads gtag (if not already), Meta fbq, TikTok ttq. Cookie notice on (sw_privacy) → waits for db_consent=all / window.dbConsentAds, listens to db:consent.
  • Theme site.js fires document event "dbt:conv" {ev, p} on click_to_call, form_submission, product_submission → Google Ads conversion (send_to AW/label), fbq Lead/Contact, ttq SubmitForm/Contact.

---

13. Full system rules – read this first (Nov 7, 2026)

One place for every rule. Where an older section or "Dealer tech Q&A - vAuto dealer.md" (Nov 5) disagrees, this section wins.

13.1 Products, names, plans

  • Product name: Car Lots (author Carzino, https://www.carzino.com/). Code names stay dealerbase_*, post type db_vehicle, text-domain/folders dealerbase* until the pre-sale clean-up.
  • Pieces and dependencies:
    • Car Lots plugin (dealerbase/): settings, vehicles, import engine, leads, SEO, redirects, images helper. Required.
    • Car Lots theme (dealerbase-theme/): every page design + blocks. Needs the plugin (falls back gracefully).
    • Car Lots Feed Importer (dealerbase-feed-importer/): feed screens, presets, schedules, history. Needs the plugin.
    • Car Lots Feed Export (dealerbase-feed-export/): outgoing feeds (Google, Facebook, CARFAX, AutoTrader…).
    • Car Lots Cloud Images (dealerbase-cloud-images/): Pro. Moves photos to the dealer's own Cloudflare R2 (bring your own account only; no hosted photos).
    • Child theme (dealerbase-child/): Pro only.
  • Sales: Freemius, Starter + Pro/Advanced. Licensing not built yet.
  • Vehicles only (cars, trucks, SUVs). No RVs.

13.2 Site settings model

  • One option array (dealerbase_get( key )), edited in Car Lots › Site settings tabs: Store info, Hours, Look & style, Header & footer, Social links, More stores (up to 4 extra), Team & About, Disclosures & privacy.
  • Getting started: 10-step checklist; each check turns green from saved data (no manual ticks). Hide link at the bottom.
  • Main phone vs Sales phone: sales phone (call tracking) shows on inventory, vehicle, compare, favorites and cards; empty = main phone. Service/Parts phones: empty = main.
  • Phones are saved/displayed as 555-555-0123 (dealerbase_phone_display; 11 digits starting with 1 → 1-555-555-0123).
  • Placeholders in defaults: dealer_name, (555) 555-0123, 123 Main Street, City, ST 00000.
  • Conditions in stock: only show New / Used / Certified choices for conditions actually in stock; with one condition, hide every condition switcher (hero tabs, dropdowns, pills, inventory filter).
  • Labels: "Certified Pre-Owned" → "Certified"; "Automatic" → "Auto" in pills/tags; miles in thousands ("121k mi") in pills/tags/cards, full number only in Mileage fact + Specifications.
  • Timezone: Getting started warns when WordPress timezone is UTC/unset (import schedules + "hours today" depend on it).

13.3 Look: colors, fonts, layouts

  • 5 slots: A Main, B Accent, C Dark text, D Light background, E Extra; grays Gray-1…Gray-7 (--gray-50…700). Never hard-code a brand hex in templates.
  • Every colored element has data-role; defaults per role are in CLAUDE.md "Roles" and includes/look.php (groups: var → [label, default slot/#hex/=var]).
  • Color themes: 9 built-in themes (FINALS in color-engine.js); "Customize this theme" saves a per-site copy; reset buttons per color + whole theme. Proofing rule: see CLAUDE.md "Color theme proofing".
  • Font: one of 8 web fonts, used everywhere. Corners: Soft/Square etc. (Look & style).
  • Layouts (Customizer › Layout): Header A–D, Footer A–D, Card A–E, Filters A–C, Vehicle page A–C (single-db_vehicle.php loads template-parts/), Hero A…
  • Breakpoints come from the container width (container queries), not the viewport: <768 phone, 768–1023 tablet (phone menu + filter drawer, 2 cards/row), ≥1024 desktop (3 cards/row).

13.4 Pages and blocks

  • Site pages are created once per pages version (includes/pages.php): Home, Inventory (/cars-for-sale/), Vehicle, Compare, Favorites, Financing, Car Finder, Sell My Car, Contact, Locations, About, Meet Our Team, Service, Reviews, Blog, Sitemap, Accessibility, Privacy Policy, Terms of Use, Terms and Conditions.
  • Page Blocks (Nov 6 rule): dealers can add, move and edit a small set of server-rendered Car Lots blocks (dealerbase-theme/inc/blocks.php): Banner, Text and photo, Vehicles, Shop by make, Shop by body style, Shop by vehicle type, Why choose us, Call to action, FAQ, Latest blog posts, Contact form, Photo gallery, Search by payment/price, Store info and hours, etc.
    • Every field has a character limit and allowed options; values are cleaned before render.
    • Car Lots blocks are full width in the editor; plain text blocks stay in the reading column.
    • Starter photos ship in assets/home/ and assets/types/ (car names blurred).
    • The Vehicles block shows only conditions/makes/body styles in stock and a live match count.
  • Header/footer, dealer info, vehicles and form questions are not page content; they come from Site settings / inventory / Forms.
  • Menus: Appearance › Menus, locations Header menu + Footer column 1–3; "Show a link to this page" box on new pages adds it to a menu on publish.
  • Legal pages are starter templates (not legal advice), auto-filled with dealer info; "Last updated" date in Disclosures & privacy.

13.5 Vehicles

  • Post type db_vehicle; fields in includes/fields.php; edit screen includes/edit-screen.php; list includes/list.php.
  • URL: /vehicle/{year}-{make}-{model}-{trim}-{last 6 of VIN}/, set at creation, not changed on edit. Inventory /cars-for-sale/ + clean landing paths [condition/][body/][make/[model/]]; /inventory/, /shop/, /cars/ redirect there.
  • Feed fields are editable on the vehicle page. Editing a feed field auto-ticks "Keep" ("Your edit" tag) so the feed won't overwrite it; unticking lets the next import update it again.
  • Trims to ignore (Vehicle settings › Listings): junk trim words removed from names and filters.
  • Custom fields (includes/custom-fields.php): dealer-made fields; any field can become a filter (dealerbase_extra_filters, URL/API name f_{key}).
  • Specials/tags: max 20 tags site-wide, 3 per vehicle, 24 characters.
  • Sold cars: after "Feed runs before sold" (default 2) the car is Sold; stays online X days; then either 301 to the same make/model inventory page or a 410 "This car has sold" page (410.php, header + search + similar cars). Optional delete after N days (0 = never).
  • Photos needed to publish (e.g. 2): fewer = stays draft.
  • Add Vehicle (front-end staff page): Sales Manager + Administrator only; Sales Managers never see wp-admin. Photos first, VIN + NHTSA vPIC decode (cached; public Sell My Car limited to 20 lookups/visitor/hour), details, features, description, store, Save draft / Publish. Second revision: Scan VIN (barcode) – button space reserved.
  • Default vehicle description template (see earlier section).

13.6 Feed import (plugin import engine + Feed Importer add-on)

  • Sources: upload, URL, FTP/SFTP pull (SFTP needs PHP ssh2). Formats CSV (any delimiter), XLSX, XML, JSON, auto-detected. No inbound API (vAuto pushes to the dealer's FTP; we pull).
  • Matching by VIN; each row fingerprinted, unchanged rows skipped; returning VIN reuses its record.
  • Presets: vAuto, HomeNet, Carzino checked against real files; 8 others auto-match only until sample files arrive.
  • Clean-up sheet (Vehicles › Clean-up sheet, includes/cleanup.php): ~1,700 standard rules (make, model, body, drivetrain, transmission, fuel, colors, features). Dealer rules override; "-" hides a feature; colors keep the real name and only set the filter color; changes apply on the next import. Spreadsheet download/upload.
  • Broken-feed protection (Vehicle settings › Feed): a car counts as missing only after N runs in a row (feed_missing_runs, default 2) and a "% missing" guard (feed_sold_guard_percent, default 30%) stops mass-selling.
  • If the feed can't be read, nothing changes. One import at a time. Schedules: daily at a time or every X hours; check every 15 min; Run now. WP-CLI: wp dealerbase import.
  • Import history: last 30 runs per feed (counts + status).
  • Status mapping: On hold/In transit/Wholesale/Inactive → draft; Pending/Deposit → Sale pending. Price = first value > 0. Certified accepts TRUE/Y/1/Yes/CPO.
  • Before launch: remove the importer "Delete" tool.

13.7 Vehicle photos and Cloud Images

  • Everything goes through the image helper (section 10). Sizes thumb/card/full WebP + share JPG; never Media Library; folder /uploads/dealerbase-vehicles/{dealer}/{vin}/.
  • Background queue runs on WP-Cron: every host needs a real cron hitting wp-cron.php every minute (Cloudways: Cron Job Management; guide in docs).
  • Reshoots (photo date column), placeholders ("coming soon" skipped), delayed removal (7 days), manual photos never touched by the feed.
  • Cloud Images plugin: dealer's own Cloudflare R2 bucket + custom domain + Account API token (Object Read & Write, that bucket only). Save & Test, then Move photos to cloud (batched, background); Move photos back before turning off. When connected, Store info shows "Vehicle photo storage: Cloud Images ✓ Connected" and the photo address is managed by the plugin.

13.8 Forms, leads, emails

  • Forms: Vehicle page (product_submission + inquiry_topic), Financing (credit_app), Car Finder (car_finder), Sell My Car (sell_my_car), Contact (contact), Service (service), Team (team_contact).
  • Every submission: lead ID DB-YYYYMMDD-NNNN → saved in Car Lots › Leads (search, filter by form/date, Details, CSV export; no spreadsheet formulas) → lead alert to recipients (each set to all or specific forms) → ADF to CRM (Vehicle page, Car Finder, Sell My Car only, when CRM is on) → shopper confirmation (on by default).
  • Financing and Contact: never ADF. Service and Team: no ADF; Team alert goes to that person + "Team messages" recipients.
  • ADF: every shopper answer in <customer><comments> (form order) then the Tracking line; <provider><name>Car Lots</name><service>Website – {source} – {form} – {topic}</service>. Car Finder placeholder vehicle: year 0000 / CARFINDER / REQUEST, VIN CARFINDER00000000, stock CAR-FINDER. Sell My Car: shopper's car as interest="sell".
  • Tracking (first touch, 30 days, skipped if cookies declined): utm_source/medium/campaign, gclid, GA client_id, landing page, form page.
  • Shopper confirmation: Forms & leads › Shopper confirmation; empty boxes = each form's own copy; fill-ins {first_name} {last_name} {vehicle} {topic} {dealer_name} {contact_method} {form}; optional "Text us" number. Phase 2 follow-up email: not built.
  • Spam: honeypot + nonce + optional reCAPTCHA v3 (includes/recaptcha.php).
  • Email delivery: FluentSMTP recommended (includes/fluentsmtp.php can download it); Email log keeps every sent email. "Save and send a test lead" sends all three emails.
  • Testing values: docs/Lead and ADF testing checklist.md.
  • GA4 ID or GTM ID in Store info (one or the other, or a plugin like Site Kit – never two).
  • Events: click_to_call (location = phone_/tablet_/desktop_ + header/footer/…), form_submission (form_name), product_submission (inquiry_topic), all with lead_id.
  • Ad tracking (Store info › Ad tracking): Google Ads AW- ID + lead label + phone-tap label, Meta Pixel (Lead / Contact), TikTok Pixel (SubmitForm / Contact). Theme fires dbt:conv.
  • Cookie notice + "Your Privacy Choices" (Header & footer switch): analytics waits for the shopper's choice; ad pixels need "all"; GPC respected.
  • Known: demo GA4 shows phone_click next to click_to_call – check GA4 for a second tag / event rule.

13.10 SEO and redirects

  • Pages/posts: SEO box under the editor (Google preview, focus keyword checklist, title, description, share image, noindex). Vehicles + inventory: templates with tags {year} {make} {model} {trim} {price} {city} {state} {dealer} {site} {sep} {phone}; per-vehicle overrides; share image defaults to the first photo.
  • Title rule: keep a space before "|".
  • Schema: Car + Offer + AutoDealer (vehicle), AutoDealer (home), BreadcrumbList (inventory). Sitemap = WordPress core /wp-sitemap.xml; filter URLs excluded; extra ?filters = noindex,follow; canonical = clean path + page.
  • Landing pages kept 90 days after the last matching car, then 301 one level up.
  • Yoast / Rank Math take over when installed.
  • Redirects (Car Lots › Redirects): 301/302, hit counter, never loops to itself; "Pages not found" list with one-click Redirect.

13.11 Payments

  • Vehicle settings › Payments: APR, term, down %; Show payments switch (hides every payment, calculator, payment search and filter); Rates by car price (up to 3 From/To rules; no match = single rate). Disclosure text in Disclosures & privacy.

13.12 Admin screens and status

  • Car Lots menu: Getting started, Site settings, Leads, Forms & leads, Reviews, Team, SEO, Status, Redirects, Cloud Images.
  • Status (includes/status.php): checks install, pages, cron, email, PHP, photo folders.
  • Uninstall deletes settings only when "Delete data on uninstall" is on; vehicles and photos are never deleted.

13.13 Build and release rules

  • Source lives in build/piece2/<folder>/; log changes in docs/Build changes - waiting.md. Zips only when asked or for urgent live fixes.
  • Theme asset version follows style.css Version (cache busting) – bump it with every theme zip.
  • GitHub: CZDevelopment2023/Carzino-Theme (private). Push only after the user confirms the build on the test site, at end of day, or before a big change. Update github.md after each push.
  • Docs to keep current: Check links.md, Lead and ADF testing checklist.md, START HERE - next chat.md, this spec, the Dealer Guide (from each page's Notes box).

13.14 Where the dealer Q&A changed since Nov 5

  • Per-field keep for every feed field (not only price + description).
  • Broken-feed protection is on (2 runs, 30%).
  • Cloud Images is built (Pro).
  • Per-vehicle SEO title/description/share image is built.
  • Product renamed My Store → Car Lots.

13.15 Inventory filters, URL parameters and the vehicles API (Nov 7)

  • Screen: Vehicles › Fields & filters, 3 tabs: Custom fields, Inventory filters, API output.
  • Inventory filters tab, one row per filter: Show (a filter renders only when ticked), Name on the site (also used by the API), Order (ascending, steps of 10), Open at start (accordion open on load). Save filters. A filter with no matching cars hides itself (e.g. Condition with Used only).
  • Default order and URL / API parameters:
    • Body Style body[] (10, open) · Condition cond[] (20, open) · Make make[] (30, open) · Model model[] (40) · Trim trim[] (50)
    • Year year_min / year_max (60) · Price price_min / price_max (70, open) · Monthly Payment pay_min / pay_max (80) · Mileage miles_min / miles_max (90)
    • Drivetrain drive[] (100) · Transmission trans[] (110) · Cylinders cyl[] (120) · Fuel Type fuel[] (130) · Fuel Economy mpg, mpg_basis (140)
    • Exterior Color ext[] (150) · Interior Color int[] (160) · Doors doors[] (170) · Features feat[] (180)
  • [] params are multi-select (OR inside one filter, AND between filters). Same params work on the inventory URL and the REST API.
  • Add a filter from any vehicle field (bottom of the tab): pick a field (Seating, Owners, Title brand, Status, Store, Badge or a custom field) + a filter type (Checkbox list, …) → Add filter. Stored in dealerbase_extra_filters; param f_{key}. It still needs Show ticked.
  • Monthly Payment filter and pay_* params are removed when Show payments is off.
  • Colors filter on the color group (color_group) from the Clean-up sheet, not the raw color name.
  • REST API: GET /wp-json/dealerbase/v1/vehicles (list; accepts every filter param above, plus paging and sort) and GET /wp-json/dealerbase/v1/vehicles/<id or VIN> (one car). ?fields=a,b,c limits output; ?preset=detail returns the full detail set. Read-only, public data only (no lead or dealer-private data).
  • API output tab: per field Include + Output name (rename for the reading app). Card fields are always included. Keys: vin, stock, status, year, make, model, trim, body, vehicle_type, condition, miles, price, sale_price, online_special, sale_ends, msrp, price_label, badge, in_stock, featured, drive, trans, trans_detail, induction, fuel, engine, cylinders, displacement, mpg_city, mpg_hwy, ev_range, doors, seating, ext, int, color_group, title_brand, owners, accidents, history_url, warranty, extended_warranty, video, store, source_url, cf_1…cf_10.
  • Photos in API output always come from the image helper (never raw meta).

13.16 Custom fields – exact rules (Nov 7)

  • Max 10 per site; key cf_1…cf_10 fixed forever (rename never loses values, feed mapping or API key).
  • Types: Text, Number, Yes / no, Choice list, Link (URL), Date. Values max 60 characters, entered on the vehicle's Custom Fields tab (or the chosen edit tab).
  • "On the vehicle page" → shows under About this vehicle › Additional details. Yes/no fields show only when Yes.
  • Feed imports fill a custom field only when it is mapped in Import feeds › Map fields (Custom fields group); unmapped fields are never touched. Standard equipment belongs in Features, not custom fields.
  • Filter: today via Inventory filters › Add a filter from any vehicle field. The hint there mentions an "As a filter on the inventory page" tick box that does not exist yet.
  • Second revision: a "Use in" choice per field on the Custom fields tab (vehicle page, filter, cards, export feeds, API) and fix that hint text.

13.17 Payments – exact math and rules (Nov 7)

  • Single rate (defaults 9% APR, 60 months, 20% down). Standard amortized payment on (price − down). Example: $20,000 car → $332/mo.
  • Rates by car price: up to 3 rules {from, to, apr, term, down}. A car uses the first rule its price fits; empty to on the last rule = "and up"; empty down = single down %; no match or all rules empty = single rate.
  • Used by: cards, vehicle page, calculator start values (shopper can change down, term, credit score), Monthly Payment filter, homepage payment search, export feeds and the API.
  • Show payments off: hides every payment and payment search/filter; prices still show; the Financing page stays (hide it from the menu if not offered).
  • Payment terms disclosure: max 1,200 characters, fill-in [dealer_name], shown after a bold "Disclosure:" in the payment terms pop-up ("See terms", the * after /mo, "Financing terms and conditions"). Same text as Site settings › Disclosures & privacy. Payment estimate note (max 300, fill-ins [months] [apr] [down]) sits under cards and the calculator.