superbig/craft3-beam

Generate CSVs and XLS files in your templates

Installs: 33 551

Dependents: 0

Suggesters: 0

Security: 0

Stars: 19

Watchers: 1

Forks: 6

Open Issues: 2

Type:craft-plugin

pkg:composer/superbig/craft3-beam

5.0.0 2024-04-24 09:19 UTC

README

Latest Version License

Generate CSV and Excel (XLSX) files directly from your Craft CMS templates

Screenshot

Table of contents

Requirements

  • Craft CMS 5.0.0 or later
  • PHP 8.2 or later
Legacy version support

For older Craft CMS versions:

  • Craft 4: Use Beam 3.x
  • Craft 3: Use Beam 2.x

Installation

Install via Composer from your Craft project directory:

composer require superbig/craft3-beam

Note: The package name is superbig/craft3-beam for all Craft CMS versions (3, 4, and 5). The version automatically installed matches your Craft version.

Then install the plugin in the Craft Control Panel:

  1. Go to Settings → Plugins
  2. Find Beam and click Install

Quick start

Generate a CSV file with just a few lines:

{% set beam = craft.beam.create({
    header: ['Name', 'Email'],
    content: [
        ['John Doe', 'john@example.com'],
        ['Jane Doe', 'jane@example.com']
    ]
}) %}
{% do beam.csv() %}

That's it! The file will automatically download in the user's browser.

Usage guide

Basic usage

Every Beam export starts by creating a Beam instance with craft.beam.create():

{% set beam = craft.beam.create({
    header: ['Email', 'Name'],
    content: [
        ['test@example.com', 'John Doe'],
        ['another@example.com', 'Jane Doe'],
        ['third@example.com', 'Trond Johansen']
    ]
}) %}

Output formats

Generate CSV

{% do beam.csv() %}

Generate Excel (XLSX)

{% do beam.xlsx() %}

Dynamic content

Build your export dynamically using loops and the append() method:

