Meta Tag Settings

0 / 60
0 / 160
Recommended: 1200×630px for best social display
☆ JSON-LD Structured Data
(Premium)

👁 Live Preview

🌐 Google Search Result

G example.com › page
Your Page Title Will Appear Here
Your meta description will show here in Google search results. Write something compelling to increase click-through rate.

🔗 Social Media Cards

📜 Generated Meta Tags

<!-- Fill in the fields to generate meta tags -->

Site Meta Analyzer

Paste HTML source or enter a URL to fetch (URL fetching is a Premium feature). Free tier: paste raw HTML to analyze.

— or —

SEO TagCraft Premium — Save projects, bulk analysis, CSV export, no ads. Just $9 once.

Keyword Density Analyzer

Paste your content below to analyze keyword frequency and density.

Go Premium — Bulk keyword analysis across multiple pages, CSV export, and more.

Saved Projects (Premium)

Save and manage your SEO meta tag configurations.

No saved projects yet. Use the Generator tab to create and save a project.

'); } document.getElementById('genMetaOutput').textContent = lines.join('\n'); } function buildSchema(type, title, desc, url, image) { const base = { '@context': 'https://schema.org', '@type': type }; switch(type) { case 'Article': base.headline = title; base.description = desc; base.author = { '@type': 'Person', name: getGenVal('schemaAuthor') || 'Author Name' }; base.datePublished = getGenVal('schemaDate') || new Date().toISOString().split('T')[0]; if (image) base.image = image; if (url) base.url = url; break; case 'Product': base.name = getGenVal('schemaProdName') || title; base.description = desc; if (image) base.image = image; base.offers = { '@type': 'Offer', price: getGenVal('schemaPrice') || '0.00', priceCurrency: 'USD' }; break; case 'Organization': base.name = getGenVal('schemaOrgName') || title; base.description = desc; if (url) base.url = url; if (image) base.logo = image; break; case 'LocalBusiness': base.name = getGenVal('schemaOrgName') || title; base.description = desc; base.address = { '@type': 'PostalAddress', streetAddress: getGenVal('schemaAddress') || '' }; if (url) base.url = url; if (image) base.image = image; break; case 'FAQ': base.mainEntity = [{ '@type': 'Question', name: 'Sample Question?', acceptedAnswer: { '@type': 'Answer', text: 'Sample answer text here.' } }]; break; default: break; } return base; } function escHtml(s) { return s.replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } // ==================== COPY CODE ==================== function copyCode(elId) { const el = document.getElementById(elId); if (!el) return; const text = el.textContent; navigator.clipboard.writeText(text).then(() => { showToast('Meta tags copied to clipboard!'); }).catch(() => { showToast('Failed to copy. Please select and copy manually.'); }); } // ==================== ANALYZER ==================== function analyzeHtml() { const html = document.getElementById('analyzeHtml').value.trim(); if (!html) { showToast('Please paste HTML to analyze.'); return; } const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const results = extractMetaTags(doc); displayAnalyzerResults(results); } async function analyzeUrl() { if (!isPremium) { openPremiumModal(); return; } const url = document.getElementById('analyzeUrl').value.trim(); if (!url) { showToast('Please enter a URL.'); return; } try { showToast('Fetching URL (via CORS proxy)...'); // Use allorigins.win as CORS proxy const proxyUrl = 'https://api.allorigins.win/raw?url=' + encodeURIComponent(url); const resp = await fetch(proxyUrl); if (!resp.ok) throw new Error('Failed to fetch: ' + resp.status); const html = await resp.text(); document.getElementById('analyzeHtml').value = html; const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const results = extractMetaTags(doc); results.url = url; displayAnalyzerResults(results); } catch(e) { showToast('Error fetching URL: ' + e.message + '. Try pasting HTML instead.'); } } function extractMetaTags(doc) { const results = { title: null, titleScore: 0, description: null, descScore: 0, canonical: null, ogTitle: null, ogDesc: null, ogImage: null, ogUrl: null, ogType: null, ogSiteName: null, twitterCard: null, twitterTitle: null, twitterDesc: null, twitterImage: null, twitterSite: null, robots: null, viewport: null, charset: null, h1: [], jsonld: [], allTags: [] }; // Title const titleEl = doc.querySelector('title'); results.title = titleEl ? titleEl.textContent.trim() : null; if (results.title) { const len = results.title.length; if (len >= 40 && len <= 65) results.titleScore = 100; else if (len >= 30 && len <= 75) results.titleScore = 75; else if (len > 0 && len < 80) results.titleScore = 50; else results.titleScore = 25; } // Meta description const descEl = doc.querySelector('meta[name="description"]'); results.description = descEl ? descEl.getAttribute('content') : null; if (results.description) { const len = results.description.length; if (len >= 120 && len <= 165) results.descScore = 100; else if (len >= 100 && len <= 180) results.descScore = 75; else if (len > 0 && len < 200) results.descScore = 50; else results.descScore = 25; } // Canonical const canEl = doc.querySelector('link[rel="canonical"]'); results.canonical = canEl ? canEl.getAttribute('href') : null; // OG tags results.ogTitle = getMetaContent(doc, 'og:title'); results.ogDesc = getMetaContent(doc, 'og:description'); results.ogImage = getMetaContent(doc, 'og:image'); results.ogUrl = getMetaContent(doc, 'og:url'); results.ogType = getMetaContent(doc, 'og:type'); results.ogSiteName = getMetaContent(doc, 'og:site_name'); // Twitter tags results.twitterCard = getMetaContent(doc, 'twitter:card'); results.twitterTitle = getMetaContent(doc, 'twitter:title'); results.twitterDesc = getMetaContent(doc, 'twitter:description'); results.twitterImage = getMetaContent(doc, 'twitter:image'); results.twitterSite = getMetaContent(doc, 'twitter:site'); // Other results.robots = getMetaContent(doc, 'robots'); results.viewport = doc.querySelector('meta[name="viewport"]') ? doc.querySelector('meta[name="viewport"]').getAttribute('content') : null; results.charset = doc.querySelector('meta[charset]') ? doc.querySelector('meta[charset]').getAttribute('charset') : null; // H1 doc.querySelectorAll('h1').forEach(h => results.h1.push(h.textContent.trim())); // JSON-LD doc.querySelectorAll('script[type="application/ld+json"]').forEach(s => { try { results.jsonld.push(JSON.parse(s.textContent)); } catch(e) { results.jsonld.push(s.textContent); } }); // Collect all meta tags doc.querySelectorAll('meta').forEach(m => { const name = m.getAttribute('name') || m.getAttribute('property') || m.getAttribute('itemprop') || ''; const content = m.getAttribute('content') || ''; if (name) results.allTags.push({ name, content }); }); return results; } function getMetaContent(doc, property) { const el = doc.querySelector(`meta[property="og:${property.split(':')[1] || property}"], meta[name="twitter:${property.split(':')[1] || property}"], meta[name="${property}"], meta[property="${property}"]`); if (!el) { // Try property-based const el2 = doc.querySelector(`meta[property="${property}"]`); return el2 ? el2.getAttribute('content') : null; } return el.getAttribute('content'); } function displayAnalyzerResults(results) { currentAnalysisData = results; const tbody = document.getElementById('analyzerTableBody'); const rows = []; function addRow(name, value, status, scoreHint) { let statusClass = 'ok'; let statusText = status || 'OK'; if (status === 'Good' || status === 'good') statusClass = 'good'; else if (status === 'Missing' || status === 'Bad' || status === 'missing' || status === 'bad') statusClass = 'bad'; let valueDisplay = value || 'Missing'; if (value && value.length > 120) valueDisplay = escHtml(value.substring(0,120)) + '...'; rows.push({ name, value: value || '-', statusClass, statusText, scoreHint: scoreHint || 0 }); } addRow('Title', results.title, results.titleScore >= 75 ? 'Good' : results.titleScore >= 50 ? 'OK' : 'Bad', results.titleScore); addRow('Meta Description', results.description, results.descScore >= 75 ? 'Good' : results.descScore >= 50 ? 'OK' : 'Bad', results.descScore); addRow('Canonical URL', results.canonical, results.canonical ? 'Good' : 'Missing', results.canonical ? 100 : 0); addRow('OG Title', results.ogTitle, results.ogTitle ? 'Good' : 'Missing', results.ogTitle ? 90 : 0); addRow('OG Description', results.ogDesc, results.ogDesc ? 'Good' : 'Missing', results.ogDesc ? 80 : 0); addRow('OG Image', results.ogImage, results.ogImage ? 'Good' : 'Missing', results.ogImage ? 100 : 0); addRow('OG URL', results.ogUrl, results.ogUrl ? 'Good' : 'Missing', results.ogUrl ? 60 : 0); addRow('OG Type', results.ogType, results.ogType ? 'Good' : 'Missing', results.ogType ? 50 : 0); addRow('OG Site Name', results.ogSiteName, results.ogSiteName ? 'Good' : 'Missing', results.ogSiteName ? 40 : 0); addRow('Twitter Card', results.twitterCard, results.twitterCard ? 'Good' : 'Missing', results.twitterCard ? 90 : 0); addRow('Twitter Title', results.twitterTitle, results.twitterTitle ? 'Good' : 'Missing', results.twitterTitle ? 70 : 0); addRow('Twitter Description', results.twitterDesc, results.twitterDesc ? 'Good' : 'Missing', results.twitterDesc ? 60 : 0); addRow('Twitter Image', results.twitterImage, results.twitterImage ? 'Good' : 'Missing', results.twitterImage ? 80 : 0); addRow('Robots', results.robots, results.robots ? 'Good' : 'OK', results.robots ? 50 : 30); addRow('Viewport', results.viewport, results.viewport ? 'Good' : 'Missing', results.viewport ? 100 : 0); addRow('Charset', results.charset, results.charset ? 'Good' : 'Missing', results.charset ? 100 : 0); addRow('H1 Tags', results.h1.length ? results.h1.join('; ') : null, results.h1.length === 1 ? 'Good' : results.h1.length > 1 ? 'OK' : 'Bad', results.h1.length === 1 ? 100 : results.h1.length > 1 ? 60 : 0); addRow('JSON-LD Schema', results.jsonld.length ? results.jsonld.length + ' schema(s)' : null, results.jsonld.length ? 'Good' : 'OK', results.jsonld.length ? 80 : 30); tbody.innerHTML = rows.map(r => ` ${escHtml(r.name)} ${typeof r.value === 'string' && r.value.startsWith(' ${r.statusText} `).join(''); // Calculate overall score const totalPossible = rows.reduce((s, r) => s + 100, 0); const totalActual = rows.reduce((s, r) => s + (r.scoreHint || 0), 0); const overallScore = Math.round((totalActual / totalPossible) * 100); document.getElementById('overallScoreBar').style.width = overallScore + '%'; document.getElementById('overallScoreBar').style.background = overallScore >= 70 ? 'var(--accent)' : overallScore >= 40 ? 'var(--orange)' : 'var(--red)'; const badge = document.getElementById('overallScoreBadge'); badge.textContent = overallScore; badge.className = 'score-badge ' + (overallScore >= 70 ? 'good' : overallScore >= 40 ? 'ok' : 'bad'); document.getElementById('analyzerResults').style.display = 'block'; } function exportCsv() { if (!isPremium) { openPremiumModal(); return; } if (!currentAnalysisData) { showToast('Run an analysis first.'); return; } const rows = [['Tag','Value']]; document.querySelectorAll('#analyzerTableBody tr').forEach(tr => { const cells = tr.querySelectorAll('td'); if (cells.length >= 2) { rows.push([cells[0].textContent.trim(), cells[1].textContent.trim()]); } }); const csv = rows.map(r => r.map(c => '"' + c.replace(/"/g,'""') + '"').join(',')).join('\n'); downloadBlob(csv, 'seo-analysis.csv', 'text/csv'); showToast('CSV exported!'); } function downloadBlob(content, filename, type) { const blob = new Blob([content], { type }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); } // ==================== KEYWORDS ==================== const STOP_WORDS = new Set([ 'a','an','the','and','or','but','in','on','at','to','for','of','with','by', 'is','are','was','were','be','been','being','have','has','had','do','does','did', 'will','would','shall','should','may','might','must','can','could','it','its', 'i','me','my','we','our','you','your','he','she','him','her','they','them','their', 'this','that','these','those','am','from','as','so','if','then','than','too','very', 'just','now','not','no','up','down','out','about','into','over','after','before', 'also','here','there','when','where','why','how','all','each','every','both','few', 'more','most','other','some','such','only','own','same','between','through','during', 'above','below','any','what','which','who','whom','because','while','although' ]); function analyzeKeywords() { const text = document.getElementById('kwContent').value; const removeStopWords = document.getElementById('kwStopWords').checked; const caseSensitive = document.getElementById('kwCaseSensitive').checked; if (!text.trim()) { document.getElementById('kwResults').style.display = 'none'; document.getElementById('kwStats').textContent = ''; return; } // Tokenize let words = text.match(/[a-zA-Z0-9]+(?:[-'][a-zA-Z0-9]+)*/g) || []; if (!caseSensitive) words = words.map(w => w.toLowerCase()); // Remove stop words if (removeStopWords) { words = words.filter(w => !STOP_WORDS.has(w.toLowerCase()) && w.length > 1); } const totalWords = words.length; if (totalWords === 0) { document.getElementById('kwResults').style.display = 'none'; document.getElementById('kwStats').textContent = 'No words found.'; return; } // Count frequency const freq = {}; words.forEach(w => { freq[w] = (freq[w] || 0) + 1; }); // Convert to array with density currentKeywords = Object.entries(freq).map(([word, count]) => ({ word, count, density: parseFloat(((count / totalWords) * 100).toFixed(2)) })); sortKeywords(kwSortKey, true); // keep current sort document.getElementById('kwStats').textContent = `${totalWords} total words · ${currentKeywords.length} unique keywords`; document.getElementById('kwResults').style.display = 'block'; renderKeywordTable(); } function sortKeywords(key, keepDir) { if (kwSortKey === key && !keepDir) { kwSortDir = kwSortDir * -1; } else { kwSortKey = key; kwSortDir = -1; // default descending } currentKeywords.sort((a, b) => { let va = a[kwSortKey], vb = b[kwSortKey]; if (typeof va === 'string') va = va.toLowerCase(); if (typeof vb === 'string') vb = vb.toLowerCase(); if (va < vb) return -1 * kwSortDir; if (va > vb) return 1 * kwSortDir; return 0; }); renderKeywordTable(); } function renderKeywordTable() { const tbody = document.getElementById('kwTableBody'); const maxCount = currentKeywords.length > 0 ? currentKeywords[0].count : 1; tbody.innerHTML = currentKeywords.slice(0, 100).map(k => { const barWidth = Math.max(2, (k.count / maxCount) * 120); return ` ${escHtml(k.word)} ${k.count} ${k.density}% `; }).join(''); if (currentKeywords.length > 100) { tbody.innerHTML += `Showing top 100 of ${currentKeywords.length} keywords`; } } // ==================== PROJECTS ==================== function saveProject() { if (!isPremium) { openPremiumModal(); return; } const title = getGenVal('genTitle'); if (!title) { showToast('Please enter a page title first.'); return; } const project = { id: Date.now(), name: title, created: new Date().toISOString(), data: {} }; const ids = ['genTitle','genDesc','genUrl','genSiteName','genImage','genTwitter','genTwitterCard','genSchemaType', 'schemaAuthor','schemaDate','schemaProdName','schemaPrice','schemaOrgName','schemaAddress']; ids.forEach(id => { const el = document.getElementById(id); if (el) project.data[id] = el.value; }); projects.unshift(project); // Keep max 50 projects if (projects.length > 50) projects = projects.slice(0, 50); localStorage.setItem(PROJECTS_KEY, JSON.stringify(projects)); showToast('Project saved!'); renderProjects(); } function deleteProject(id) { projects = projects.filter(p => p.id !== id); localStorage.setItem(PROJECTS_KEY, JSON.stringify(projects)); renderProjects(); showToast('Project deleted.'); } function loadProject(id) { const project = projects.find(p => p.id === id); if (!project) return; Object.entries(project.data).forEach(([id, val]) => { const el = document.getElementById(id); if (el) el.value = val; }); // Switch to generator tab document.querySelectorAll('.nav-tab').forEach(t => t.classList.remove('active')); document.querySelector('[data-panel="generator"]').classList.add('active'); document.querySelectorAll('.panel').forEach(p => p.classList.remove('active')); document.getElementById('panel-generator').classList.add('active'); updateGenerator(); showToast('Project loaded!'); } function renderProjects() { const list = document.getElementById('projectList'); if (projects.length === 0) { list.innerHTML = '

