SimpleXLSXGen, exporting Excel files from PHP without a spreadsheet library
Export data to Excel. PHP XLSX generator
At a glance
- What is it?
- SimpleXLSXGen writes XLSX files from PHP with no external tools and no libraries, using a markup convention inside cell values to express formatting. That convention is the whole design, and it is both the reason the library is small and the reason some inputs need care.
- Who is it for?
- SimpleXLSXGen suits a PHP application that needs to hand a user an Excel file and does not want a dependency that pulls in a full spreadsheet engine, and it suits a project where the export is a straightforward table rather than a styled report.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 173 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One class, one array, one file
SimpleXLSXGen exports data to an Excel XLSX file, and the description is a PHP XLSX generator with no external tools and libraries. That is the whole selling point, and it is a real constraint: the library writes the XML of an XLSX container itself rather than delegating to PhpSpreadsheet or a similar toolkit.
The basic usage is four lines:
$books = [
['ISBN', 'title', 'author', 'publisher', 'ctry' ],
[618260307, 'The Hobbit', 'J. R. R. Tolkien', 'Houghton Mifflin', 'USA'],
[908606664, 'Slinky Malinki', 'Lynley Dodd', 'Mallinson Rendel', 'NZ']
];
$xlsx = Shuchkin\SimpleXLSXGen::fromArray( $books );
$xlsx->saveAs('books.xlsx'); // or downloadAs('books.xlsx') or $xlsx_content = (string) $xlsx A plain PHP array becomes a spreadsheet. The first row is the header because it is the first row of the array, and the saveAs call writes the file, while downloadAs sends it to the browser and casting the object to a string gives you the raw content, which is what you want if the file is going somewhere else.
There are several interfaces, and the README shows them. The fluid interface calls fromArray on the class and then chains, for example fromArray($books)->downloadAs('table.xlsx'). Multiple sheets work by chaining addSheet, and there is an alternative interface where you construct the object and add sheets to it, and a classic interface that uses a use statement. The alternative interface is the one to note, because it is the only way to get the content as a string without the static entry point.
The library is MIT licensed, the author is Sergey Shuchkin, and the README lists the sibling projects: an XLSX reader, an XLS reader and a CSV reader and writer. The last push was on 2026-05-03, the default branch is master, and there are no GitHub releases, so the CHANGELOG.md in the repository root is where the version history lives.
Formatting is markup inside the cell value
The design decision that defines this library is that formatting is expressed with tags embedded in the string value itself.
$data = [
['Normal', '12345.67'],
['Bold', '<b>12345.67</b>'],
['Italic', '<i>12345.67</i>'],
['Underline', '<u>12345.67</u>'],
['Strike', '<s>12345.67</s>'],
['Bold + Italic', '<b><i>12345.67</i></b>'],
['Hyperlink', 'https://github.com/shuchkin/simplexlsxgen'],
['Italic + Hyperlink + Anchor', '<i><a href="https://github.com/shuchkin/simplexlsxgen">SimpleXLSXGen</a></i>'],
['Green', '<style color="#00FF00">12345.67</style>'],
['Bold Red Text', '<b><style color="#FF0000">12345.67</style></b>'],
['Size 32 Font', '<style font-size="32">Big Text</style>'],
['Blue Text and Yellow Fill', '<style bgcolor="#FFFF00" color="#0000FF">12345.67</style>'],
['Border color', '<style border="#000000">Black Thin Border</style>'],
['Row height', '<style height="50">Row Height = 50</style>'],
];So bold, italic, underline, strike, colour, fill, font size, border, row height, alignment and word wrap are all in the value, and the library parses them out when it writes the cell. That is what makes it small, and it is also the whole risk, because a value that happens to contain angle brackets will be interpreted as markup.
The alignment tags are separate from the style tag, which is worth noticing because they are positioned differently in the value. You get left, center and right as their own tags, and the positioning combinations, top, middle and bottom combined with left, center and right, work by nesting:
['Center + Bold', '<center><b>Name</b></center>'],
['Middle + Center', '<style height="50"><middle><center>Middle + Center</center></middle></style>'],The border styling is the most elaborate part. A single border value sets a style and a colour, and a more complex form sets a style per side, and a separate top-level tag carries the list of acceptable border styles, where the value in the style attribute names one of them: none, thin, medium, dashed, dotted, thick, double, hair, mediumDashed, dashDot, mediumDashDot, dashDotDot, mediumDashDotDot and slantDashDot.
That top-level tag is an unusual pattern, and it exists because the border style names are a fixed set in the format, so the library needs to be told which ones are legal before it sees a cell using one. Cell merging is done with a null value in the cell to be merged into, and the row that holds a merge has a null in the last cell of the span.
Beyond formatting there are comments, which are an HTML comment inside the value, with an optional author and a line break, and line breaks in a cell with a plain newline in the string.
Data types, and the raw string escape hatch
The library does its own type detection, and the data types example shows what it recognises.
$data = [
['Integer', 123],
['Float', 12.35],
['Percent', '12%'],
['Currency $', '$500.67'],
['Currency (other)', '<style nf=""£"#,##0.00">500</style>'],
['Datetime', '2020-05-20 02:38:00'],
['Date', '2020-05-20'],
['Time', '02:38:00'],
['Datetime PHP', new DateTime('2021-02-06 21:07:00')],
['Formula', '<f v="135.35">SUM(B1:B2)</f>'],
['Hyperlink', 'https://github.com/shuchkin/simplexlsxgen'],
['Internal link', '<a href="sheet2!A1">Go to second page</a>'],
['RAW string', "\0" . '2020-10-04 16:02:00'],
];The type detection is a heuristic, and the example makes the edge cases visible. A number is a number, a percentage is a string with a percent sign, and a currency is a string with a currency marker. The custom number format for a currency that the library does not recognise is done with the nf attribute inside a style tag, where the format string is the Excel number format syntax, so a format like a currency symbol followed by a thousands separator and two decimals.
Dates come in several shapes: a datetime string, a date string, a time string, and a PHP DateTime object, which the library handles natively.
Two cell types need more than the string. A formula is written with an f tag carrying a cached value, so the formula is there and the value is there for readers that do not calculate. A hyperlink is a bare URL, and a hyperlink with an anchor is an a tag with an href.
The raw string mechanism is the escape hatch, and it comes in three forms. You can prefix the value with a null byte, as in "\0" . '2020-10-04 16:02:00', which disables the type detection so the string is stored exactly as given. There are also ::raw() and ::rawArray() methods, and a raw tag for a formatted raw value.
$data = [
['Datetime as raw string', "\0" . '2023-01-09 11:16:34'],
['Date as raw string', "\0" . $PushkinDOB],
['Disable type detection', "\0" . '+12345'],
['Method ::raw, insert greater/less them simbols', SimpleXLSXGen::raw('20- short term: <6 month')],
['Formatted raw', '<b><center><raw>+123456 <tag><tag2></raw></center></b>'],
];The null byte prefix is the mechanism worth remembering, because it is the fix for the markup problem. If a value may contain something that looks like a tag, prefixing it with \0 stops the library from interpreting it, and the raw method is the same idea for the case where the value legitimately contains the greater and less signs.
Layout, sizing and the wider package family
The layout controls are chainable methods on the generator, and they are the part that decides how the sheet looks when opened rather than what a cell says.
SimpleXLSXGen::fromArray($data)
->setDefaultFont('Courier New')
->setDefaultFontSize(14)
->setColWidth(1, 35) // or ->setColWidth('A', 35)
->setColWidth('B:C', 20) // column letters and ranges also work
->mergeCells('A20:B20')
->saveAs('styles_and_tags.xlsx');Column widths can be set by index or by letter, and ranges work, so a whole block of columns is one call. Merging is a separate method rather than a tag, which is more consistent than the alignment tags being inline, and it names the range in A1 notation. The default font and default font size are set at the sheet level, so cells that carry no style tag inherit them.
The examples directory holds a custom-date-format.php file and a .gitignore, so the example set is small, and the repository root carries the three screenshots the README shows, books.png, datatypes.png and styles.png, which are useful because they show the actual rendered result of each example rather than a description of it.
The family of libraries is worth noting as a whole, because it explains the design. The author also maintains SimpleXLSX, an XLSX reader, SimpleXLS, an XLS reader, and SimpleCSV, a CSV reader and writer. The XLSX is a ZIP container holding XML parts, and SimpleXLSXGen is the counterpart that writes one, so a project that needs to read and write the same format can use both, and the CSV library covers the case where a CSV would have done.
Installation is through Composer with composer require shuchkin/simplexlsxgen, which installs the latest supported version, and the README also offers downloading the class directly from the repository, which is a reasonable fallback when adding a Composer dependency is not an option.
The last detail is a note about encoding. Use UTF-8 encoded strings, and that is the only input requirement stated anywhere in the README.
Editorial conclusion
SimpleXLSXGen suits a PHP application that needs to hand a user an Excel file and does not want a dependency that pulls in a full spreadsheet engine, and it suits a project where the export is a straightforward table rather than a styled report. It is the wrong choice if you need to read spreadsheets back, since this is a writer and the author's XLSX reader is a separate project, and it is the wrong choice if your cell content may contain angle brackets, because the formatting convention is markup inside the value. Before adopting it, check whether your data can contain characters that look like tags, and read the CHANGELOG.md in the repository root, since the project has no GitHub releases and the changelog is the version record.
Frequently asked questions
How do I export data to XLSX from PHP with SimpleXLSXGen?
Build a PHP array of rows, call SimpleXLSXGen::fromArray($books), then saveAs('books.xlsx') to write the file, or downloadAs('books.xlsx') to send it to the browser, or cast the object to a string to get the raw content. Install it with composer require shuchkin/simplexlsxgen.
How does SimpleXLSXGen apply formatting to a cell?
Formatting is expressed as tags inside the cell value, for example <b> for bold, <i> for italic, <style color="#00FF00"> for colour, <style bgcolor> for fill, and <left>, <center> and <right> for alignment. The library parses these when it writes the cell, which is what keeps it free of dependencies.
How do I write a value that contains angle brackets or that should not be type detected?
Prefix the value with a null byte, as in "\0" . '+12345', which disables type detection and stops the value being parsed as markup. There are also the SimpleXLSXGen::raw() and ::rawArray() methods and a <raw> tag for formatted raw values.
Can SimpleXLSXGen read spreadsheets as well as write them?
No. It is a writer only. The author maintains separate projects for reading, SimpleXLSX for XLSX and SimpleXLS for XLS, plus SimpleCSV for CSV reading and writing.
How do I set column widths and merge cells in SimpleXLSXGen?
With chainable methods rather than inline tags. setColWidth(1, 35) sets a column by index, setColWidth('A', 35) by letter, and setColWidth('B:C', 20) sets a range. mergeCells('A20:B20') merges in A1 notation, and setDefaultFont and setDefaultFontSize set the sheet-level defaults.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/shuchkin-simplexlsxgen)Was this useful?