Share Your Knowledge!

This fast-growing section of our site is the new one-stop shop for educational materials for ExpressionEngine with rich and expert content, from both EllisLab and the community. Submit your article, tip, or solution today!

  • Blog Channel Set

    in: Resources, Channel Sets

    Quickly create a Blog Channel for ExpressionEngine with this Channel Set.

    The Blog Channel Set comes with custom fields, statuses, and categories to get you up and running fast. Further customizations can be made to tailor your blog, but here’s what’s inside:

    Custom Fields

    • {blog_content}: a Textarea for the blog post’s content
    • {blog_image}: a File field for a featured image for the blog post


    • Open: published
    • Closed: not published
    • Featured: to call attention to a blog post, typically on the homepage


    • News
    • Personal
    • Photos
    • Videos
    • Music

    Sample Tags

    {exp:channel:entries channel='blog' limit='1' require_entry='yes'}
    	{if no_results}
    	<h1>Blog Channel Set</h1>
    	{if has_categories}
    					<li><a href="{path='blog/index'}">{category_name}</a></li>
    	{if blog_image}
    				<img src="{blog_image:image}" alt="{blog_image:caption}">
    | Read in 1 minute
  • Using Gmail SMTP to Send Emails from your Website

    in: Tutorials, Configuration

    Gmail’s SMTP server requirements differ from ExpressionEngine’s defaults. Google also has a few extra steps on their end to get up and running. Here’s how.

    Create an App Password for your Google Account

    If you have enabled 2-Step Verification on your Google account—which you have, right?—you will need to set up an app password that ExpressionEngine can use to authenticate. In Google, visit My Account, Sign-in & security, and under the Signing in to Google section, visit “App passwords”. Here is a direct link: https://myaccount.google.com/apppasswords.

    On the following screen, choose “Other” under “Select app”, and type in the name of your website or your domain. This will help you remember what you generated this app password for.

    Click Generate. Google will then give you the app password that you will use in ExpressionEngine. You need to copy this password to your clipboard or keep the browser window open. When you click Done, there is no way to retrieve this app password. Don’t worry if you jumped the gun and clicked Done before copying though; just revoke the app password and make another one.

    The generated app password. Copy this to the clipboard before closing the window.
    The generated app password. Copy this to the clipboard before closing the window.

    If you do not have 2-Step Verification enabled, and for some reason do not want to enable it, you will have to enable less secure sign-ins under Connected apps & sites. Toggle the switch under “Allow less secure apps” to “ON”.

    Configure Outgoing Email Settings in ExpressionEngine

    In ExpressionEngine, navigate to Settings > Outgoing Email. Change the Protocol to SMTP which will open the SMTP Options. Use the following settings:

    Server address
    Server port
    The app password you generated earlier
    Connection type
    SSL (ssl://)

    Lastly make sure your Newline character setting on this page is set to \r\n, a requirement for Google’s SMTP servers.

    That’s it!

    You are finished and ready to start sending emails through your Gmail account. In the Developer menu, visit Utilities, and from the Communicate utility, send yourself a test email to verify that everything is working. If you get an error message, go back through the steps starting with your Google My Account page, and verify your settings.


    Using Gmail for website-generated emails is less than ideal. There is a sending limit of 100 emails per day via SMTP (G Suite accounts with a custom domain don’t suffer this limitation). But marketing and even transactional emails coming from a Gmail account may get flagged by email servers as spam, or even as phishing emails. This affects both the deliverability of your emails as well as your account reputation. Google has a laundry list of bulk sender guidelines, and problems might result in your Gmail account being suspended.

    So as a general rule, we do not recommend using Gmail SMTP for your website’s emails. It can be okay for your development environment, or if you just need something better than PHP mail() or sendmail for admin notifications, password reset emails, etc., and have only a handful of users. If your site has an email contact form, member registration, commenting, or discussion forums, you should use a transactional email service like:

    Marketing emails are a different category from transactional emails, which are the type triggered by a user taking an action. Registering, resetting a password, being notified of subscribed comments, and so on. For marketing and bulk email, you should use a service made for that purpose like Campaign Monitor, ConstantContact, or MailChimp. Some of the transactional providers also handle marketing emails, and mention that specifically in their marketing.

    If you are running a version of ExpressionEngine older than 3.5.0, this article still applies but you will need to set the newline and connection type manually in your system/user/config.php file.

    | Read in 4 minutes
  • Easy Multi-device Local Development with xip.io

    in: Tips

    You are building a site locally and you’d like to quickly check your work on your iPad, iPhone, another computer, or maybe a client’s laptop. You could pay for a dynamic DNS service, setup a DNS server on your local network, or setup a proxy server perhaps. But there’s an easier way. The key is a “magic domain” service, xip.io, which lets you give meaningful names to your local IP addresses.

    In this example, we will make my local https://awesomesite.dev available to others on my network, including my client’s iOS devices.

    How to do it

    I have my Apache VirtualHost and /etc/hosts file on my iMac configured to use https://awesomesite.dev/ for local development. I’ll add the special xip.io address as a ServerAlias, using the local IP address of my iMac (

    ServerName awesomesite.dev
    ServerAlias awesomesite.dev.
    DocumentRoot /Users/derek/Sites/AwesomeSite

    I’m keeping the awesomesite.dev. prefix so I can use this naming system with all my projects and make it clear which VirtualHost I’m accessing. If you’re using MAMP Pro, just go to your Hosts tab and click the + button under the “Aliases” box to add the xip.io address as an alias. Notice that xip.io addresses follow the simple pattern of your domain name, followed by your local IP address, followed by xip.io.

    Now to make sure that links and asset requests go to the right URLs, at the bottom of my config.php file in ExpressionEngine, I add the following:

    if (preg_match('/.*\.xip\.io$/', $_SERVER['HTTP_HOST']))
    	$config['base_url'] = "http://{$_SERVER['HTTP_HOST']}/";

    This block of code tells ExpressionEngine to use the xip.io address as the basis for URLs, but only if the request came from a .xip.io address. The preg_match() conditional validates the HTTP_HOST header for us in this case.

    Use the special {base_url} variable in all of your URL settings which keeps your site ultra-portable. If you do not, then you will need to override site_url, cp_url, theme_folder_url, and upload_preferences for each Upload Destination.

    Never use HTTP_HOST without validating it! It is a request variable, which means it can be faked, and you can’t trust it. If you use it without validation, you run the risk of a cache poisoning attack. That means an attacker could cause all generated links to point to their own servers for people who access your site after they do. If they copy your design, they have an effective phishing attack on your site’s visitors. Always validate the HTTP_HOST header before using it! If you’re using a multi-environment config, make sure yours does this! Exclamation mark!


    Make sure to restart your web server after updating your VirtualHost, then anytime you want to pull up your local site on any device on your network, just key the xip.io address into the browser, in my case https://awesomesite.dev. Then I can work on my iMac and check it instantly on my phone, iPad, or Macbook Air, or let a client spin it up on their device.

    There are many ways to do this; xip.io is just one of the easy and seamless options.

    | Read in 4 minutes
  • Cross-Origin Resource Sharing (CORS)

    in: Tutorials

    If you’re seeing any of these things, there’s a good chance that you are experiencing a cross-origin resource failure:

    • Font-based icons (or any web font) not showing up
    • JavaScript failing in your control panel with add-ons while using the Site Manager
    • Front-end Ajax endpoint that won’t load

    In short, web browsers don’t allow sites to make requests to origins that differ from the one it is accessing. Developers are most affected by this restriction with web fonts and Ajax requests. The “origin” includes both the protocol (http://, https://) and the domain. It may not be immediately obvious that all these are different origins:

    • http://example.com
    • https://example.com
    • http://www.example.com
    • http://subdomain.example.com

    How do you know that you’re experiencing a problem with cross-origin resource sharing? If you open your developer tools in your browser, the console will have logged an error. The wording differs based on the browser, but typically will mention “Access-Control-Allow-Origin” somewhere in the message.

    Notice in the above examples that a domain with and without www are two separate origins. If this is the reason you’re experiencing a CORS issue, it is best solved by using a simple canonical redirect to either remove or force www in your URLs. This is best practice even if you aren’t having an issue with cross-origin resource sharing.

    Depending on the scenario you are facing, we will use a slightly different solution. In every case, we are adding an appropriate Access-Control-Allow-Origin header to the request. We use * if we don’t care about the requesting domain, or a specific origin if we need tighter control.

    Web Fonts Won’t Load

    Let’s say you have a @font-face declaration in your CSS, but the font is on a different origin. In that case, we will whitelist cross-origin access just for font files. In the folder where the font files live, add a .htaccess file with the following:

    # Allow font assets to be used across domains and subdomains
    <FilesMatch "\.(ttf|otf|eot|woff)$">
      <IfModule mod_headers.c>
        Header set Access-Control-Allow-Origin "*"

    JavaScript Errors in the Control Panel

    If you’re getting JavaScript errors in your ExpressionEngine control panel, and upon inspection see that something is not loaded due to cross-origin policy, you’ll need to add a header covering those resources. In this scenario:

    • An addon named Example is attempting to make an Ajax request to the themes folder
    • The control panel runs from https://admin.example.com
    • The Themes URL runs from https://example.com

    In the user/themes/example/ folder add a .htaccess file with the following:

    # Allow access to these theme files from https://admin.example.com
    <IfModule mod_headers.c>
      Header set Access-Control-Allow-Origin "https://admin.example.com"

    Note that we don’t have to specify https://example.com, because browsers will always allow access to resources from the same origin they live on.

    Front-end Ajax Endpoint Won’t Load

    In this scenario, you have a template that you’ve set up to act as an Ajax endpoint. We’ll assume it’s a public resource, so we will allow * origins.

    We have two different ways to do this. One is with server config, like the .htaccess we’ve used so far, and the other is to control it within your ExpressionEngine template itself. In both cases, we’re assuming a URL of https://example.com/ajax/endpoint.

    Using .htaccess

    In the web root’s .htaccess, add:

    # Allow cross-domain access to our Ajax endpoint
    <IfModule mod_headers.c>
      SetEnvIf Request_URI "/ajax/endpoint" CORS=True
      Header set Access-Control-Allow-Origin "*" env=CORS

    The first line sets an environment variable named CORS, but only for our specific URI. The second line sets the Access-Control-Allow-Origin header as normal, but the addition of env=CORS means that it will only set the header when that environment variable is set.

    This method is effective whether ExpressionEngine manages the resource, and allows regular expression URL patterns much as you’d use RewriteCond %{REQUEST_URI} with mod_rewrite.

    In an ExpressionEngine Template

    You may prefer for the cross-origin access to be set in the ExpressionEngine template itself, so another developer (including your future self) doesn’t have to hunt around to figure out how and where the header is set, or why. In this case, you can use a simple plugin to add cross-origin resource sharing to any template: Cross-Origin-Headers (plugin on GitHub).


    This will allow * origins to access the content. Or to allow only a unique foreign origin:

    {exp:cross_origin_headers domain='https://subdomain.example.com'}

    Behind the scenes this extremely lightweight plugin sets the Access-Control-Allow-Origin header for us with PHP.

    Allowing Multiple Domain Origins

    One complication of the CORS implementation is that it only allows a resource to either be made fully public, or allowed to a single foreign origin. What if you have many domains (including subdomains) that need to share secure resources? You can accomplish this with server configuration, but it does have a caveat. In the folder that includes the resources that need to be shared, add the following .htaccess:

    # whitelist domains to allow CORS between, including http/https
    <IfModule mod_headers.c>
        SetEnvIf Origin "http(s)?://(example.com|subdomain.example.com)$" AccessControlAllowOrigin=$0
        Header set Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin

    Just replace example.com|subdomain.example.com in the above with a pipe-delimited list of the domains you need to allow. The first line sets an environment variable, only if the origin is in our list. The second line sets our header with the domain that we matched, only when the environment variable is set.

    What’s the caveat? Origin is a request header, and thus cannot be trusted. It works in all major browsers, so it is convenient and adds a modest amount of protection compared with *. But, a malicious user could violate your cross-origin policies by faking the Origin header. If you use this method on secure resources, make sure that they are adequately protected with server/user authentication, and do not rely soley on cross-origin browser policies.

    Further Reading

    If this topic is of interest to you, or you need to dive more deeply than the examples provided, here are some more resources for you.

    Browser Error Message Reference

    The following is a list of error messages from major browser vendors related to CORS. I’ve included it here to help you identify if that’s the issue you’re having, and to hopefully expose this article to people searching for help with these error messages.

    • Chrome / Opera
      • Access to Font at ‘https://subdomain.example.com/some-font.woff’ from origin ‘https://www.example.com’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘https://www.example.com’ is therefore not allowed access.
      • XMLHttpRequest cannot load https://subdomain.example.com/ajax.html. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘https://www.example.com’ is therefore not allowed access.
    • FireFox
      • Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://subdomain.example.com/ajax.html. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). (unknown)
      • Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://subdomain.example.com/some-font.woff. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
      • downloadable font: download failed (font-family: “Some Font” style:normal weight:500 stretch:normal src index:1): bad URI or cross-site access not allowed source: https://subdomain.example.com/some-font.woff
    • Safari
      • XMLHttpRequest cannot load https://subdomain.example.com/ajax.html. Origin https://www.example.com is not allowed by Access-Control-Allow-Origin.
    • Windows Edge
      • SEC7120: Origin https://www.example.com not found in Access-Control-Allow-Origin header. cors.html
      • SCRIPT7002: XMLHttpRequest: Network Error 0x80700013, Could not complete the operation due to error 80700013. cors.html
      • CSS3116: @font-face failed cross-origin request. No Access-Control-Allow-Origin header. some-font.woff
    | Read in 7 minutes
  • Template Routes for Single Entry / Permalink URLs

    in: Tips, Tutorials, Templating

    Single entry permalink URLs are common on the web. We’re going to apply it to a blog here, as that’s readily understood, but the pattern will work for everything else as well.

    By default ExpressionEngine’s URLs work like this https://example.com/blog/entry/url-title, where “blog” is a template group, and “entry” is a template, which is fine. But nicer would be something like https://example.com/blog/url-title, cause who needs that extra entry segment anyway? You could handle all of this in the blog template group’s index, but that’s inelegant, full of conditionals, hard to read, and hard to maintain.

    Template Routes give us an easy to create and maintain implementation. What you’ll need is the following. A blog template group with the templates index and single-entry.

    Inside of index you’ll put your blog listing, this will cover paginated archives, and categorized listings as well—URLs like https://example.com/blog/P15 and https://example.com/blog/category/cat-url-title.

    Index might look something like this:

    {exp:channel:entries channel='blog' limit='25'}
    	<div class="blog-entry">
    		<h2><a href="{route='blog/single-entry' url_title='template-routes-for-single-entry-permalink-urls'}">{title}</a></h2>
    		<p>{entry_date format='%n/%j/%Y'}</p>
    		{if has_categories}
    			<p><b>in</b>: {categories backspace='2'}<a href="{path='blog'}" title="View more in {category_name}">{category_name}</a>, {/categories}</p>
    		<p>by: {author}</p>
    	{if no_results}

    See that route= variable above? It’ll come into play in a moment. Next you’ll need a single-entry template that will cover your, well, single entries. And will be reached with URLs like https://example.com/blog/url-title.

    Single Entry will look something like this:

    {exp:channel:entries channel='blog' limit='1' require_entry='yes'}
    	<p>{entry_date format='%n/%j/%Y'}</p>
    	{if has_categories}
    		<p><b>in</b>: {categories backspace='2'}<a href="{path='blog'}" title="View more in {category_name}">{category_name}</a>, {/categories}</p>
    	<p>by: {author}</p>
    	{if no_results}

    Now that you have those set up, you need to add your route. Go to Developer > Template Manager > Template Routes and set up a route for the template single-entry Give it a Route of /blog/{url_title:regex[(((?!(P\d+|category\/)).)+?)]} select “no” for “Segments Required?” then save. The regex may look a little complex, so let’s break it down. (((?!(P\d+|category\/)).)+?) uses a negative lookahead assertion, so the route does not match if the second URL segment is either pagination (P\d+) or category/. We want those URL patterns to still use the ExpressionEngine default routing to the blog/index template.

    Now in your templates replace any path variables to your single-entry URLs to use the route= variable in the first code sample. This will make sure that when clicked the visitor is sent to the right place!

    That’s it! Now go forth and clean up your ExpressionEngine URLs today!

    | Read in 3 minutes
.(JavaScript must be enabled to view this email address)

ExpressionEngine News!

#eecms, #events, #releases