{# Create beam with headers #}
{% set beam = craft.beam.create({
    header: ['Username', 'Name', 'Email']
}) %}

{# Append data from entries or users #}
{% set users = craft.users().group('authors').all() %}
{% for user in users %}
    {% do beam.append([user.username, user.name, user.email]) %}
{% endfor %}

{# Generate the file #}
{% do beam.csv() %}

Configuration methods

Beam provides several methods to customize your export:

Set Custom Filename
{% set currentDate = now|date('Y-m-d') %}
{% do beam.setFilename("user-report-#{currentDate}") %}
Update Headers
{% do beam.setHeader(['Username', 'Full Name', 'Email Address']) %}
Replace Content
{% do beam.setContent([
    ['test@example.com', 'John Doe'],
    ['another@example.com', 'Jane Doe']
]) %}

Advanced features

Custom cell formatting

Excel (XLSX) files support custom cell formatting. Define column types in the header:

{% set beam = craft.beam.create({
    header: [
        'Email',
        'Name',
        { text: 'Amount', type: 'price' },
        { text: 'Date', type: 'date' }
    ],
    content: [
        ['john@example.com', 'John Doe', 1500.50, '2024-01-15'],
        ['jane@example.com', 'Jane Doe', 2300.75, '2024-01-16']
    ]
}) %}
{% do beam.xlsx() %}

Supported format types

Type Excel Format Example Output
string @ Text
integer 0 12345
date YYYY-MM-DD 2024-01-15
datetime YYYY-MM-DD HH:MM:SS 2024-01-15 14:30:00
time HH:MM:SS 14:30:00
price #,##0.00 1,234.56
dollar [$$-1009]#,##0.00;[RED]-[$$-1009]#,##0.00 $1,234.56
euro #,##0.00 [$€-407];[RED]-#,##0.00 [$€-407] €1.234,56

Multiple sheets (XLSX only)

You can create an Excel file with multiple sheets using the fluent sheet() method:

{% set beam = craft.beam.create() %}
{% do beam.setFilename('users-by-group') %}

{# Create and populate sheets using fluent methods #}
{% for group in craft.users.groups() %}
    {% set users = craft.users().group(group.handle).all() %}
    
    {# Select/create a sheet and set its header #}
    {% do beam.sheet(group.name).setHeader(['Email', 'Full Name']) %}
    
    {# Append users to the active sheet #}
    {% for user in users %}
        {% do beam.append([user.email, user.fullName]) %}
    {% endfor %}
{% endfor %}

{% do beam.xlsx() %}

You can switch between sheets as needed:

{% set beam = craft.beam.create() %}

{# Set 'Summary' as the active sheet #}
{% do beam.setSheet('Summary') %}
{% do beam.setHeader(['Total Users', 'Active', 'Inactive']) %}
{% do beam.append([100, 75, 25]) %}

{# Switch to 'Details' sheet #}
{% do beam.sheet('Details').setHeader(['Email', 'Name', 'Status']) %}
{% do beam.append(['john@example.com', 'John', 'Active']) %}

{% do beam.xlsx() %}
More sheet configuration options

The sheet() method also accepts an options array as the second parameter:

{% do beam.sheet('Products', {
    header: ['ID', 'Name', 'Price']
}) %}
{% do beam.append(['1', 'Product A', '10.00']) %}

Alternative: Using array-based configuration

If you need to configure all sheets upfront, you can provide a sheets array in the options:

{% set options = {
    filename: 'users-report',
    sheets: [
        {
            name: 'Active Users',
            header: ['Email', 'Name', 'Status'],
            content: [
                [ 'john@example.com', 'John Doe', 'Active' ],
                [ 'jane@example.com', 'Jane Doe', 'Active' ],
            ]
        },
        {
            name: 'Inactive Users',
            header: ['Email', 'Name', 'Status'],
            content: [
                [ 'inactive@example.com', 'Bob Smith', 'Inactive' ],
            ]
        }
    ]
} %}
{% set beam = craft.beam.create(options) %}
{% do beam.xlsx() %}

Or build the sheets array dynamically with setSheets():

{% set beam = craft.beam.create() %}
{% do beam.setFilename('users-by-group') %}

{% set sheets = [] %}
{% for group in craft.users.groups() %}
    {% set users = craft.users().group(group.handle).all() %}
    {% set sheetContent = [] %}
    {% for user in users %}
        {% set sheetContent = sheetContent|merge([[ user.email, user.fullName ]]) %}
    {% endfor %}
    
    {% set sheets = sheets|merge([{
        name: group.name,
        header: ['Email', 'Full Name'],
        content: sheetContent
    }]) %}
{% endfor %}

{% do beam.setSheets(sheets) %}
{% do beam.xlsx() %}

Note: The sheets configuration only works with XLSX exports. If you use it with csv(), it will be ignored and a standard single-sheet CSV will be generated.

Soft newlines in XLSX cells

Soft newlines (line breaks within cells) are supported in XLSX files. Simply use \n in your cell content:

{% set options = {
    header: ['Name', 'Address'],
    content: [
        [ 'John Doe', "123 Main St\nApt 4B\nNew York, NY" ],
        [ 'Jane Smith', "456 Oak Ave\nSuite 200\nLos Angeles, CA" ],
    ]
} %}
{% set beam = craft.beam.create(options) %}
{% do beam.xlsx() %}

You can also join arrays with newlines to create multi-line cells:

{% set myArray = ['Item 1', 'Item 2', 'Item 3'] %}
{% set options = {
    header: ['Name', 'Items'],
    content: [
        [ 'Order 1', myArray|join("\n") ],
    ]
} %}
{% set beam = craft.beam.create(options) %}
{% do beam.xlsx() %}

Text wrapping is enabled by default to properly display multi-line content. If you need to disable it:

{% do beam.setWrapText(false) %}

Common use cases

Export Entry Data
{% set beam = craft.beam.create({
    header: ['Title', 'Author', 'Date Published', 'URL']
}) %}

{% set entries = craft.entries()
    .section('blog')
    .orderBy('postDate DESC')
    .all() %}

{% for entry in entries %}
    {% do beam.append([
        entry.title,
        entry.author.fullName,
        entry.postDate|date('Y-m-d'),
        entry.url
    ]) %}
{% endfor %}

{% do beam.csv() %}
Export Commerce Orders

Note: This example requires Craft Commerce to be installed.

{% set beam = craft.beam.create({
    header: ['Order Number', 'Customer', 'Total', 'Date', 'Status']
}) %}

{% set orders = craft.orders()
    .isCompleted(true)
    .orderBy('dateOrdered DESC')
    .all() %}

{% for order in orders %}
    {% do beam.append([
        order.number,
        order.email,
        order.totalPrice,
        order.dateOrdered|date('Y-m-d'),
        order.orderStatus
    ]) %}
{% endfor %}

{% do beam.xlsx() %}
Export with Formatted Numbers
{% set beam = craft.beam.create({
    header: [
        'Product',
        { text: 'Price', type: 'dollar' },
        { text: 'Quantity', type: 'integer' },
        { text: 'Total', type: 'dollar' }
    ]
}) %}

{% set products = craft.entries().section('products').all() %}
{% for product in products %}
    {% do beam.append([
        product.title,
        product.price,
        product.stock,
        product.price * product.stock
    ]) %}
{% endfor %}

{% do beam.xlsx() %}

Load-balanced environments

If you're running on a load-balanced environment (like Fortrabbit, Servd, or Craft Cloud), you may experience intermittent download failures when temporary files are stored on the local filesystem.

Configure Craft to use a shared filesystem for temporary files by setting tempAssetUploadFs in your config/general.php:

return [
    '*' => [
        'tempAssetUploadFs' => 's3', // use your filesystem handle
    ],
];

Or use the CRAFT_TEMP_ASSET_UPLOAD_FS environment variable.

See the Craft documentation for more details.

About

Multiple sheets in Excel (XLSX only):

You can create an Excel file with multiple sheets using the fluent sheet() method:

{% set beam = craft.beam.create() %}
{% do beam.setFilename('users-by-group') %}

{# Create and populate sheets using fluent methods #}
{% for group in craft.users.groups() %}
    {% set users = craft.users().group(group.handle).all() %}
    
    {# Select/create a sheet and set its header #}
    {% do beam.sheet(group.name).setHeader(['Email', 'Full Name']) %}
    
    {# Append users to the active sheet #}
    {% for user in users %}
        {% do beam.append([user.email, user.fullName]) %}
    {% endfor %}
{% endfor %}

{% do beam.xlsx() %}

You can switch between sheets as needed:

{% set beam = craft.beam.create() %}

{# Set 'Summary' as the active sheet #}
{% do beam.setSheet('Summary') %}
{% do beam.setHeader(['Total Users', 'Active', 'Inactive']) %}
{% do beam.append([100, 75, 25]) %}

{# Switch to 'Details' sheet #}
{% do beam.sheet('Details').setHeader(['Email', 'Name', 'Status']) %}
{% do beam.append(['john@example.com', 'John', 'Active']) %}

{% do beam.xlsx() %}

The sheet() method also accepts an options array as the second parameter:

{% do beam.sheet('Products', {
    header: ['ID', 'Name', 'Price']
}) %}
{% do beam.append(['1', 'Product A', '10.00']) %}

Alternative: Using array-based configuration

If you need to configure all sheets upfront, you can provide a sheets array in the options:

{% set options = {
    filename: 'users-report',
    sheets: [
        {
            name: 'Active Users',
            header: ['Email', 'Name', 'Status'],
            content: [
                [ 'john@example.com', 'John Doe', 'Active' ],
                [ 'jane@example.com', 'Jane Doe', 'Active' ],
            ]
        },
        {
            name: 'Inactive Users',
            header: ['Email', 'Name', 'Status'],
            content: [
                [ 'inactive@example.com', 'Bob Smith', 'Inactive' ],
            ]
        }
    ]
} %}
{% set beam = craft.beam.create(options) %}
{% do beam.xlsx() %}

Or build the sheets array dynamically with setSheets():

{% set beam = craft.beam.create() %}
{% do beam.setFilename('users-by-group') %}

{% set sheets = [] %}
{% for group in craft.users.groups() %}
    {% set users = craft.users().group(group.handle).all() %}
    {% set sheetContent = [] %}
    {% for user in users %}
        {% set sheetContent = sheetContent|merge([[ user.email, user.fullName ]]) %}
    {% endfor %}
    
    {% set sheets = sheets|merge([{
        name: group.name,
        header: ['Email', 'Full Name'],
        content: sheetContent
    }]) %}
{% endfor %}

{% do beam.setSheets(sheets) %}
{% do beam.xlsx() %}

Note: The sheets configuration only works with XLSX exports. If you use it with csv(), it will be ignored and a standard single-sheet CSV will be generated.

Soft newlines in XLSX cells:

Soft newlines (line breaks within cells) are supported in XLSX files. Simply use \n in your cell content:

{% set options = {
    header: ['Name', 'Address'],
    content: [
        [ 'John Doe', "123 Main St\nApt 4B\nNew York, NY" ],
        [ 'Jane Smith', "456 Oak Ave\nSuite 200\nLos Angeles, CA" ],
    ]
} %}
{% set beam = craft.beam.create(options) %}
{% do beam.xlsx() %}

You can also join arrays with newlines to create multi-line cells:

{% set myArray = ['Item 1', 'Item 2', 'Item 3'] %}
{% set options = {
    header: ['Name', 'Items'],
    content: [
        [ 'Order 1', myArray|join("\n") ],
    ]
} %}
{% set beam = craft.beam.create(options) %}
{% do beam.xlsx() %}

Text wrapping is enabled by default to properly display multi-line content. If you need to disable it:

{% do beam.setWrapText(false) %}

Load-balanced environments

If you're running on a load-balanced environment (like Fortrabbit, Servd, or Craft Cloud), you may experience intermittent download failures when temporary files are stored on the local filesystem.

Configure Craft to use a shared filesystem for temporary files by setting tempAssetUploadFs in your config/general.php:

return [
    '*' => [
        'tempAssetUploadFs' => 's3', // use your filesystem handle
    ],
];

Or use the CRAFT_TEMP_ASSET_UPLOAD_FS environment variable.

See the Craft documentation for more details.

Brought to you by Superbig

Useful Resources: