WordPress “File-based Posts” Plugin (v1.04)
Current Version: 1.04
(version history)
Download: file-based-posts.php

I much prefer to compose and edit text with my favorite text editor, rather than in a <textarea> box on a web page. Thus, I created the 'file-based posts' plugin for WordPress, which allows you to maintain the body of a post in a file on the local web-server host. Changes to the file are reflected immediately on the blog site.

Executive Summary

  1. Install this plugin (download here and install as “file-based-posts.php” in your wp-content/plugins directory).
  2. On the newly-available “File-Based Posts” tab of the “Options” blog admin page, configure the directory in which post files will go. (Be sure the web server can read files in that directory.)
  3. In that directory, create a file containing the body content of your next post, say “my-next-post.html”. (The filename must end with .html, and may not contain whitespace.)
  4. With WordPress's “Write Post” browser-based interface, enter the post's title, date, draft/public, etc., as you normally would. However, instead of putting the full body content in the big <textarea> box, simply put FILE: my-next-post.html in that box.
  5. That's it. You may edit the “my-next-post.html” file any time; changes are pulled in the next time the post is viewed.

Start over from step #3 for your next post or page (using a unique filename, of course).

More Details

After first installing this plugin (see step one above), visit the Options admin page (“File-Based Posts” tab) to configure this plugin's base-directory parameter (the directory in which you'll create and maintain post-related files). From then on, as you create content with WordPress's “Write Post” and “Write Page” browser-based interface, you have the option of typing the full content in the <textarea> box as before, or, you can simply refer to the file which contains the content (the file having been previously created by you with your favorite text editor).

For example, you can create a filemy-next-post.html” (the filename must end with “.html”), and enter the body content of your post. You then use the normal WordPress mechanism to create a post, but instead of typing the post body content in the big <textarea> box, you need enter only “FILE:” followed by the filename (“my-next-post.html”). For example,

FILE: my-next-post.html


Be sure to fill in the title, date, visibility, etc., on the normal WordPress “Write Post” page. Only the body content is in the file.

Updating A Post

You may update the file at any time; any changes are reflected online the next time the post is viewed. If you wish to update the post's title, date, categories, custom fields, etc. (anything except the body), do so as with any WordPress post or page, via the browser-based admin interface.

The Post's “edit” Page

If you view the post's “edit” page in WordPress, you'll see something like:

FILE: file-based-posts.html

***  E d i t   t h i s   p o s t ' s   b o d y   i n   t h e   a b o v e - n a m e d
***  f i l e ,   n o t   h e r e   i n   W o r d P r e s s .
***  A n y   c h a n g e   m a d e   h e r e   w i l l   n o t   b e   s a v e d .
***               - -   t h e   ' f i l e - b a s e d   p o s t s '   p l u g i n
<p>This is my next post blah blah blah....


The first line is the “FILE:” line you entered when the post was created, followed by a warning (inserted by this plugin) that any changes to the body content via this interface will be lost, since the body content is refreshed from the file, if needed, each time the post is viewed.

Following the warning, the body content most-recently-pulled from the file is shown.

Reverting A Post From File-Based Back To Normal

If you wish to move control of a post's body content from the file back to WordPress's database, simply view the post's “edit” page and remove the “FILE:” line (and the warning that this plugin added). What's left is the post content most recently pulled from the file, and that's what will stay in the database. The lack of a leading “FILE:” line means that this post has now been disassociated from this plugin and associated files. You can then safely go ahead and delete the file, if you wish, as it will no longer be used.

Backdoor Testing

Any content of the file within

    <nopost>  . . . . .   </nopost>

tags is removed before what remains is used for the post. This can be useful for “backdoor” testing of a page prior to posting.

If you choose to make your base directory one that's part of your web server's document tree, you can view files you've written (or are in the process of writing) as normal static web pages. I like to do this to preview a post before I bother to integrate it with WordPress. However, in order to get a more true rendition of what it'll look like once in my blog, I need to include the style sheet link. I include that link within <nopost> ... </nopost> tags so that the extra link doesn't actually become part of the post once I do integrate it with WordPress. (I don't want to just remove the link prior to posting, since I'd like to keep it there for when I test later edits.)

