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 file | Builds into |
|---|---|
| Style Guide | Theme tokens: colors A–D, grays, font, radius, buttons, links, focus, icons, type scale |
| Header A–D | Header template parts (one active, picked in Look & style) |
| Footer A–D | Footer template parts (one active) |
| Card A–F | Vehicle 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–C | Inventory filter sidebar / drawer (one active) |
| Pagination A (+ Results Bar A) | Inventory archive: results bar, grid, pager |
| Vehicle Page A–C | Single 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–C | Homepage 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 |
| Favorites | Favorites page |
| Homepage A (+ Hero, Makes, Finance, Blocks parts) | Front page (website page, content-locked) |
| About A, Contact A, Locations A | Website pages (content-locked) |
| Finance A, Car Finder A | Website 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 B | How pages are edited (block editor rules, below) |
| Setup Wizard A | Plugin setup wizard (Car Lots > Setup) |
| Site Settings A | Plugin settings (Car Lots > Site settings) |
| Forms A | Plugin Forms & leads (Car Lots > Forms & leads) |
| Add Vehicle A | Staff add/edit vehicle screen (phone + desktop) |
| Homepage B, Homepage C | Drafts 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)
| Form | Lead alert email | CRM (ADF) | GA4 event |
|---|---|---|---|
| Vehicle page form | Yes | Yes | product_submission + inquiry_topic |
| Car Finder | Yes | Yes (placeholder vehicle) | form_submission, form_name: car_finder |
| Financing (credit app) | Yes | No | form_submission, form_name: credit_app |
| Contact us | Yes | No | form_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; vehicleinterest=test-drivefor test drive topic elsebuy,statusnew/used, year, make, model, vin, stock, trim, bodystyle, odometer (units mi), price type asking USD; trade-in = second vehicleinterest="trade-in"; customer contact first/last name, email + phone (cellphone) withpreferredcontact; 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 orCARFINDER; model = picked orREQUEST; VINCARFINDER00000000; stockCAR-FINDER; budget in<finance>; timeframe in<timeframe>. - Lead ID:
DB-YYYYMMDD-NNNN, sent in ADF and aslead_idon 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
locationvalue 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=jsonand 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 )anddealerbase_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
| Size | Width | Format | Used for |
|---|---|---|---|
| thumb | ~400px | WebP | cards, thumbnails |
| card | ~800px | WebP | cards on larger screens, gallery side photos, photo grid |
| full | ~1600px | WebP | Vehicle Page main photo, full-screen viewer |
| share | ~1200px | JPG | emails (lead alert + shopper confirmation), ad/marketplace feeds, social posts |
- Web sizes output
srcset(thumb/card/full) andsizes.shareis 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.sortis the display order. Alt text{n}= display position, notnn.- 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=feedphotos; 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}/withthumb/,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
priorityflag.
- the first Vehicle Page photo (
- Always output
widthandheight. - 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, firesdealerbase_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 keyDB 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 + settingfeed_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_deletedfired; all failed → old kept. Normal queue paused for that car while a batch runs. - Placeholders:
feed_no_photo_date= placeholder (default) | use; per feedno_photo_date('' = site setting). Only applies when the feed maps the photo date column (has_photo_date).placeholder_patterns(lines, partial match, filterdealerbase/placeholder_patterns) →dealerbase_is_placeholder_url(): never downloaded, hidden fromdealerbase_vehicle_photo_list. Flag_db_photos_placeholder. min_photos counts real photos only. Theme showsdbt_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_deletedper 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 ofdealerbase_photo_from_file;$files= [ size => absolute local path ]. Return the item or a WP_Error.do_action( 'dealerbase_photo_files_deleted', $vehicle_id, $paths )– indealerbase_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_photosis saved; post meta_db_named = 1.apply_filters( 'dealerbase_photo_storage', null )– returnarray( 'name' => 'Cloud Images' )to show "Photo storage: Cloud Images" and makeimg_baseread-only on Site settings.- Permanent delete of a
db_vehicle(any path) runsbefore_delete_post→ all photo files (feed + manual) viadealerbase_delete_photo_files. Selling alone does not delete photos. - Site settings save keeps keys it doesn't own (array_replace), so
img_baseset 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 singlepayment_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 datapay/pay_nempty; body classdb-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 typedb_vehicle, text-domain/foldersdealerbase*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.
- Car Lots plugin (
- 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" andincludes/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.phploadstemplate-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/andassets/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 inincludes/fields.php; edit screenincludes/edit-screen.php; listincludes/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 namef_{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.phpevery 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.phpcan 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.
13.9 Analytics, ads, consent
- 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) · Conditioncond[](20, open) · Makemake[](30, open) · Modelmodel[](40) · Trimtrim[](50) - Year
year_min/year_max(60) · Priceprice_min/price_max(70, open) · Monthly Paymentpay_min/pay_max(80) · Mileagemiles_min/miles_max(90) - Drivetrain
drive[](100) · Transmissiontrans[](110) · Cylinderscyl[](120) · Fuel Typefuel[](130) · Fuel Economympg,mpg_basis(140) - Exterior Color
ext[](150) · Interior Colorint[](160) · Doorsdoors[](170) · Featuresfeat[](180)
- Body Style
[]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; paramf_{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) andGET /wp-json/dealerbase/v1/vehicles/<id or VIN>(one car).?fields=a,b,climits output;?preset=detailreturns 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_10fixed 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.
