Skip to Content
PluginsLW SEO

LW SEO

Lightweight SEO plugin for WordPress - minimal footprint, maximum impact.

Overview

Requires WordPress6.0+
Requires PHP8.2+
Tested up to6.7
LicenseGPL-2.0-or-later
GitHublwplugins/lw-seo 

Quick Start

  1. Install and activate the plugin
  2. Navigate to LW Plugins > SEO
  3. Configure title templates in the General tab
  4. Upload a default social image in the Social tab
  5. Enable the Sitemap in the Sitemap tab

Installation

Via WordPress Admin

  1. Go to Plugins > Add New
  2. Search for “LW SEO”
  3. Click Install Now
  4. Click Activate

Via Composer

composer require lwplugins/lw-seo

The plugin will be installed to wp-content/plugins/lw-seo/.

Manual Installation

  1. Download the plugin from GitHub Releases 
  2. Upload the lw-seo folder to /wp-content/plugins/
  3. Activate via Plugins menu in WordPress admin

After Installation

  1. Navigate to LW Plugins > SEO in the admin menu
  2. Configure your settings across the available tabs
  3. If using permalinks, visit Settings > Permalinks and click Save to flush rewrite rules

Conflict Detection

LW SEO automatically detects and disables its output when the following SEO plugins are active:

  • Yoast SEO
  • Rank Math
  • All in One SEO

This prevents duplicate meta tags and conflicts.

Uninstallation

  1. Deactivate the plugin via Plugins menu
  2. Delete the plugin
  3. Plugin settings are stored in wp_options table under lw_seo_options and will remain unless manually removed

Settings

Configure under LW Plugins > SEO. The settings page uses a tabbed interface:

TabDescription
GeneralTitle templates, separator, site name format
ContentMeta descriptions, canonical URLs, robots meta
SocialOpen Graph, Twitter Cards, default social image
SitemapPost types and taxonomies to include
AI/LLMContent Signals, llms.txt, AI crawler blocking, Markdown endpoint
LocalLocalBusiness schema, address, hours
WooCommerceProduct SEO settings (when WooCommerce is active)
AdvancedSchema.org, head cleanup, verification codes, redirects
RedirectsURL Redirect Manager (301, 302, 307, 410, 451)
ImportRankMath SEO data migration

General Settings

Navigate to LW Plugins > SEO > General to configure basic SEO settings.

Title Separator

Choose the character used between title parts. Default is |.

Available options: |, -, >, >>, *, /

Example: “My Post Title | Site Name”

Title Templates

Configure how page titles are generated using template variables.

Homepage Title

Template for your site’s front page.

Default: %%sitename%% %%sep%% %%sitedesc%%

Example output: “My Website | Your tagline here”

Post Title

Template for single blog posts.

Default: %%title%% %%sep%% %%sitename%%

Example output: “How to Configure SEO | My Website”

Page Title

Template for static pages.

Default: %%title%% %%sep%% %%sitename%%

Archive Title

Template for category, tag, and date archives.

Default: %%term_title%% %%sep%% %%sitename%%

Search Results Title

Template for search result pages.

Default: %%searchphrase%% %%sep%% %%sitename%%

Example output: “wordpress plugins | My Website”

Tips

  • Keep titles under 60 characters for best display in search results
  • Include your brand/site name for recognition
  • Use the separator to create visual hierarchy
  • Test how your titles appear using browser dev tools or SEO testing tools

Content SEO Settings

Navigate to LW Plugins > SEO > Content to configure content-related SEO options.

Meta Descriptions

Auto-generate Meta Descriptions

When enabled, the plugin automatically creates meta descriptions from post content when no custom description is set.

  • Extracts the first ~155 characters from post content
  • Strips HTML tags and shortcodes
  • Falls back to excerpt if available

Custom Meta Descriptions

Set custom meta descriptions per post/page using the SEO meta box in the editor:

  1. Edit any post or page
  2. Scroll to the LW SEO meta box
  3. Enter your custom meta description
  4. Save the post

Canonical URLs

Enable Canonical URLs

Adds <link rel="canonical"> tags to prevent duplicate content issues.

  • Automatically generates canonical URL for each page
  • Points search engines to the preferred version of a page
  • Handles pagination correctly

Custom Canonical URLs

Override the automatic canonical URL per post/page:

  1. Edit the post/page
  2. In the LW SEO meta box, find “Canonical URL”
  3. Enter the full URL you want as canonical
  4. Save

Robots Meta

Default Robots Settings

Configure default robots meta tags for different content types.

Per-Post Robots

Override robots settings per post/page:

  1. Edit the post/page
  2. In the LW SEO meta box, find “Robots”
  3. Select desired options:
    • noindex - Don’t show in search results
    • nofollow - Don’t follow links on this page

SEO Meta Box

The LW SEO meta box appears on post/page edit screens and includes:

FieldDescription
SEO TitleCustom title (overrides template)
Meta DescriptionCustom meta description
Canonical URLCustom canonical URL
Robotsnoindex/nofollow settings
OG TitleCustom Open Graph title
OG DescriptionCustom Open Graph description
OG ImageCustom social sharing image

Tips

  • Write unique meta descriptions for important pages
  • Keep meta descriptions between 120-155 characters
  • Include a call-to-action in descriptions
  • Use noindex for thin content, thank you pages, or internal search results

Social Media Settings

Navigate to LW Plugins > SEO > Social to configure Open Graph and Twitter Card settings.

Open Graph

Open Graph tags control how your content appears when shared on Facebook, LinkedIn, and other social platforms.

Enable Open Graph

Toggle Open Graph meta tags on/off. Enabled by default.

Default Social Image

Upload a fallback image used when posts don’t have a featured image.

Recommended specifications:

  • Size: 1200 x 630 pixels
  • Format: JPG or PNG
  • Max file size: 1MB

This image appears when sharing:

  • Posts without featured images
  • Archive pages
  • The homepage

Per-Post Open Graph

Customize Open Graph data per post in the SEO meta box:

FieldDescription
OG TitleCustom title for social sharing
OG DescriptionCustom description for social sharing
OG ImageCustom image for social sharing

Twitter Cards

Twitter Cards provide rich media experiences when your content is shared on Twitter/X.

Enable Twitter Cards

Toggle Twitter Card meta tags on/off. Enabled by default.

Twitter Card Type

Choose the card format:

TypeDescription
SummarySmall square image with title and description
Summary Large ImageLarge image above title and description

Recommendation: Use “Summary Large Image” for better visibility.

Twitter Username

Enter your Twitter/X username (without @) for attribution.

Example: lwplugins

This adds twitter:site and twitter:creator meta tags.

Generated Meta Tags

When enabled, the plugin outputs:

<!-- Open Graph --> <meta property="og:title" content="Post Title"> <meta property="og:description" content="Post description..."> <meta property="og:image" content="https://example.com/image.jpg"> <meta property="og:url" content="https://example.com/post/"> <meta property="og:type" content="article"> <meta property="og:site_name" content="Site Name"> <!-- Twitter Cards --> <meta name="twitter:card" content="summary_large_image"> <meta name="twitter:title" content="Post Title"> <meta name="twitter:description" content="Post description..."> <meta name="twitter:image" content="https://example.com/image.jpg"> <meta name="twitter:site" content="@lwplugins">

Testing Social Media Tags

Validate your Open Graph and Twitter Card implementation:

Tips

  • Always set a default social image
  • Use high-quality images for better engagement
  • Keep OG descriptions under 200 characters
  • Test shares before launching campaigns

Sitemap Settings

Navigate to LW Plugins > SEO > Sitemap to configure XML sitemap generation.

Enable Sitemap

Toggle XML sitemap generation on/off.

When enabled, your sitemap is available at:

https://yoursite.com/sitemap.xml

Sitemap URL

After enabling, flush rewrite rules to activate:

  1. Go to Settings > Permalinks
  2. Click Save Changes (no changes needed)

Include Content Types

