Database Powered CSS in WordPress Themes

A popular ability in WordPress themes is to add custom CSS driven by options. This brings up a common question, how should the theme inject custom CSS? I’ll outline three different approaches on how to do this. These aren’t new, many people have written about these; forums, blog posts, email lists and IRC. I’m still seeing questions about this though, so I wanted to address this specific question with specific solutions.

For the purposes of code examples I’ll assume that you have an option called my_background_color and that you want to do something like this:

[sourcecode lang=”css”]
body {
background-color: <?php echo $theme_opt[‘my_background_color’]; ?>
}
[/sourcecode]

We’ll start with the simplest method.

header.php

Most themes have a header.php file that contains template code for the top of the HTML output. This makes it easy to add custom CSS with options, just echo it out inside the HEAD section of the HTML:

[sourcecode lang=”html”]
<style type=’text/css’>
body {
background-color: <?php echo $theme_opt[‘my_background_color’]; ?>
}
</style>
[/sourcecode]

The advantages to this approach is that it’s very simple, you already have a header.php so adding a few more lines doesn’t take much work. The disadvantage is that this solution isn’t very flexible, if you have complex rules about when and how to include the CSS then your header.php file gets a lot extra “stuff” that may not need for every page.

If your needs are simple then this works great. If not, I suggest using either wp_head or parse_request.

wp_head

Each theme calls a WordPress action at the end of the HTML HEAD section – wp_head – that can be used to include the custom CSS:

[sourcecode lang=”php”]
<?php
add_action( ‘wp_head’, ‘my_custom_css_hook’ );
function my_custom_css_hook( ) {
# get theme options
?>

<style type=’text/css’>
body {
background-color: <?php echo $theme_opt[‘my_background_color’]; ?>
}
</style>

<?php
}
[/sourcecode]

The only real difference between this approach and the previous one is that it’s less clutter in header.php. Instead of having all that code in header.php it can be moved out to a separate file and WordPress will include it at runtime whenever the wp_head action fires.

parse_request

WordPress can provide your theme with custom URLs, these can turn around and serve up what ever you want, including CSS. This technique takes a little bit more work, but provides the maximum degree of flexibility. There are a couple steps to this one, first what you’ll need to have in header.php:

[sourcecode lang=”html”]
<link rel=’stylesheet’ type=’text/css’ href="<?php bloginfo( ‘url’ ); ?>/?my-custom-content=css" />
[/sourcecode]

The my-custom-content=css just needs to be something unique to your theme so that it doesn’t conflict with plugins that might be using parse_request as well.

Next we need to tell WordPress how we want to handle this request:

[sourcecode lang=”php”]
add_action( ‘parse_request’, ‘my_custom_wp_request’ );
function my_custom_wp_request( $wp ) {
if (
!empty( $_GET[‘my-custom-content’] )
&& $_GET[‘my-custom-content’] == ‘css’
) {
# get theme options
header( ‘Content-Type: text/css’ );
?>

body {
background-color: <?php echo $theme_opt[‘my_background_color’]; ?>
}

<?php
exit;
}
}
[/sourcecode]

A few things in there that I want to point out. Pay attention to line 8, this tells the browser what sort of content we are sending back. In this case it was CSS, but it could have been JavaScript or anything else. Also note that I didn’t add any cache related headers, it’s worth reading up on cache control in HTTP headers so that you know how that works. Line 16 is also important, we don’t want WordPress attempting to do any further processing after we return the CSS so the right thing to do is exit as soon as possible.

And if you wanted to keep the CSS in a separate file ( custom-css.php for our example ) that looked more like a normal CSS file then the my_custom_wp_request function could look like:

[sourcecode lang=”php”]
function my_custom_wp_request( $wp ) {
if (
!empty( $_GET[‘my-custom-content’] )
&& $_GET[‘my-custom-content’] == ‘css’
) {
# get theme options
header( ‘Content-Type: text/css’ );
require dirname( __FILE__ ) . ‘/custom-css.php’;
exit;
}
}
[/sourcecode]

allowing your custom-css.php to look like:

[sourcecode lang=”CSS”]
body {
background-color: <?php echo $theme_opt[‘my_background_color’]; ?>
}
[/sourcecode]

basically just enough PHP to fill in the option blanks, other wise a normal looking CSS file. I rather like this approach, it provides a nice degree of separation and control.

Conclusion

Now you have three methods for including database powered CSS in your WordPress theme. I like using parse_request with the CSS in a separate file ( the last example ), for a little bit of extra work you get lots of flexibility and a nice layer of separation that makes managing the CSS portion easier.

Having any tips on how to improve on this? Leave a comment below!

49 Comments

  1. For Method 3:
    of hooking to parse_request, you should have the code check unconditionally right at the top of the plugin load.

    Like this:
    if ($_GET[‘my-custom-content’] == ‘css’) {
    header( ‘Content-Type: text/css’ );
    // do your CSS output here, or whatever…
    exit;
    }

    Reason you should do it right at the top of the plugin and NOT in a function: Speed.

    When you do things like add that stylesheet link, then you’re doubling the server load. For every page view, now WordPress actually has to load twice: Once for the page, once for your CSS call. So this is a technique best avoided entirely (the best technique is the wp-head one you mention above), but if you really want to do this, then you want to minimize the server impact on that second load as much as possible.

    If the CSS that you’re outputting here doesn’t depend on the content of the Post, or the query, or other elements that get loaded and initialized after this point, then there’s no point in waiting all the way until you get to the parse_request to output your code and halt execution of WordPress.

    So anytime you’re overriding WordPress output to produce something else, you want to produce that something else as early as possible. The earliest possible time you can do that is in the main body of the plugin, because that code gets executed at the time the plugin is first included. You have database access working by then, but the query and the rewrites and the templates and all that other stuff has yet to be initialized. But then, if you don’t need that stuff, that’s a great time to override the output.

  2. Mohammad Koubeissi

    Thu 25 Mar 2010 at 9:10 am

    I’m all over this on my next wordpress theme. Thanks for this.

  3. Oh, also, new technique in 3.0:
    <link rel='stylesheet' type='text/css' href="” />

    The blog_url() function can take a path as the first param, which gets appended to the blog’s url. Accounts for SSL and everything else. Highly recommended instead of the bloginfo stuff.

  4. Thanks for bringing that up. There are definitely some optimizations that could be done here to help reduce the server load to make this work. You’ve got some good suggestions there.

    Another would be to cache the generated output. In this example the CSS only changes when a user edits the color option, which probably isn’t happening very often. This makes it highly cacheable, so folks who have a WP object cache installed ( like memcached ) could see the load drop quite a bit. A bonus would be to see if batcache works for this sort thing, if not it would make a nice addition.

  5. Yes, I even considered using that in the example, but opted not to since most people aren’t using 3.0 yet.

  6. I wrote a tutorial for making a dynamic stylesheet and theme options panel with Thematic a few months back. Thought I’d post the link in case other people want to see how to set up the options panel as well.

    I also like calling an external stylesheet best because I don’t like a lot of clutter in the head of the document and inline css- but I notice the big guys (like WooThemes) throw it all inline. Not sure if there is a big advantage to that or not.

  7. Gonna have a closer look at this on Monday. Might come in handly. Thanks for the writeup. ๐Ÿ™‚

  8. There is also the fourth method of writing the CSS file and use the wp_enqueue_style which then lets you update the version number every time the file is updated.

  9. Chris / Fredericton Web Design

    Fri 26 Mar 2010 at 8:16 am

    It’ll be interesting to see how well this integrates into 3.0, pushing CSS from a database would save a lot of my problems.

  10. Yes, you could point to the custom CSS URL using wp_enqueue_style as well.

  11. All three of these methods will work fine in WP3.0.

  12. I’d strongly recommend not passing data via $_GET for this.

  13. Awesome. I’ve been wanting to know how to do this for awhile now. Thanks!

  14. This works like a charm Joseph! Do you have any links to suggest to learn more about โ€œCache control in HTTP headers?โ€ The WordPress Codex pretty much says the same thing, you do. ๐Ÿ™‚

    Also, none of my options seem to be coming through when I try using Ottoโ€™s suggestion. Any thoughts?

  15. The caching issue can be a bit more involved so I didn’t want to side track into that too much in this post. Some resources in that area:

    http://developer.yahoo.com/performance/rules.html (specifically the ‘Add an Expires or a Cache-Control Header’, ‘Configure ETags’ sections)
    http://www.websiteoptimization.com/speed/tweak/cache/
    http://php.net/manual/en/function.header.php (example #2)
    http://www.mnot.net/cache_docs/

    Usually focusing on expires and etags. To really do this right (especially for etags) the script needs to more than just spit out those headers, it needs to have enough logic to tell if things have changed so that it can send proper replies back to queries that just want to know if things changed.

    There’s more than enough for a whole separate post on the topic ๐Ÿ™‚

    One Otto’s suggestion, really what he’s saying is add the $_GET check at the top to try and minimize the amount of extra work that might have to be done.

  16. I am going to use this database powered CSS as it’s one interesting idea. Thank you for your work and the same to Otto for joining your discussion. Cannot wait for wp3 release!

  17. have you seen CSS scaffold?

    We’re thinking about using it for our themes; it allows you to use logic in CSS (e.g. variables, if/else etc…)

    “Database driven” is probably to complicated a term… we like to think about it as “option driven css” haha

  18. I’m not into CSS generation tools. I find myself still editing CSS directly a lot and would rather not incur the additional overhead in most cases.

    That said I’d love it if CSS had a bit more gusto, variables would be a great start.

  19. I’m using a fourth option but I’m not sure if it is the best one:

    1. First I use the wp_enqueue_style on my functions.php file to add my CUSTOM CSS file.


    wp_enqueue_style('custom_css', get_bloginfo('template_url').'/custom-css.php');

    2. In my theme folder I have a file called custom-css.php with the following lines in the beggining:

    // Custom CSS goes here...
    body {
    color: get_option('my_color');
    }

  20. The comment system deleted some of the code:

    Part 2:

    First lines of custom-css.php

    header("Content-type: text/css");
    include('../../../wp-load.php');
    // Custom CSS goes here...
    echo 'body {'
    echo 'color: ' . get_option('my_color');
    echo '}'

  21. The problem with this approach is trying to reference wp-load.php. You can never rely on that being in the same location, so it will break on some WP installs. A plugin or theme never needs pull in wp-load.php / wp-config.php / wp-blog-header.php it should use one of the other methods that I outlined so that it will work on any and all WP installs.

  22. Maybe I’m wrong but I don’t think that you can change wp-load.php location.

    You may change the location for the wordpress install but still those 2 files won’t change relatively to each other.

  23. So how will that work when your wp-content dir is /opt/some/path/wp-content and your WP install location is /var/www/wordpress ? The relative position may as well be an infinite number of possibilities, so it can not be relied on.

    Any theme or plugin that is trying to reference wp-load.php / wp-config.php / wp-blog-header.php / etc. is going to break on some WP installs. So why not use a method that will work for all WP installs instead? WP provides all of the tools that are needed to do these types of things that will work for every WP install.

  24. there is a 4th and simpler way to do this, and it should take care of 90% of the situations where you need to mod the css on the fly

    http://wordpress.org/extend/plugins/art-direction/

  25. For users, yes, there are plenty of plugins to provide custom CSS. This was targeted more for developers looking to write those types of plugins and/or theme features.

  26. Thank you so much Joseph, I finally solved my problem with this parse request!

  27. Happy to hear it helped.

  28. Chris / Fredericton Web Design

    Thu 10 Jun 2010 at 7:27 am

    Thanks Joseph,

    I was thinking specifically with the new background editor found in WP 3, it’ll be interesting to see how the editor changes how we design themes.

    Chris

  29. Hopefully for at least some cases it will simplify things for theme developers.

  30. Have you seen LESS (http://lesscss.org/)?

    It’s fundamental flaw is that it is not in css core, or supported natively by browsers.

  31. Which would make it another CSS generator.

  32. Yes, sorry I didn’t mean to imply that it wasn’t.

    It’s more that I am commiserating with you, and pointing out that there are others out there feeling your pain. Some are trying to propose a solution, that *could* be the solution you are looking for.

    Something that perhaps if one had the time, energy and skills, one could get behind and perhaps give it that extra it needs.

  33. When I used cakePHP, I had dynamic CSS and JS file includes which cake injected within the tags (links to actual css / js files, not php code generating custom css).

    http://bakery.cakephp.org/articles/view/nicehead-helper-with-autoloading-of-javascript-and-css

    Is there a way to do this using in WordPress? (2.9.2)

  34. Sounds similar to wp_enqueue_script() and wp_enqueue_style()

  35. Deleted my post and redirected it back here. Thanks again for the info.

  36. how do you define $theme_opt in your custom.php?? When i try to use get_option() I get the error “Call to undefined function get_option” which suggests that custom.php is outside of the scope of wordpress?

    also- can someone explain the path from from dirname( __FILE__ ) to the custom.php file?

  37. figured it out finally. apparently the above parse_request method doesn’t work if you try to enqueue the my_custom_content=css stylesheet. you have to print it in the header somewhere.

  38. Very useful information. I am not a WordPress expert, however I am trying to learn more about creating custom templates. This article was very helpful & specific. Thank you!

  39. for anyone else who struggled w/ this…. it doesn’t work w/ wp_enqueue_script. it does work as written by manually adding the script tag to the header. cheers.

  40. Hi Joseph, great post.
    However, having a little trouble getting it to work.
    I’m using the options framework plugin by Devin at http://wptheming.com/options-framework-plugin/#comments.
    I’ve tried all three techniques you mention but not having any success with any of them, obviously doing something wrong…

    I’d really like to get the third method to work as it provides the flexibility I’m looking for.

    So, adding to my header file just above the wp_head.

    <link rel='stylesheet' type='text/css' href="/?my-custom-content=css” />

    Then adding this to functions.php.:

    function my_custom_wp_request( $wp ) {
    if (
    !empty( $_GET[‘my-custom-content’] )
    && $_GET[‘my-custom-content’] == ‘css’
    ) {
    # get theme options
    header( ‘Content-Type: text/css’ );
    require dirname( __FILE__ ) . ‘/custom-css.php’;
    exit;
    }
    }

    Finally adding this to: custom-css.php

    body {
    background-color:
    }

    of_get_option[‘example_background’] it taken from the option theme which is from http://wptheming.com/options-framework-plugin/#comments. where of_get_option($id,$default); is returned from the options.php file.
    I have that all set up and working, just trying to get it all into a styles sheet for greater flexibility.

    Any suggestion would be much appreciated.
    Cheers
    Chris

  41. as it didn’t come out for some reason in my last comment.
    this is what I added to custom-css.php

    < !–
    body {
    background-color:
    }
    — >

  42. For some reason thats not coming out. maybe you can edit the comments / delete my last.

    Basically I’m echoing the following: of_get_option[‘example_background’] in place of your theme_opt[‘my_background_color’];

  43. Apologies for the repeat email, but just a quick update. I’ve got it working using your first method. styles adding into the top of the header, but still not luck with the third.

  44. Has anyone managed to get the third method working? drawing in the styles from a separate .php file?

  45. Theresa Stanton

    Tue 4 Sep 2012 at 6:21 pm

    I finally got it to work. Looks like you may have forgotten to add this in your functions.php file:
    add_action( ‘parse_request’, ‘my_custom_wp_request’ );

    Also, at the end of my custom-css.php file, I had to add to it.

  46. Theresa Stanton

    Tue 4 Sep 2012 at 6:23 pm

    Oops, my comment didn’t come out right at the very end. I meant to show that I added a php end bracket at the end of the custom-css.php file. Without it I got an error.

  47. What about cache? I’m a performance maniac and It would be great do something like this:

    1. Generate the CSS code dynamically
    2. Cache it
    3. Then a check: Were the options updated? Yes: update cache. No: Forget it and use the cached on.

    In my mind this would lower the cpu load because it’s just an “if” check. Do you think this approach is good? Could it work?

  48. If you are using an object cache you could potentially leverage that and get caching for free.

  49. Hi Joseph Sir, I am using wp_head method in my first theme I am creating. this article I searched is really helpful for me and learned alot.

    thank you!
    Umar

Leave a Reply

Your email address will not be published. Required fields are marked *

© 2019 Joseph Scott

Theme by Anders NorénUp ↑