No saved projects yet. Use the Generator tab to create and save a project.

'; return; } list.innerHTML = projects.map(p => `
${escHtml(p.name)}
Saved ${new Date(p.created).toLocaleDateString()}
`).join(''); // Build bulk CSV updateBulkCsv(); } function updateBulkCsv() { const area = document.getElementById('exportArea'); if (!area || projects.length === 0) { if (area) area.style.display = 'none'; return; } area.style.display = 'block'; const rows = [['Project','Title','Description','URL','OG Title','OG Desc','OG Image','Twitter Card']]; projects.forEach(p => { rows.push([ p.name, p.data.genTitle || '', p.data.genDesc || '', p.data.genUrl || '', p.data.genTitle || '', p.data.genDesc || '', p.data.genImage || '', p.data.genTwitterCard || '' ]); }); document.getElementById('csvOutput').value = rows.map(r => r.map(c => '"' + (c||'').replace(/"/g,'""') + '"').join(',')).join('\n'); } function downloadCsv() { const csv = document.getElementById('csvOutput').value; if (!csv) return; downloadBlob(csv, 'seo-projects.csv', 'text/csv'); showToast('Projects CSV downloaded!'); } // ==================== INIT CALLS ==================== renderProjects(); // Auto-simulate premium for testing (comment out in production): // localStorage.setItem(PREMIUM_KEY, 'true'); isPremium = true; updatePremiumUI();