Select which content types to include in your sitemap:

  • Posts - Include blog posts. Enabled by default.
  • Pages - Include static pages. Enabled by default.
  • Custom Post Types - If you have custom post types (e.g., products, portfolio), they appear here. Enable as needed.

Include Taxonomies

Select which taxonomies to include:

  • Categories - Include category archive pages.
  • Tags - Include tag archive pages.
  • Custom Taxonomies - Additional taxonomies appear here if registered.

Exclude Specific Content

Exclude Posts/Pages

Exclude individual items from the sitemap:

  1. Edit the post/page
  2. In the LW SEO meta box, check “Exclude from Sitemap”
  3. Save

Exclude by ID

Enter comma-separated post IDs to exclude:

42, 156, 789

WooCommerce Sitemap Integration

When WooCommerce is active, additional options appear:

OptionDescription
ProductsInclude product pages
Product CategoriesInclude product category archives
Product TagsInclude product tag archives

Sitemap Structure

The generated sitemap follows the sitemaps.org  protocol:

<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc>https://yoursite.com/</loc> <lastmod>2024-01-15T10:30:00+00:00</lastmod> <changefreq>daily</changefreq> <priority>1.0</priority> </url> <url> <loc>https://yoursite.com/sample-post/</loc> <lastmod>2024-01-14T15:20:00+00:00</lastmod> <changefreq>monthly</changefreq> <priority>0.8</priority> </url> </urlset>

Submitting to Search Engines

Google Search Console

  1. Go to Search Console 
  2. Select your property
  3. Navigate to Sitemaps
  4. Enter sitemap.xml and click Submit

Bing Webmaster Tools

  1. Go to Bing Webmaster Tools 
  2. Select your site
  3. Navigate to Sitemaps
  4. Submit your sitemap URL

Tips

  • Keep sitemaps under 50,000 URLs
  • Exclude thin content and duplicate pages
  • Update sitemap after major content changes
  • Monitor indexing status in Search Console

AI/LLM Settings

Navigate to LW Plugins > SEO > AI/LLM to control AI content signals, crawler access, and llms.txt generation.

Content Signals

What are Content Signals?

Content Signals tell AI agents how they may use your content. They are sent as:

  • HTTP header (X-Content-Signals) on every response
  • HTML meta tag (<meta name="ai-content-signals">) in the <head>

Inspired by Cloudflare - Markdown for Agents .

Global Settings

Three checkboxes, all enabled by default:

SettingHeader keyDescription
AI Trainingai-trainAllow AI systems to use content for model training
AI Input (RAG)ai-inputAllow AI systems to use content for generating responses
SearchsearchAllow content to appear in AI search results

Per-Content Override

Each post/page has an AI Content Signals section in the LW SEO meta box (post editor). Three dropdowns:

  • Default - uses the global setting
  • Yes - explicitly allow
  • No - explicitly deny

Output Example

HTTP header (every response):

X-Content-Signals: ai-train=yes, ai-input=yes, search=yes

HTML meta tag:

<meta name="ai-content-signals" content="ai-train=yes, ai-input=no, search=yes" />

Resolution Order

  1. Per-post meta value (if not “default”)
  2. lw_seo_content_signals filter (if applied)
  3. Global setting from AI/LLM tab

Markdown Endpoint (/md)

What is it?

Every post, page, taxonomy, and WooCommerce product is available as clean markdown. AI agents can consume content without parsing HTML.

Three Ways to Access

1. URL suffix:

https://yoursite.com/hello-world/md/ https://yoursite.com/category/news/md/ https://yoursite.com/product/shoes/md/

2. Query parameter:

https://yoursite.com/?p=123&format=md

3. Accept header (singular pages only):

curl -H "Accept: text/markdown" https://yoursite.com/hello-world/

Response Format

Headers:

Content-Type: text/markdown; charset=UTF-8 X-Content-Signals: ai-train=yes, ai-input=yes, search=yes X-Markdown-Tokens: 1250

Body - YAML frontmatter + markdown:

--- title: "Hello World" url: "https://yoursite.com/hello-world/" date: "2026-03-21" modified: "2026-03-21" author: "Admin" language: "hu_HU" categories: ["WordPress"] tags: ["example"] featured_image: "https://yoursite.com/image.jpg" --- # Hello World Your post content converted to markdown...

Security

  • Only publish and private (with capability) posts are accessible
  • Password-protected posts return 403
  • Non-public taxonomies return 404
  • Draft, pending, future posts return 404

Flush Rewrite Rules

After activating the plugin, go to Settings > Permalinks > Save Changes to flush rewrite rules. Without this, the /md/ URLs won’t work.

Renderers

Content TypeRendererFrontmatter
Post/PagePostRenderertitle, url, date, modified, author, language, categories, tags, featured_image, excerpt
WC ProductProductRenderertitle, url, language, price, sku, stock_status, categories, featured_image
TaxonomyTaxonomyRenderertitle, url, type, post_count, language, parent

Response Headers

HeaderDescription
Content-Type: text/markdown; charset=UTF-8MIME type
X-Content-Signals: ai-train=yes, ai-input=yes, search=yesContent permission signals
X-Markdown-Tokens: 1250Approximate token count (mb_strlen / 4)

HTML to Markdown Conversion

The HtmlToMarkdown helper converts post_content HTML to markdown:

HTMLMarkdown
<h1>-<h6>#-######
<p>Double newline
<a>[text](url)
<strong>, <b>**text**
<em>, <i>*text*
<ul><li>- item
<ol><li>1. item
<blockquote>> text
<code>`code`
<pre>Fenced code block
<img>![alt](src)
<table>Markdown table
UnsupportedPlain text fallback

llms.txt

What is llms.txt?

The llms.txt file provides structured information about your website to AI systems. Similar to robots.txt but designed for LLMs.

Learn more: llmstxt.org 

When enabled, available at: https://yoursite.com/llms.txt

Content

The generated file includes:

  • Site name and description
  • Content summary (post/page/category counts)
  • Important pages
  • Recent posts
  • Sitemap reference

AI Crawler Control

Block or allow specific AI crawlers via robots.txt rules.

Available Crawlers

CrawlerCompanyPurpose
GPTBotOpenAIChatGPT training data
ChatGPT-UserOpenAIChatGPT browsing
Claude-WebAnthropicClaude training
Google-ExtendedGoogleGemini training
BytespiderByteDanceTikTok AI training
CCBotCommon CrawlOpen dataset
PerplexityBotPerplexityAI search engine
Cohere-AICohereAI training

Blocking

Check “Block” to add a Disallow rule to robots.txt:

User-agent: GPTBot Disallow: /

Content Signals vs Crawler Blocking

FeatureScopeEnforcement
Content SignalsPer-contentAdvisory (agent decides)
AI Crawler BlockingSite-wideHard block (robots.txt)

Content Signals are advisory - the AI agent decides whether to respect them. Crawler blocking via robots.txt is a hard block (though also technically advisory).

Technical Notes

  • robots.txt blocking doesn’t remove already-crawled content
  • Content Signals follow the emerging web standard for AI content permissions
  • The /md endpoint respects Content Signals but doesn’t block content - it includes the signals in the response headers
  • X-Markdown-Tokens is an approximate token count (mb_strlen / 4)

Local SEO Settings

Navigate to LW Plugins > SEO > Local SEO to configure LocalBusiness schema markup.

Overview

Local SEO helps your business appear in local search results and Google Maps. The plugin generates Schema.org LocalBusiness structured data.

Enable Local SEO

Toggle LocalBusiness schema generation on/off.

Business Information

Business Type

Select the Schema.org type that best describes your business. 100+ options available including:

CategoryExamples
FoodRestaurant, Bakery, CafeOrCoffeeShop, FastFoodRestaurant
RetailStore, ClothingStore, ElectronicsStore, GroceryStore
ServicesAutoRepair, HairSalon, DaySpa, RealEstateAgent
HealthDentist, Pharmacy, MedicalClinic, Optician
EntertainmentMovieTheater, NightClub, SportingGoodsStore
ProfessionalAccountingService, Attorney, FinancialService