File Includes

[ This documentation is correct and up to date, but the documentation has expanded more quickly than my ability to organize it well, so my apologies for the disorganized presentation ]

You can include the contents of other files in your post file with the

   <fbp_include: filename>

directive. (The file is read from the same directory as other file-based-post files)

As a guard against an infinitely-recursive include, there is a limit to how deep the include nesting can go (currently 8), which is wildly larger than will ever be needed, I think.

“Include-once” Directive

I also use the include feature to include advertisements by Yahoo!. This is what I put in the post where I want the ads to appear:

   <fbp_include once: ADS.html>

Note the location of the colon -- it's easy to get in the wrong spot.

The ADS.html file contains the call to Yahoo! to insert the ads. I have only one such call per post, but some pages (such as the blog home page) can display multiple posts. Since I don't want multiple ad calls, I use the once attribute to have only the first include actually happen.

The “abort if already included” check happens only with a <fbp_include> that has the once attribute. Even after including the file this way, a subsequent include without the once attribute does actually include the file, since it wasn't told to check.

Including one from among a group of files

Let's say that some posts want ads on the right (via Ads-Right.html), on the left (Ads-Left.html), or as a banner (Ads-Top.html). Even if these files are included into each post with the once attribute, you'll still get multiple includes when these includes are mixed and matched on one page, since the filenames being included are distinct.

To solve this problem, you must tell each <fbp_include> that the file being included is part of a group. This is done with the radio attribute (think <input type='radio'>). Here's how the three includes might appear:

  <fbp_include once radio=ads: Ads-Left.html>
  <fbp_include once radio=ads: Ads-Right.html>
  <fbp_include once radio=ads: Ads-Top.html>

The radio=ads tells the plugin to consider the name, for counting purposes, to be “ads”. Thus, only the first of the three will actually be included.

The order of attributes (in this case radio=ads and once) is not relevant.

Passing along variables with the include

An included file may contain references such as

    <fbp_var: name>


    <fbp_var default='value': name>

These insert the value of the named variable, which itself had been set via something like:

   <fbp_include once set background = 'white': ADS.html>

Note that no variables exist until you create them in a <fbp_include> directive.

Here's a snippet from ADS.html:

<script language="JavaScript"><!--
ctxt_ad_bg = '<fbp_var default='CDCDCD': background>';
// --></script>

The <fbp_var> directive is replaced by the value of the background variable, which was set to “white” in the above <fbp_include> example. If that variable hadn't been set (such as by the simple call <fbp_include once: ADS.html>), the default value CDCDCD is used. The default default is an empty string.

You can also indicate that if a variable hasn't been set, use the value of another variable (or the first from among a list of variables which has a value). This is done with the fallback attribute, which contains a comma-separated list of variable names. As a simple (contrived) example, consider an include to display an image. It expects that the url and caption variables be set, and, optionally, an alt variable (for the alt text):

  <img src="<fbp_var: url>" alt="<fbp_var fallback='caption,url': alt>">
  <fbp_var: caption>

In the request of for the alt variable, the fallback='caption,url' indicates that if alt has not been given a value, use the caption variable (if it has a value), or the url variable (if it has one).

If the requested variable has no value, and none of the fallback variables has a value, the default (via the default attribute) is used, or if no default, an empty string.

Regardless of how the value is derived, you can use the doublequoted attribute to cause any double quotes in the value to be replaced by &quot;, which allows the value to be used within a doublequoted HTML attribute or JavaScript string. (The alt variable reference in the example above really should use this. You can use singlequoted to have all single quotes replaced by &#39;.

See this post for an example which uses a lot of this stuff (the incorporation of Y!Q into my blog prose).

You may have multiple set components to an include:

   <fbp_include once set background = 'white', set foreground = "red": ADS.html>

Don't forget to repeat the “set” keyword for each variable. The comma between the settings is optional, but helps make it read more easily.

Currently, there's no way to set a variable outside of an <fbp_include> directive, nor a way to clear variables. I can add these features if there's a need for them.

Other Include Options

If you use the raw attribute with an include, the read file is injected directly, without any parsing. (<nopost>, other includes, and variable references, for example, are not recognized.)

The cooked include attribute is similar to raw, except that the text is cooked for html ('&' is replaced by '&amp;', etc.).

The uncounted include attribute indicates that the file is unconditionally included, but not counted as having been included (so that a subsequent include of the same file with the once attribute does succeed). The uncounted attribute is particularly useful with cooked. (See this post for an example.)


There's a simple debugging mechanism built into this plugin. On the options page for this plugin (where the posts directory is configured), you can set the ID of the post you wish to debug. When that number is set and you view the post while logged in as someone allowed to edit the post, you'll see at the top various messages.

For example, consider my post on raising a bilingual child, which is post #88 of my blog, after having entered “88” as the ID on the options page, I see the following:

How To Raise a Bilingual Child

[file-based-posts: about to inspect post #88 data in WordPress database]
[file-based-posts: data begins: FILE:·bilingual.html\n\n<!--\n***********************
[file-based-posts: fbp_process_one_file(level is 1, file is “/jfriedl/blog/posts/bilingual.html”)]
[file-based-posts: read 8972 bytes]
[file-based-posts: include of 'ADS.html', option string 'once']
[file-based-posts: include of ADS.html, option 'once' has value '1']
[file-based-posts: fbp_process_one_file(level is 2, file is “/jfriedl/blog/posts/ADS.html”)]
[file-based-posts: read 409 bytes]
[file-based-posts: new content for level 2 is 409 bytes]
[file-based-posts: new content for level 1 is 9100 bytes]
[file-based-posts: content has not changed; leaving WordPress database as is]

Anthony turns three years old tomorrow. He’s been talking up a storm for quite some time, but he’s behind other kids his age. It’s not something we’re concerned about because, if he’s got, say, 70% of the language skills as his peers, he’s really got 140% of the skills (70% English + 70% Japanese). I know of other kids who have three or four languages — a kid’s brain is just amazing.

blah blah blah.....

If you set the debug ID to -1 (minus one), debugging is turned on for all posts.

Issues To Be Aware Of

  • When configuring the directory for post-related files, be sure that such files are editable by you, and readable by the web server.

  • File updates are incorporated when the blog content is viewed (such as by a user with a browser, or via a full-content RSS feed being pulled). This provides for one small “gotcha” which you should be aware of: edits to a post file do not become visible to WordPress's “Blog Search” feature until the related post has been viewed. In practice this is a minor nit, as you'll normally immediately view the post yourself to confirm that the changes look okay.

  • The “FILE: my-next-post.html” used to refer to each post's filename becomes part of the post's content with respect to WordPress's “Search” feature. Thus, for example, a user searching for “file html” would see all your file-based posts and not understand why.

  • If the “FILE:” line suddenly appears at the top of a post (when viewing your blog as a reader would), it means that the post's file was not accessible (either because it's been moved or removed, or perhaps due to changed permissions). In such cases, the content that had been most recently read from the file (and hence most-recently saved in WordPress's database) is displayed, albeit along with the “FILE:” line. (There is a good argument to be made that the “FILE:” line should not be shown in this failure mode, but I choose to show it simply as an easily-seen indication that there is a failure.)

    To help debug why you see the “FILE:”, see the debugging section above.

  • The author is not particularly familiar with WordPress's (convoluted and under-documented) internals.

Version History

VersionRelease DateChanges
1.0411-Nov-2005Changed uses of &apos; to &#39;, since it seems that IE doesn't support &apos. Sighh.
1.0330-Oct-2005Added the radio, raw, cooked, and uncounted attributes to <fbp_include>
Added the fallback, singlequoted, and doublequoted attributes to <fbp_var>.
1.0229-Oct-2005Added <fbp_include> and <fbp_var> directives.
Allowed a debugging id of -1 to mean “debug all posts”
1.0124-Sep-2005Made debugging messages invisible except to logged-in users who are allowed to edit the post. That way, random readers don't see your debugging cruft.
1.0015-Sep-2005Initial release

Feedback appreciated, via a comment below, or via email.

All 8 comments so far, oldest first...

I’m an old-school HTML hand coder, and am just starting to tentatively use WordPress to manage some portions of two sites. On one site I have an existing news section which consists of about 150 static HTML pages. Being accustomed to working with a text editor on the Real Files with all their data inside, I find I have a superstition about dumping all that material into the black hole of a database I don’t understand at all.

Would your plugin perhaps be appropriate for this kind of use: I could keep my existing 150 news files, comment out more or less everything outside the HTML , and then have WordPress access them via your plugin. As I add new news items, I can just create new static files as I have been doing all along. (And the virtue of having them continue to exist as independent files is that, if I have a mind to, I can easily do a search-and-replace through them, or change something about their format or style easily in my text editor, which I know how to use well.)

Can you see any obstacles or downsides to this approach? Many thanks for your work on this plugin, and any insights you can provide.

— comment by Bob on November 5th, 2005 at 3:53pm JST (18 years, 6 months ago) comment permalink

The only downside I see is that you’d have to inject the 150 files by hand
(use the WordPress web interface to say “FILE: …..” 150 times). The plugin
has no way to automate adding files.

But, once added, indeed, you can do what you want (change the files and the blog-view of the pages changes immediately).

— comment by Jeffrey Friedl on November 6th, 2005 at 12:26am JST (18 years, 6 months ago) comment permalink

Nice work Jeffrey. Much like Bob I’ve found much comfort in the ‘static include’ path, and I currently author a site with roughly 80-90 articles. Due to the structure of the blog and the nature of my (often obtuse) workflow, the ability to work on and include external files is a must.

Recently I’ve been (cautiously) thinking of moving to a system like WordPress and if your plugin works as it looks like it does, you would have solve my biggest hurdle.


— comment by Adam's Pants on November 18th, 2005 at 9:42pm JST (18 years, 6 months ago) comment permalink

Thanks for it …was looking for a similar plugin ! 🙂

— comment by Dell on January 28th, 2006 at 5:46am JST (18 years, 4 months ago) comment permalink

This is an excellent plugin! However, being limited to one directory is not very useful if you plan to manage many files this way. I was planning on writing all my articles by hand and then using this plugin to include them in WordPress. However, all my articles (and their associated pages) would have to be in the same directory. It would be very helpful if I could specify “FILE: project1/intro.html” to specify that the file is located in a subdirectory of the directory specified in the Options. If this is an easy fix, any pointers on what I would need to do to modify the plugin would be helpful. I have a decent understanding of PHP.

Thank you for this plugin!

— comment by Raam on February 20th, 2006 at 6:22am JST (18 years, 3 months ago) comment permalink

Hi, this is a very useful plugin.
My comment is only to stay that I confirm that:
1) it works under WordPress 2.3
2) replacing all “html” occurences by “php” in the download file allows using PHP files in place of plain HTML (use the exec-PHP plugin as well).

Thanks very much!

— comment by Olivier on October 8th, 2007 at 11:41pm JST (16 years, 7 months ago) comment permalink

I can not get this to work with my shared hosting. No matter what I do, I get the error message:

The specified base directory “. . . . ” does not seem to exist (or is not readable by the web server); ignoring.

The directory does exist, and the permissions are wide open. I suspect my web-hoster – dot5hosting – is doing some odd linking thing. But how do I get around that? I have tried doing a pwd from ftp, and I have tried the dot5hosting file manager. I don’t have shell access. Nothing seems to work. Any ideas?

I am using wordpress 2.3.1.

— comment by walterbyrd on November 30th, 2007 at 10:54am JST (16 years, 6 months ago) comment permalink

Just wanted to say that this plugin still works flawless and is superb 🙂
Greetings from the Netherlands.

— comment by Jurriaan on October 27th, 2011 at 1:02am JST (12 years, 7 months ago) comment permalink
Leave a comment...

All comments are invisible to others until Jeffrey approves them.

Please mention what part of the world you're writing from, if you don't mind. It's always interesting to see where people are visiting from.

IMPORTANT:I'm mostly retired, so I don't check comments often anymore, sorry.

You can use basic HTML; be sure to close tags properly.

Subscribe without commenting