Build valid LocalBusiness JSON-LD with a visual opening-hours builder, multi-location support, 30+ business types, and a built-in validation checklist.
Structured data only helps if the underlying facts are accurate and match everywhere else your business is listed.
| Element | Why it matters |
|---|---|
| Name, Address, Phone (NAP) | Must match exactly across your website, Google Business Profile, and other directories — mismatches undermine trust signals. |
| Business type | Use the most specific type available (Restaurant, Dentist, Plumber...) instead of generic LocalBusiness for access to type-specific properties. |
| Opening hours | Feeds directly into Google's "Open now" / "Closes at" labels — incorrect hours are a common source of poor customer experience. |
| Geo-coordinates | Removes ambiguity for map-based and "near me" results, especially for businesses that are hard to geocode from address alone. |
Also want your homepage's meta tags and SERP snippet sorted? Try the Meta Tag Generator and SERP Preview Tool.
Most free generators handle a single location with a plain text hours field. This one is built for real businesses with real complexity.
| Feature | SEOTriggers | TechnicalSEO.com | Localo |
|---|---|---|---|
| JSON-LD output | ✓ | ✓ | ✓ |
| Visual day-by-day opening hours builder | ✓ | Plain text | Basic |
| Multi-location support (one block per branch) | ✓ | ✗ | Paid feature |
| Type-specific fields (cuisine, star rating, etc.) | ✓ | Generic fields | ✗ |
| Built-in validation checklist | ✓ | ✗ | ✗ |
| WordPress / Shopify / Next.js ready code | ✓ | ✗ | ✗ |
| No account or upsell required | ✓ | ✓ | Pushes paid app |
Set open/close times per day with one click, and copy Monday's hours to every open day instantly — no manual openingHoursSpecification syntax needed.
Add as many branches as you need — each gets its own address, phone, and geo-coordinates in a separate, ready-to-paste JSON-LD block.
Pick Restaurant, Hotel, or 30+ other business types and get the extra fields that type actually supports — not just generic LocalBusiness properties.
Flags missing required fields, incomplete addresses, and suspicious phone formats before you ever hit Google's Rich Results Test.
One click for WordPress, Shopify Liquid, or Next.js code — not just raw JSON.
Everything runs in your browser. Nothing you type is ever uploaded or stored.
It doesn't directly boost rankings, but it helps Google confidently confirm your business name, address, phone number, hours, and category, which supports eligibility for rich results like the local knowledge panel and can improve how accurately you're matched to relevant local searches.
Always pick the most specific type available. Google and other consumers of structured data get more value from a precise type like Restaurant or Dentist than the generic LocalBusiness, because specific types unlock type-specific properties (like servesCuisine or availableService) that generic LocalBusiness doesn't support.
Each physical location should have its own LocalBusiness entry with its own address, phone number, and geo-coordinates, since these are location-specific. This tool lets you add multiple locations and generates a separate, self-contained JSON-LD block for each one — place each block on that location's own page, or all of them on a single locations/store-finder page.
NAP stands for Name, Address, and Phone number. Search engines cross-reference your NAP across your website, Google Business Profile, and other directories to verify your business is legitimate and correctly located — inconsistent NAP data across these sources is one of the most common causes of poor local search visibility.
They're optional but recommended, especially for map-based and "near me" results. Google can usually geocode your address on its own, but supplying exact coordinates removes any ambiguity. You can find your coordinates by right-clicking your business location on Google Maps.
After pasting the generated JSON-LD into your page's <head> or before the closing </body> tag, run the page through Google's Rich Results Test and the Schema.org Validator to confirm there are no errors or missing required properties.
tag {% endcomment %}\n\n" + objs.map(function(o){ return ''; }).join("\n\n"); } function buildNextJS(){ var objs = buildSchemaObjects(); var out = []; out.push("// Add inside your page or layout component"); objs.forEach(function(o, i){ out.push("export function LocalBusinessSchema" + (objs.length>1 ? (i+1) : "") + "() {"); out.push(" return ("); out.push(" "); out.push(" );"); out.push("}"); out.push(""); }); return out.join("\n"); } function renderCms(){ document.getElementById("wpOut").textContent = buildWordPress(); document.getElementById("shopifyOut").textContent = buildShopify(); document.getElementById("nextjsOut").textContent = buildNextJS(); } /* ═══════════════════════════════════════════════════════════════════════ VALIDATION CHECKLIST ═══════════════════════════════════════════════════════════════════════ */ function renderChecklist(){ var issues = []; var name = document.getElementById("fldName").value.trim(); var type = document.getElementById("fldType").value; if (!name) issues.push({level:"error", text:"Missing business name — this is a required field."}); if (type === "LocalBusiness") issues.push({level:"info", text:"Using the generic LocalBusiness type — pick a more specific type if one fits (e.g. Restaurant, Dentist) for richer results."}); var hasAnyHours = hours.some(function(h){ return !h.closed; }); if (!hasAnyHours) issues.push({level:"warning", text:"No opening hours set — all days are marked closed."}); locations.forEach(function(loc, i){ var tag = locations.length > 1 ? "Location " + (i+1) + ": " : ""; if (!loc.street || !loc.city) issues.push({level:"warning", text: tag + "Address is incomplete — street and city are recommended for local search."}); if (!loc.telephone) issues.push({level:"warning", text: tag + "Missing telephone number."}); else if (!/^[+0-9() .-]{7,}$/.test(loc.telephone)) issues.push({level:"info", text: tag + "Phone number format looks unusual — double check it."}); if ((loc.lat && !loc.lng) || (!loc.lat && loc.lng)) issues.push({level:"info", text: tag + "Latitude and longitude should both be set, or both left blank."}); }); if (!document.getElementById("fldImage").value.trim()) issues.push({level:"info", text:"No image set — an image is recommended for richer local search results."}); if (!document.getElementById("fldDesc").value.trim()) issues.push({level:"info", text:"No description set — a short description helps clarify what the business offers."}); if (issues.filter(function(i){return i.level==="error";}).length === 0 && issues.filter(function(i){return i.level==="warning";}).length === 0){ issues.unshift({level:"ok", text:"Required fields look good."}); } document.getElementById("issuesList").innerHTML = issues.map(function(i){ return '
'; }).join(""); } /* ═══════════════════════════════════════════════════════════════════════ TAB SWITCHING ═══════════════════════════════════════════════════════════════════════ */ function setOutTab(tab){ document.querySelectorAll(".out-tab").forEach(function(b){ b.classList.toggle("active", b.dataset.tab === tab); }); document.querySelectorAll(".out-panel").forEach(function(p){ p.classList.add("hidden"); }); document.getElementById("panel-" + tab).classList.remove("hidden"); } /* ═══════════════════════════════════════════════════════════════════════ COPY / DOWNLOAD ═══════════════════════════════════════════════════════════════════════ */ function fallbackCopy(text, cb){ var ta = document.createElement("textarea"); ta.value = text; ta.style.position = "fixed"; ta.style.opacity = "0"; document.body.appendChild(ta); ta.select(); try { document.execCommand("copy"); } catch(e) {} document.body.removeChild(ta); if (cb) cb(); } function copyOutput(mode){ var text = document.getElementById("codeOut").textContent; if (mode === "download"){ var blob = new Blob([text], {type:"text/html"}); var a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = "local-business-schema.html"; a.click(); return; } if (navigator.clipboard && navigator.clipboard.writeText){ navigator.clipboard.writeText(text).catch(function(){ fallbackCopy(text); }); } else { fallbackCopy(text); } } function copyCms(which){ var id = which === "wordpress" ? "wpOut" : which === "shopify" ? "shopifyOut" : "nextjsOut"; var el = document.getElementById(id); var text = el.textContent; var done = function(){ var btn = el.previousElementSibling.querySelector(".copy-btn"); if (btn){ var original = btn.textContent; btn.textContent = "Copied!"; btn.classList.add("copied"); setTimeout(function(){ btn.textContent = original; btn.classList.remove("copied"); }, 1500); } }; if (navigator.clipboard && navigator.clipboard.writeText){ navigator.clipboard.writeText(text).then(done).catch(function(){ fallbackCopy(text, done); }); } else { fallbackCopy(text, done); } } function toggleFaq(btn){ var item = btn.parentElement; var wasOpen = item.classList.contains("open"); document.querySelectorAll(".faq-item").forEach(function(i){ i.classList.remove("open"); }); if (!wasOpen) item.classList.add("open"); } /* ═══════════════════════════════════════════════════════════════════════ MASTER UPDATE ═══════════════════════════════════════════════════════════════════════ */ function update(){ document.getElementById("ratingFields").classList.toggle("hidden", !document.getElementById("chkRating").checked); renderCode(); renderCms(); renderChecklist(); } function handleTypeChange(){ renderTypeSpecificFields(); update(); } /* init */ renderTypeSelect(); renderTypeSpecificFields(); renderHoursTable(); renderLocations(); update();