Business Name

Your official business name. Leave empty to use the site name.

Business Description

A short description of your business (1-2 sentences).

Price Range

Indicate your price level:

  • $ - Budget
  • $$ - Moderate
  • $$$ - Expensive
  • $$$$ - Luxury

Address

Enter your physical business address:

FieldDescriptionExample
Street AddressPrimary street address123 Main Street
Address Line 2Suite, unit, floorSuite 100
CityCity/localityBudapest
State/RegionState, province, regionBudapest
Postal CodeZIP/postal code1051
CountryISO 3166-1 alpha-2 codeHU

Country codes: US, HU, DE, GB, FR, etc.

Contact Information

Phone

Enter your business phone number in international format:

+36 1 234 5678

Email

Business contact email:

Opening Hours

Enable Opening Hours

Toggle opening hours in the schema output.

Configure Hours

For each day of the week:

  1. Closed - Check if closed on this day
  2. Open time - Opening time (e.g., 09:00)
  3. Close time - Closing time (e.g., 17:00)

Use 24-hour format for times.

Coordinates

Latitude & Longitude

Enter your exact location coordinates for map accuracy.

Finding coordinates:

  1. Go to Google Maps 
  2. Right-click on your location
  3. Click the coordinates to copy them
  4. First number is latitude, second is longitude

Example:

  • Latitude: 47.4979
  • Longitude: 19.0402

Generated Schema

The plugin outputs JSON-LD structured data:

{ "@context": "https://schema.org", "@type": "Restaurant", "name": "My Business", "description": "A great local business", "address": { "@type": "PostalAddress", "streetAddress": "123 Main Street", "addressLocality": "Budapest", "postalCode": "1051", "addressCountry": "HU" }, "telephone": "+36 1 234 5678", "email": "[email protected]", "priceRange": "$$", "geo": { "@type": "GeoCoordinates", "latitude": 47.4979, "longitude": 19.0402 }, "openingHoursSpecification": [ { "@type": "OpeningHoursSpecification", "dayOfWeek": "Monday", "opens": "09:00", "closes": "17:00" } ] }

Testing Local SEO

Validate your Local SEO markup:

Tips

  • Use the most specific business type available
  • Keep business info consistent across the web (NAP consistency)
  • Add coordinates for accurate map placement
  • Update hours for holidays using your Google Business Profile

Redirect Manager

Navigate to LW Plugins > SEO > Redirects to create and manage URL redirects.

Overview

The Redirect Manager helps you:

  • Prevent 404 errors when URLs change
  • Preserve SEO value when moving content
  • Handle deleted pages gracefully
  • Set up complex redirect patterns with regex

Enable Redirects

Toggle redirect processing on/off. When disabled, no redirect rules are applied.

Adding a Redirect

Source URL

The old URL path that should redirect. Enter just the path without domain:

/old-page/ /blog/2023/old-post/ /products/discontinued-item/

Destination URL

The new URL to redirect to. Can be:

  • Relative path: /new-page/
  • Full URL: https://example.com/new-page/
  • External URL: https://other-site.com/page/

Leave empty for 410/451 status codes.

Redirect Types

CodeNameUse Case
301Moved PermanentlyContent permanently moved. Transfers SEO value.
302Found (Temporary)Content temporarily moved. Testing changes.
307Temporary RedirectLike 302 but preserves request method. API endpoints.
410Content DeletedContent intentionally removed. No destination.
451Unavailable For Legal ReasonsLegal takedown. GDPR, DMCA. No destination.

Best Practices

  • Use 301 for permanent moves (most common)
  • Use 302 only for temporary changes
  • Use 410 when content is intentionally deleted
  • Use 451 for legal compliance

Regex Redirects

Enable “Source is a regular expression” for pattern matching.

Example Patterns

Redirect category URLs:

Source: ^/category/(.*)$ Destination: /blog/category/$1

Redirect all blog posts:

Source: ^/blog/(\d{4})/(\d{2})/(.*)$ Destination: /articles/$1-$2-$3

Redirect product URLs:

Source: ^/shop/(.*)$ Destination: /store/$1

Capture Groups

Use $1, $2, etc. in destination to reference captured groups:

PatternInputCaptures
^/old/(.*)$/old/page$1 = page
^/(\d+)/(\w+)$/123/abc$1 = 123, $2 = abc

Managing Redirects

Redirect Table

The table shows all configured redirects:

ColumnDescription
SourceThe URL path to match
DestinationWhere to redirect
TypeHTTP status code
HitsNumber of times triggered
Last AccessedMost recent redirect time

Edit Redirect

Click Edit to modify an existing redirect. The form populates with current values.

Delete Redirect

Click Delete to remove a redirect. Confirm the deletion when prompted.

Import / Export

Export to CSV

Click Download CSV to export all redirects. Format:

source,destination,type,regex /old-page/,/new-page/,301,false ^/category/(.*)$,/blog/$1,301,true /deleted-page/,,410,false

Import from CSV

  1. Prepare a CSV file with columns: source, destination, type, regex
  2. Select the file
  3. Click Import CSV

CSV Format:

ColumnRequiredValues
sourceYesURL path or regex pattern
destinationNo*Target URL (empty for 410/451)
typeNo301, 302, 307, 410, 451 (default: 301)
regexNotrue, false, 1, 0, yes, no (default: false)

How It Works

  1. When a request comes in, the plugin checks for a matching redirect
  2. Exact matches are checked first, then regex patterns
  3. If found, the appropriate HTTP redirect is sent
  4. Hit counter is incremented for analytics

Performance

  • Redirects are stored in WordPress options (no database table)
  • Matching happens early in the request lifecycle
  • Regex patterns are compiled once per request
  • Minimal impact on non-matching URLs

Troubleshooting

Redirect Not Working

  1. Check if “Enable Redirects” is on
  2. Verify the source path is correct (starts with /)
  3. Check for conflicting redirects
  4. Clear any caching plugins
  5. Test in incognito/private browsing

Redirect Loop

A redirect pointing to itself or creating a chain that loops back:

/page-a/ -> /page-b/ /page-b/ -> /page-a/ <- Loop!

Remove one redirect to break the loop.

Regex Not Matching

  1. Test your pattern at regex101.com 
  2. Ensure special characters are escaped: \., \?, etc.
  3. Use ^ for start and $ for end anchors
  4. Check the “Invalid regex pattern” error message

Migration from Other Plugins

From Yoast SEO Premium

Export redirects from Yoast as CSV and import into LW SEO. The format is compatible.

From Redirection Plugin

  1. Export from Redirection (Tools > Export > CSV)
  2. Adjust columns to match: source, destination, type, regex
  3. Import into LW SEO

From .htaccess

Convert .htaccess rules to LW SEO redirects:

# .htaccess Redirect 301 /old-page/ /new-page/ RedirectMatch 301 ^/blog/(.*)$ /articles/$1

Becomes:

source,destination,type,regex /old-page/,/new-page/,301,false ^/blog/(.*)$,/articles/$1,301,true

Tips

  • Start with exact matches, use regex only when needed
  • Test redirects immediately after creating them
  • Review hit counts periodically to identify unused redirects
  • Export redirects before major site changes
  • Use 301 for SEO value transfer

Advanced Settings

Navigate to LW Plugins > SEO > Advanced to configure advanced SEO options.

Schema.org / JSON-LD

Organization or Person

Choose how your site is represented in schema markup:

TypeUse Case
OrganizationBusiness, company, brand
PersonPersonal blog, portfolio, individual

Organization Name

Your organization’s official name. Defaults to site name.

Upload your logo for schema markup. Recommended: 112x112 pixels minimum, square format.

Head Cleanup

Remove unnecessary elements from the <head> section to clean up your HTML.

Removes the shortlink <link> tag:

<!-- Removed: --> <link rel='shortlink' href='https://example.com/?p=123' />

Removes the Really Simple Discovery link (used by XML-RPC clients):

<!-- Removed: --> <link rel="EditURI" type="application/rsd+xml" href="https://example.com/xmlrpc.php?rsd" />

Remove Windows Live Writer Manifest

Removes the WLW manifest link:

<!-- Removed: --> <link rel="wlwmanifest" type="application/wlwmanifest+xml" href="https://example.com/wp-includes/wlwmanifest.xml" />

Remove WordPress Generator Tag

Removes the WordPress version meta tag:

<!-- Removed: --> <meta name="generator" content="WordPress 6.4" />

Security note: Hiding the WordPress version provides minimal security benefit but reduces information disclosure.

Verification Codes

Add search engine verification meta tags without editing theme files.

Google Search Console

  1. Go to Search Console 
  2. Add your property
  3. Choose “HTML tag” verification
  4. Copy the content value from the meta tag
  5. Paste into the “Google Verification” field

Bing Webmaster Tools

  1. Go to Bing Webmaster Tools 
  2. Add your site
  3. Choose “Meta tag” verification
  4. Copy the content value
  5. Paste into the “Bing Verification” field

Generated Output

<meta name="google-site-verification" content="your-google-code" /> <meta name="msvalidate.01" content="your-bing-code" />

Redirects

www/non-www Redirect

Ensure consistent URL format:

OptionDescription
No redirectLet server handle (default)
Force wwwRedirect non-www to www
Force non-wwwRedirect www to non-www

Note: This is better handled at server/hosting level for performance.

HTTPS Redirect

Force HTTPS for all pages:

OptionDescription
No redirectAllow both HTTP and HTTPS
Force HTTPSRedirect HTTP to HTTPS

Note: Ensure you have a valid SSL certificate before enabling.

Additional Meta Tags

Add custom meta tags to all pages:

<meta name="author" content="Your Name"> <meta name="copyright" content="Your Company">

Enter one meta tag per line. Tags are output in the <head> section.

Performance

Disable Plugin on Specific Pages

Disable LW SEO output on certain pages by ID:

42, 156, 789

Useful for landing pages with their own SEO setup.

Debug Mode

Enable Debug Output

When enabled, adds HTML comments showing:

  • Which title template was used
  • Meta description source (auto-generated vs custom)
  • Active schema types
<!-- LW SEO Debug: Title template: %%title%% %%sep%% %%sitename%% --> <!-- LW SEO Debug: Meta desc: auto-generated from content -->

Warning: Only enable for debugging. Disable on production sites.

Tips

  • Remove unused head elements to reduce page size
  • Always verify site ownership with search engines
  • Use HTTPS for better SEO and security
  • Test changes in staging before applying to production

WooCommerce Integration

The plugin automatically detects WooCommerce and adds product-specific SEO features.

Product-specific OpenGraph Tags

  • Price, availability, brand, condition
  • Product Open Graph type (og:type: product)

Product Schema.org Markup

  • Product structured data with reviews and offers
  • Automatic price and stock status

WooCommerce Settings Tab

When WooCommerce is active, a dedicated WooCommerce tab appears in settings for product SEO configuration.

Sitemap Inclusion

  • Products and product taxonomies can be included in the XML sitemap
  • Product categories and tags as separate sitemap entries

Template Variables

Use these variables in title templates to dynamically generate page titles.

Available Variables

VariableDescriptionExample Output
%%sitename%%Site name from Settings > GeneralMy Website
%%sitedesc%%Site tagline from Settings > GeneralJust another WordPress site
%%title%%Post/page titleHow to Configure SEO
%%sep%%Separator character (configured in settings)|
%%excerpt%%Post excerpt (first 155 chars)This post explains…
%%author%%Post author display nameJohn Doe
%%category%%Primary category nameWordPress
%%term_title%%Current taxonomy term titleTutorials
%%searchphrase%%Search query on search pageswordpress seo
%%currentdate%%Current dateJanuary 15, 2024
%%currentyear%%Current year2024
%%currentmonth%%Current month nameJanuary
%%page%%Page number for paginated contentPage 2
%%pagetotal%%Total pages for paginated content5
%%currentday%%Current day of month15

Usage Examples

Homepage

Template: %%sitename%% %%sep%% %%sitedesc%%

Output: My Website | Your tagline here

Single Post

Template: %%title%% %%sep%% %%sitename%%

Output: How to Configure SEO | My Website

Category Archive

Template: %%term_title%% %%sep%% %%sitename%%

Output: Tutorials | My Website

Search Results

Template: Search: %%searchphrase%% %%sep%% %%sitename%%

Output: Search: wordpress seo | My Website

Author Archive

Template: Posts by %%author%% %%sep%% %%sitename%%

Output: Posts by John Doe | My Website

Paginated Archives

Template: %%term_title%% %%sep%% Page %%page%% of %%pagetotal%% %%sep%% %%sitename%%

Output: Tutorials | Page 2 of 5 | My Website

Date-based Content

Template: %%title%% %%sep%% Published %%currentmonth%% %%currentyear%%

Output: My Post | Published January 2024

Context Availability

Not all variables work on every page type:

VariableHomepagePostsPagesArchivesSearch
%%sitename%%YesYesYesYesYes
%%sitedesc%%YesYesYesYesYes
%%title%%NoYesYesNoNo
%%sep%%YesYesYesYesYes
%%excerpt%%NoYesYesNoNo
%%author%%NoYesYesYes*No
%%category%%NoYesNoNoNo
%%term_title%%NoNoNoYesNo
%%searchphrase%%NoNoNoNoYes
%%currentdate%%YesYesYesYesYes
%%page%%Yes*Yes*Yes*Yes*Yes*

*Only on paginated content

Custom Title Override

Override the template for individual posts/pages:

  1. Edit the post/page
  2. Find the LW SEO meta box
  3. Enter a custom title in the “SEO Title” field
  4. This replaces the template entirely

Tips

  • Google displays ~50-60 characters
  • Important info should be at the beginning
  • Include brand name for recognition

Good patterns:

%%title%% %%sep%% %%sitename%% # Standard post %%sitename%% %%sep%% %%sitedesc%% # Homepage %%term_title%% Archives %%sep%% %%sitename%% # Archives

Avoid:

%%sitename%% %%sep%% %%title%% %%sep%% %%category%% %%sep%% %%author%% # Too long, keyword stuffing %%sitename%% # Too short, no context

Shortcodes

LW SEO provides shortcodes for displaying SEO-related content in your posts, pages, and widgets.

[lw_breadcrumbs]

Display a breadcrumb navigation trail.

Attributes:

AttributeDefaultDescription
separator>>Character between breadcrumb items
homeHomeText for home link
schematrueInclude Schema.org markup

Examples:

[lw_breadcrumbs] [lw_breadcrumbs separator=" / " home="Start"] [lw_breadcrumbs schema="false"]

Output:

<nav class="lw-seo-breadcrumbs" aria-label="Breadcrumb"> <ol itemscope itemtype="https://schema.org/BreadcrumbList"> <li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem"> <a itemprop="item" href="https://example.com/"> <span itemprop="name">Home</span> </a> <meta itemprop="position" content="1" /> </li> <li>>></li> <li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem"> <span itemprop="name">Current Page</span> <meta itemprop="position" content="2" /> </li> </ol> </nav>

PHP Function:

<?php if ( function_exists( 'lw_seo_breadcrumbs' ) ) { lw_seo_breadcrumbs(); } ?>

Local SEO Shortcodes

These shortcodes display business information configured in LW Plugins > SEO > Local SEO.

[lw_address]

Display your business address.

Attributes:

AttributeDefaultDescription
formatfullfull includes country, short excludes it
schematrueInclude Schema.org microdata

Examples:

[lw_address] [lw_address format="short"] [lw_address schema="false"]

Output:

<address class="lw-seo-address" itemscope itemtype="https://schema.org/PostalAddress"> <span itemprop="streetAddress">123 Main Street, Suite 100</span><br> <span itemprop="postalCode">1051</span> <span itemprop="addressLocality">Budapest</span>, <span itemprop="addressRegion">Budapest</span><br> <span itemprop="addressCountry">HU</span> </address>

[lw_phone]

Display your business phone number.

Attributes:

AttributeDefaultDescription
linktrueWrap in clickable tel: link
schematrueInclude Schema.org microdata

Examples:

[lw_phone] [lw_phone link="false"] [lw_phone schema="false"]

Output:

<span class="lw-seo-phone" itemprop="telephone"> <a href="tel:+3612345678">+36 1 234 5678</a> </span>

[lw_email]

Display your business email address.

Attributes:

AttributeDefaultDescription
linktrueWrap in clickable mailto: link
schematrueInclude Schema.org microdata

Examples:

[lw_email] [lw_email link="false"]

Output:

<span class="lw-seo-email" itemprop="email"> <a href="mailto:[email protected]">[email protected]</a> </span>

[lw_hours]

Display your business opening hours.

Attributes:

AttributeDefaultDescription
formattabletable or list
schematrueInclude Schema.org microdata

Examples:

[lw_hours] [lw_hours format="list"] [lw_hours format="table" schema="false"]

Table Output:

<div class="lw-seo-hours"> <table class="lw-seo-hours-table"> <tbody> <tr> <th>Monday</th> <td> <time itemprop="openingHours" datetime="Monday 09:00-17:00"> 09:00 - 17:00 </time> </td> </tr> <tr> <th>Saturday</th> <td>Closed</td> </tr> </tbody> </table> </div>

List Output:

<div class="lw-seo-hours"> <ul class="lw-seo-hours-list"> <li><strong>Monday:</strong> 09:00 - 17:00</li> <li><strong>Saturday:</strong> Closed</li> </ul> </div>

[lw_map]

Display a Google Maps embed of your business location.

Requires: Latitude and longitude set in Local SEO settings.

Attributes:

AttributeDefaultDescription
width100%Map width (px or %)
height400Map height (px)
zoom15Zoom level (1-20)

Examples:

[lw_map] [lw_map height="300" zoom="17"] [lw_map width="600" height="400"]

Output:

<div class="lw-seo-map" style="width:100%;height:400px;"> <iframe src="https://maps.google.com/maps?q=123+Main+Street%2C+Budapest&output=embed&z=15" width="100%" height="100%" style="border:0;" allowfullscreen="" loading="lazy" referrerpolicy="no-referrer-when-downgrade" title="My Business"> </iframe> </div>

Styling Shortcodes

Add custom CSS to style shortcode output:

/* Breadcrumbs */ .lw-seo-breadcrumbs { padding: 10px 0; font-size: 14px; } .lw-seo-breadcrumbs ol { list-style: none; display: flex; flex-wrap: wrap; gap: 5px; margin: 0; padding: 0; } /* Address */ .lw-seo-address { font-style: normal; line-height: 1.6; } /* Hours Table */ .lw-seo-hours-table { width: 100%; border-collapse: collapse; } .lw-seo-hours-table th, .lw-seo-hours-table td { padding: 8px; border-bottom: 1px solid #eee; text-align: left; } /* Map */ .lw-seo-map { border-radius: 8px; overflow: hidden; }

Tips

  • Use shortcodes in widgets for sidebar/footer display
  • Combine multiple shortcodes for a complete contact section
  • Enable schema markup for better search visibility
  • Test shortcode output after changing Local SEO settings

REST API

REST API endpoints for headless WordPress implementations. Get SEO meta data, Schema.org JSON-LD, and breadcrumbs in JSON format.

Base URL

/wp-json/lw-seo/v1/

Authentication

All endpoints are publicly accessible (read-only). No authentication required for published content.

For draft/private content, use WordPress Application Passwords with Basic Auth:

Authorization: Basic base64(username:app_password)

Get Post SEO Meta

Returns SEO meta data for a post, page, or custom post type.

Endpoint: GET /wp-json/lw-seo/v1/meta/{id}

Parameters:

ParameterTypeRequiredDescription
idintegerYesPost ID

Response:

{ "title": "Post Title - Site Name", "description": "Meta description for the post...", "canonical": "https://example.com/post-slug/", "robots": { "index": "index", "follow": "follow" }, "og": { "locale": "en_US", "type": "article", "title": "Post Title", "description": "OG description...", "url": "https://example.com/post-slug/", "site_name": "Site Name", "image": "https://example.com/wp-content/uploads/image.jpg", "article:published_time": "2024-01-15T10:30:00+00:00", "article:modified_time": "2024-01-16T14:22:00+00:00", "article:author": "John Doe" }, "twitter": { "card": "summary_large_image", "title": "Post Title", "description": "Twitter description...", "image": "https://example.com/wp-content/uploads/image.jpg" } }

Example:

curl "https://example.com/wp-json/lw-seo/v1/meta/123"

Errors:

  • 404 - Post not found
  • 403 - Post is not accessible (draft/private without auth)

Get Term SEO Meta

Returns SEO meta data for a taxonomy term (category, tag, custom taxonomy).

Endpoint: GET /wp-json/lw-seo/v1/meta/term/{id}

Parameters:

ParameterTypeRequiredDescription
idintegerYesTerm ID
taxonomystringNoTaxonomy name (default: category)

Response:

{ "title": "Category Name - Site Name", "description": "Category description...", "canonical": "https://example.com/category/slug/", "robots": { "index": "index", "follow": "follow" }, "og": { "locale": "en_US", "type": "website", "title": "Category Name - Site Name", "description": "Category description...", "url": "https://example.com/category/slug/", "site_name": "Site Name", "image": "https://example.com/wp-content/uploads/default-og.jpg" }, "twitter": { "card": "summary_large_image", "title": "Category Name - Site Name", "description": "Category description...", "image": "https://example.com/wp-content/uploads/default-og.jpg" } }

Examples:

# Get category meta curl "https://example.com/wp-json/lw-seo/v1/meta/term/5" # Get custom taxonomy term meta curl "https://example.com/wp-json/lw-seo/v1/meta/term/10?taxonomy=product_cat"

Errors:

  • 404 - Term not found

Get Author SEO Meta

Returns SEO meta data for an author archive.

Endpoint: GET /wp-json/lw-seo/v1/meta/author/{id}

Parameters:

ParameterTypeRequiredDescription
idintegerYesUser/Author ID

Response:

{ "title": "John Doe - Site Name", "description": "Author bio description...", "canonical": "https://example.com/author/johndoe/", "robots": { "index": "index", "follow": "follow" }, "og": { "locale": "en_US", "type": "profile", "title": "John Doe - Site Name", "description": "Author bio description...", "url": "https://example.com/author/johndoe/", "site_name": "Site Name", "image": "https://secure.gravatar.com/avatar/..." }, "twitter": { "card": "summary", "title": "John Doe - Site Name", "description": "Author bio description...", "image": "https://secure.gravatar.com/avatar/..." } }

Example:

curl "https://example.com/wp-json/lw-seo/v1/meta/author/1"

Errors:

  • 404 - Author not found

Get Post Schema

Returns Schema.org JSON-LD structured data for a post.

Endpoint: GET /wp-json/lw-seo/v1/schema/{id}

Parameters:

ParameterTypeRequiredDescription
idintegerYesPost ID

Response:

{ "@context": "https://schema.org", "@graph": [ { "@type": "WebSite", "@id": "https://example.com/#website", "url": "https://example.com/", "name": "Site Name", "description": "Site tagline", "inLanguage": "en_US", "potentialAction": { "@type": "SearchAction", "target": { "@type": "EntryPoint", "urlTemplate": "https://example.com/?s={search_term_string}" }, "query-input": "required name=search_term_string" }, "publisher": { "@id": "https://example.com/#organization" } }, { "@type": "Organization", "@id": "https://example.com/#organization", "name": "Company Name", "url": "https://example.com/", "logo": { "@type": "ImageObject", "@id": "https://example.com/#logo", "url": "https://example.com/logo.png" }, "sameAs": [ "https://facebook.com/example", "https://twitter.com/example" ] }, { "@type": "WebPage", "@id": "https://example.com/post-slug/#webpage", "url": "https://example.com/post-slug/", "name": "Post Title", "isPartOf": {"@id": "https://example.com/#website"}, "inLanguage": "en_US", "datePublished": "2024-01-15T10:30:00+00:00", "dateModified": "2024-01-16T14:22:00+00:00", "primaryImageOfPage": {"@id": "https://example.com/#primaryimage"}, "thumbnailUrl": "https://example.com/wp-content/uploads/image.jpg" }, { "@type": "Article", "@id": "https://example.com/post-slug/#article", "headline": "Post Title", "datePublished": "2024-01-15T10:30:00+00:00", "dateModified": "2024-01-16T14:22:00+00:00", "mainEntityOfPage": {"@id": "https://example.com/post-slug/#webpage"}, "wordCount": 1250, "inLanguage": "en_US", "author": { "@type": "Person", "@id": "https://example.com/author/johndoe/#author", "name": "John Doe", "url": "https://example.com/author/johndoe/" }, "publisher": {"@id": "https://example.com/#organization"}, "image": { "@type": "ImageObject", "@id": "https://example.com/#primaryimage", "url": "https://example.com/wp-content/uploads/image.jpg" }, "keywords": "Category 1, Category 2" } ] }

Example:

curl "https://example.com/wp-json/lw-seo/v1/schema/123"

Errors:

  • 404 - Post not found
  • 403 - Post is not accessible

Get Post Breadcrumbs

Returns breadcrumb navigation items for a post.

Endpoint: GET /wp-json/lw-seo/v1/breadcrumbs/{id}

Parameters:

ParameterTypeRequiredDescription
idintegerYesPost ID

Response:

[ { "title": "Home", "url": "https://example.com/", "position": 1 }, { "title": "Category Name", "url": "https://example.com/category/slug/", "position": 2 }, { "title": "Post Title", "url": "https://example.com/post-slug/", "position": 3 } ]

Example:

curl "https://example.com/wp-json/lw-seo/v1/breadcrumbs/123"

Errors:

  • 404 - Post not found
  • 403 - Post is not accessible

Usage Examples

Next.js / React

// Fetch SEO data for a post async function getPostSEO(postId) { const response = await fetch( `https://example.com/wp-json/lw-seo/v1/meta/${postId}` ); return response.json(); } // Use in Next.js page export async function generateMetadata({ params }) { const seo = await getPostSEO(params.id); return { title: seo.title, description: seo.description, alternates: { canonical: seo.canonical, }, openGraph: { title: seo.og.title, description: seo.og.description, url: seo.og.url, siteName: seo.og.site_name, images: seo.og.image ? [seo.og.image] : [], locale: seo.og.locale, type: seo.og.type, }, twitter: { card: seo.twitter.card, title: seo.twitter.title, description: seo.twitter.description, images: seo.twitter.image ? [seo.twitter.image] : [], }, robots: { index: seo.robots.index === 'index', follow: seo.robots.follow === 'follow', }, }; }

Nuxt.js / Vue

// composables/useSeo.js export async function useSeo(postId) { const { data: seo } = await useFetch( `https://example.com/wp-json/lw-seo/v1/meta/${postId}` ); useHead({ title: seo.value.title, meta: [ { name: 'description', content: seo.value.description }, { property: 'og:title', content: seo.value.og.title }, { property: 'og:description', content: seo.value.og.description }, { property: 'og:image', content: seo.value.og.image }, { name: 'twitter:card', content: seo.value.twitter.card }, ], link: [ { rel: 'canonical', href: seo.value.canonical }, ], }); return seo; }

PHP (Server-side)

// Fetch SEO data from another WordPress site $response = wp_remote_get( 'https://example.com/wp-json/lw-seo/v1/meta/123' ); $seo = json_decode( wp_remote_retrieve_body( $response ), true ); // Use the data echo '<title>' . esc_html( $seo['title'] ) . '</title>'; echo '<meta name="description" content="' . esc_attr( $seo['description'] ) . '">';

Error Responses

All error responses follow this format:

{ "code": "error_code", "message": "Human readable error message", "data": { "status": 404 } }
CodeStatusDescription
post_not_found404The requested post does not exist
post_not_accessible403Post is draft/private and user lacks permission
term_not_found404The requested term does not exist
author_not_found404The requested author does not exist

For Developers

Technical documentation for developers extending or integrating with LW SEO.

Namespace & Autoloading

The plugin uses PSR-4 autoloading:

namespace LightweightPlugins\SEO; // Classes are in includes/ directory // LightweightPlugins\SEO\Plugin -> includes/Plugin.php // LightweightPlugins\SEO\Admin\Settings -> includes/Admin/Settings.php

Hooks & Filters

Title Filters

/** * Filter the generated title. * * @param string $title The generated title. * @param int $post_id Post ID (0 for non-singular pages). */ $title = apply_filters( 'lw_seo_title', $title, $post_id ); /** * Filter the title separator. * * @param string $separator The separator character. */ $separator = apply_filters( 'lw_seo_title_separator', $separator );

Meta Description Filters

/** * Filter the meta description. * * @param string $description The meta description. * @param int $post_id Post ID. */ $description = apply_filters( 'lw_seo_meta_description', $description, $post_id ); /** * Filter auto-generated description length. * * @param int $length Maximum characters. */ $length = apply_filters( 'lw_seo_description_length', 155 );

Open Graph Filters

/** * Filter Open Graph meta tags. * * @param array $og_tags Array of OG tags. * @param int $post_id Post ID. */ $og_tags = apply_filters( 'lw_seo_og_tags', $og_tags, $post_id ); /** * Filter the default social image URL. * * @param string $image_url Image URL. */ $image_url = apply_filters( 'lw_seo_default_social_image', $image_url );

Schema Filters

/** * Filter JSON-LD schema output. * * @param array $schema Schema data array. * @param string $type Schema type (Organization, LocalBusiness, etc). */ $schema = apply_filters( 'lw_seo_schema', $schema, $type ); /** * Filter LocalBusiness schema. * * @param array $schema LocalBusiness schema array. */ $schema = apply_filters( 'lw_seo_local_schema', $schema );

Sitemap Filters

/** * Filter sitemap URLs before output. * * @param array $urls Array of URL entries. */ $urls = apply_filters( 'lw_seo_sitemap_urls', $urls ); /** * Filter post types included in sitemap. * * @param array $post_types Post type names. */ $post_types = apply_filters( 'lw_seo_sitemap_post_types', $post_types ); /** * Exclude specific post from sitemap. * * @param bool $exclude Whether to exclude. * @param int $post_id Post ID. */ $exclude = apply_filters( 'lw_seo_sitemap_exclude_post', false, $post_id );
/** * Filter breadcrumb items. * * @param array $items Breadcrumb items array. */ $items = apply_filters( 'lw_seo_breadcrumb_items', $items ); /** * Filter breadcrumb separator. * * @param string $separator Separator HTML. */ $separator = apply_filters( 'lw_seo_breadcrumb_separator', '>>' );

Markdown Endpoint Filters

/** * Control whether a request should receive markdown output. */ add_filter( 'lw_seo_markdown_is_supported', function ( bool $supported, WP_Query $query ): bool { // Disable markdown for a specific post type. if ( $query->is_singular( 'secret_docs' ) ) { return false; } return $supported; }, 10, 2 ); /** * Add or modify YAML frontmatter fields. */ add_filter( 'lw_seo_markdown_frontmatter', function ( array $data, WP_Post|WP_Term $object ): array { // Add custom field to product frontmatter. if ( $object instanceof WP_Post && 'product' === $object->post_type ) { $data['brand'] = get_post_meta( $object->ID, 'brand', true ); } return $data; }, 10, 2 ); /** * Modify or replace the markdown body content. */ add_filter( 'lw_seo_markdown_body', function ( string $body, WP_Post|WP_Term $object ): string { // Append related posts to the body. if ( $object instanceof WP_Post ) { $body .= "\n## Related\n\n"; // ... add related post links ... } return $body; }, 10, 2 ); /** * Override Content Signals for any content type. */ add_filter( 'lw_seo_content_signals', function ( array $signals, WP_Post|WP_Term|null $object ): array { // Disable AI training for premium posts. if ( $object instanceof WP_Post && has_tag( 'premium', $object ) ) { $signals['ai-train'] = 'no'; } return $signals; }, 10, 2 ); /** * Full output override (escape hatch). */ add_filter( 'lw_seo_markdown_output', function ( string $output, WP_Post|WP_Term $object ): string { // Completely replace output for a custom post type. if ( $object instanceof WP_Post && 'event' === $object->post_type ) { return "---\ntitle: ...\n---\n\nCustom event markdown..."; } return $output; }, 10, 2 );

Action Hooks

/** * Fires before SEO meta tags are output. */ do_action( 'lw_seo_before_meta' ); /** * Fires after SEO meta tags are output. */ do_action( 'lw_seo_after_meta' ); /** * Fires when plugin settings are saved. * * @param array $old_options Previous options. * @param array $new_options New options. */ do_action( 'lw_seo_settings_saved', $old_options, $new_options );

Options API

use LightweightPlugins\SEO\Options; // Get option with default $value = Options::get( 'option_name', 'default_value' ); // Get all options $all_options = Options::get_all(); // Option name constant $option_key = Options::OPTION_NAME; // 'lw_seo_options'

Available Options

// General 'title_separator' // string: |, -, >, >>, *, / 'title_home' // string: Homepage title template 'title_post' // string: Post title template 'title_page' // string: Page title template 'title_archive' // string: Archive title template 'title_search' // string: Search title template // Social 'og_enabled' // bool: Enable Open Graph 'twitter_enabled' // bool: Enable Twitter Cards 'twitter_card_type' // string: summary, summary_large_image 'social_twitter' // string: Twitter handle 'default_social_image'// int: Attachment ID // Sitemap 'sitemap_enabled' // bool: Enable sitemap 'sitemap_posts' // bool: Include posts 'sitemap_pages' // bool: Include pages // Local SEO 'local_enabled' // bool: Enable LocalBusiness schema 'local_business_type' // string: Schema.org type 'local_business_name' // string: Business name 'local_street' // string: Street address 'local_city' // string: City 'local_phone' // string: Phone number 'local_lat' // string: Latitude 'local_lng' // string: Longitude 'local_hours_enabled' // bool: Enable opening hours 'local_hours_{day}_open' // string: Opening time 'local_hours_{day}_close' // string: Closing time 'local_hours_{day}_closed' // bool: Closed on this day // AI/LLM 'llms_txt_enabled' // bool: Enable llms.txt 'block_gptbot' // bool: Block GPTBot 'block_claude' // bool: Block Claude-Web // ... other crawlers

Post Meta

// Meta key prefix $prefix = '_lw_seo_'; // Available meta keys '_lw_seo_title' // Custom SEO title '_lw_seo_description' // Custom meta description '_lw_seo_canonical' // Custom canonical URL '_lw_seo_noindex' // bool: noindex this post '_lw_seo_nofollow' // bool: nofollow this post '_lw_seo_og_title' // Custom OG title '_lw_seo_og_description' // Custom OG description '_lw_seo_og_image' // Custom OG image (attachment ID) '_lw_seo_exclude_sitemap' // bool: Exclude from sitemap // Example usage $custom_title = get_post_meta( $post_id, '_lw_seo_title', true );

PHP Functions

/** * Output breadcrumbs. * * @param array $args { * @type string $separator Separator between items. Default '>>'. * @type string $home Home link text. Default 'Home'. * @type bool $schema Include schema markup. Default true. * @type bool $echo Echo or return. Default true. * } * @return string|void */ lw_seo_breadcrumbs( $args = [] ); // Example lw_seo_breadcrumbs( [ 'separator' => ' / ', 'home' => 'Start', 'schema' => true, ] );

Extending the Plugin

Add Custom Schema Type

add_filter( 'lw_seo_schema', function( $schema, $type ) { if ( is_singular( 'product' ) ) { $schema['@type'] = 'Product'; $schema['name'] = get_the_title(); $schema['description'] = get_the_excerpt(); // Add more product properties... } return $schema; }, 10, 2 );

Add Custom Template Variable

add_filter( 'lw_seo_title', function( $title, $post_id ) { // Replace custom variable if ( strpos( $title, '%%custom%%' ) !== false ) { $custom_value = 'My Custom Value'; $title = str_replace( '%%custom%%', $custom_value, $title ); } return $title; }, 10, 2 );

Modify Sitemap Output

add_filter( 'lw_seo_sitemap_urls', function( $urls ) { // Add custom URL $urls[] = [ 'loc' => 'https://example.com/custom-page/', 'lastmod' => '2024-01-15', 'changefreq' => 'monthly', 'priority' => '0.5', ]; return $urls; } );

Conditionally Disable Output

add_filter( 'lw_seo_title', function( $title, $post_id ) { // Disable on specific pages if ( $post_id === 42 ) { return ''; // Return empty to use default WP title } return $title; }, 10, 2 );

Adding Support for Custom Post Types (Markdown Endpoint)

Custom post types with public => true work automatically via PostRenderer. To customize the output:

// Add custom frontmatter for 'event' post type. add_filter( 'lw_seo_markdown_frontmatter', function ( array $data, WP_Post|WP_Term $object ): array { if ( ! $object instanceof WP_Post || 'event' !== $object->post_type ) { return $data; } $data['event_date'] = get_post_meta( $object->ID, 'event_date', true ); $data['location'] = get_post_meta( $object->ID, 'event_location', true ); $data['ticket_url'] = get_post_meta( $object->ID, 'ticket_url', true ); return $data; }, 10, 2 );

Adding Support for Custom Taxonomies (Markdown Endpoint)

Public custom taxonomies work automatically via TaxonomyRenderer. To customize:

add_filter( 'lw_seo_markdown_frontmatter', function ( array $data, WP_Post|WP_Term $object ): array { if ( ! $object instanceof WP_Term || 'location' !== $object->taxonomy ) { return $data; } $data['map_url'] = get_term_meta( $object->term_id, 'map_url', true ); return $data; }, 10, 2 );

WooCommerce Integration

The plugin automatically detects WooCommerce and adds:

  • Product schema markup
  • Product Open Graph tags (price, availability)
  • Product sitemap entries
// Check if WooCommerce integration is active if ( class_exists( 'LightweightPlugins\SEO\WooCommerce\Integration' ) ) { // WooCommerce features available }

Conflict Detection

The plugin checks for other SEO plugins:

// Detected plugins that disable LW SEO output $conflicting_plugins = [ 'wordpress-seo/wp-seo.php', // Yoast SEO 'seo-by-rank-math/rank-math.php', // Rank Math 'all-in-one-seo-pack/all_in_one_seo_pack.php', // AIOSEO ];

File Structure

lw-seo/ ├── lw-seo.php # Main plugin file ├── composer.json # Composer config ├── includes/ │ ├── Plugin.php # Main plugin class │ ├── Options.php # Options handler │ ├── Admin/ │ │ ├── Settings.php # Settings page │ │ └── Settings/ │ │ ├── TabGeneral.php │ │ ├── TabContent.php │ │ └── ... │ ├── Frontend/ │ │ ├── Meta.php # Meta tags output │ │ ├── Schema.php # JSON-LD output │ │ └── ... │ ├── Sitemap/ │ │ └── Generator.php │ ├── Local/ │ │ ├── Schema.php │ │ └── Shortcodes.php │ └── WooCommerce/ │ └── Integration.php └── assets/ ├── css/ └── js/

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Follow WordPress Coding Standards
  4. Run composer phpcs before committing
  5. Submit a pull request

Repository: github.com/lwplugins/lw-seo 


AI Abilities (Site Manager Integration)

LW SEO registers 5 abilities with LW Site Manager, enabling AI agents and automation tools to manage SEO settings programmatically.

These abilities are only active when LW Site Manager is also installed and activated. No hard dependency - the integration is a no-op otherwise.

Abilities Overview

AbilityTypeDescription
lw-seo/get-metareadonlyGet SEO metadata for a post or term
lw-seo/set-metawriteSet SEO metadata
lw-seo/get-content-signalsreadonlyGet resolved AI Content Signals
lw-seo/get-markdownreadonlyGet content as markdown
lw-seo/get-optionsreadonlyGet global SEO settings

Authentication

All requests require a WordPress Application Password:

curl -u "[email protected]:XXXX XXXX XXXX XXXX XXXX XXXX" <URL>

Permissions

AbilityRequired capability
lw-seo/get-metaedit_posts
lw-seo/set-metaedit_posts
lw-seo/get-content-signalsedit_posts
lw-seo/get-markdownedit_posts
lw-seo/get-optionsmanage_options

lw-seo/get-meta

Retrieve SEO meta fields for a post or taxonomy term.

Method: GET

# Post meta curl -u "user:app-password" \ "https://example.com/wp-json/wp-abilities/v1/abilities/lw-seo/get-meta/run?input[post_id]=123" # Term meta curl -u "user:app-password" \ "https://example.com/wp-json/wp-abilities/v1/abilities/lw-seo/get-meta/run?input[term_id]=5"

Response:

{ "success": true, "type": "post", "id": 123, "meta": { "title": "Custom SEO Title", "description": "Meta description...", "noindex": "", "og_title": "Social Title", "og_description": "Social description...", "og_image": "https://example.com/image.jpg", "ai_train": "default", "ai_input": "no", "search": "default", "markdown_content": "" } }

Available fields:

FieldTypeDescription
titlestringSEO title (overrides template)
descriptionstringMeta description
noindexstring”1” if noindex, "" if not
og_titlestringOpen Graph title
og_descriptionstringOpen Graph description
og_imagestringOpen Graph image URL
ai_trainstringAI training signal: “default” / “yes” / “no”
ai_inputstringAI input signal: “default” / “yes” / “no”
searchstringAI search signal: “default” / “yes” / “no”
markdown_contentstringCustom markdown for the /md endpoint

lw-seo/set-meta

Set SEO meta fields. Only the provided fields are updated, others remain unchanged.

Method: POST

curl -u "user:app-password" \ -X POST -H "Content-Type: application/json" \ -d '{ "input": { "post_id": 123, "meta": { "title": "New SEO Title", "description": "New meta description for search engines.", "og_title": "Share Title", "og_description": "Share description.", "ai_train": "no" } } }' \ "https://example.com/wp-json/wp-abilities/v1/abilities/lw-seo/set-meta/run"

Setting term meta:

curl -u "user:app-password" \ -X POST -H "Content-Type: application/json" \ -d '{ "input": { "term_id": 5, "meta": { "title": "Category SEO Title", "description": "Category description for search engines." } } }' \ "https://example.com/wp-json/wp-abilities/v1/abilities/lw-seo/set-meta/run"

Response:

{ "success": true, "message": "5 SEO fields updated.", "updated": ["title", "description", "og_title", "og_description", "ai_train"] }

lw-seo/get-content-signals

Get the resolved Content Signals for a post or term. Resolution order: per-post/term meta > lw_seo_content_signals filter > global setting.

Method: GET

curl -u "user:app-password" \ "https://example.com/wp-json/wp-abilities/v1/abilities/lw-seo/get-content-signals/run?input[post_id]=123"

Response:

{ "success": true, "signals": { "ai-train": "yes", "ai-input": "no", "search": "yes" } }

If neither post_id nor term_id is provided, returns the global values.

lw-seo/get-markdown

Get content as markdown with YAML frontmatter. Returns the same output as the /md endpoint.

Method: GET

# Post/page markdown curl -u "user:app-password" \ "https://example.com/wp-json/wp-abilities/v1/abilities/lw-seo/get-markdown/run?input[post_id]=123" # Taxonomy term markdown curl -u "user:app-password" \ "https://example.com/wp-json/wp-abilities/v1/abilities/lw-seo/get-markdown/run?input[term_id]=5"

Response:

{ "success": true, "markdown": "---\ntitle: \"Hello World\"\nurl: \"https://example.com/hello-world/\"\ndate: \"2026-03-22\"\nauthor: \"Admin\"\nlanguage: \"en_US\"\n---\n\n# Hello World\n\nContent here...\n", "tokens": 125 }

If the post/term has a custom markdown_content meta field, it takes precedence over the auto-generated conversion.

lw-seo/get-options

Get all global LW SEO settings.

Method: GET

curl -u "user:app-password" \ "https://example.com/wp-json/wp-abilities/v1/abilities/lw-seo/get-options/run"

Response (excerpt):

{ "success": true, "options": { "separator": "-", "title_home": "%%sitename%% %%sep%% %%sitedesc%%", "sitemap_enabled": true, "llms_txt_enabled": true, "content_signals_ai_train": true, "content_signals_ai_input": true, "content_signals_search": true, "schema_enabled": true, "breadcrumbs_enabled": true } }

Examples

Bulk SEO meta update

for id in 100 101 102 103; do curl -s -u "user:app-password" \ -X POST -H "Content-Type: application/json" \ -d "{\"input\":{\"post_id\":$id,\"meta\":{\"ai_train\":\"no\",\"ai_input\":\"yes\"}}}" \ "https://example.com/wp-json/wp-abilities/v1/abilities/lw-seo/set-meta/run" echo "" done

Export all posts as markdown

ids=$(curl -s -u "user:app-password" \ "https://example.com/wp-json/wp/v2/posts?per_page=100&_fields=id" \ | python3 -c "import sys,json; [print(p['id']) for p in json.load(sys.stdin)]") for id in $ids; do curl -s -u "user:app-password" \ "https://example.com/wp-json/wp-abilities/v1/abilities/lw-seo/get-markdown/run?input[post_id]=$id" \ | python3 -c "import sys,json; d=json.load(sys.stdin); open(f'post-{$id}.md','w').write(d.get('markdown',''))" 2>/dev/null echo "Exported post $id" done

FAQ

Does this work with other SEO plugins?

LW SEO detects Yoast SEO, Rank Math, and All in One SEO and automatically disables its output to prevent conflicts.

How do I add breadcrumbs?

Use the shortcode [lw_breadcrumbs] in your content or the PHP function lw_seo_breadcrumbs() in your theme.

What is llms.txt?

The llms.txt file provides information to AI crawlers about your website. See llmstxt.org  for more details.

How do I block AI crawlers like ChatGPT or Claude?

Go to LW Plugins > SEO > AI/LLM tab and enable blocking for the crawlers you want to block. This adds the appropriate rules to your robots.txt.

How do I set a default social image?

Go to LW Plugins > SEO > Social tab and upload a default image. This will be used for Open Graph and Twitter Cards when a post has no featured image.

Where can I find the sitemap?

Your sitemap is available at yoursite.com/sitemap.xml. To regenerate rewrite rules, go to Settings > Permalinks and click Save.

Where can I find robots.txt and llms.txt?

  • robots.txt: yoursite.com/robots.txt
  • llms.txt: yoursite.com/llms.txt

How do I access the Markdown endpoint?

Append /md/ to any post, page, or taxonomy URL. For example: yoursite.com/hello-world/md/. You can also use the query parameter ?format=md or send an Accept: text/markdown header.

Support