TSML (WordPress 12 Step Meeting List plugin)

Uit De Vliegende Brigade
Naar navigatie springen Naar zoeken springen

There seems to exist only one current publicly available plugin for listing 12 Step meetings on WordPress websites: 12 Step Meeting List (TSML) plugin. The documentation on the plugin's Wordpress.org, seems promising.

There are surely lots of non-public plugins or other solutions for listing 12 Step meetings on WordPress websites. One of these is MeoModo Meeting Finder. This plugin was custom developed for Alcoholics Anonymous in The Netherlands. As far as I am concerned, it is not supported anymore.


wp-admin » Plugins after installing the 12 Step Meeting List plugin
After installation, a new admin menu item Meetings becomes available
wp plugin install 12-step-meeting-list --activate

E.g., 2023.05.13:

$ wp plugin install 12-step-meeting-list --activate

Installing 12 Step Meeting List (3.14.14)
Downloading installation package from https://downloads.wordpress.org/plugin/12-step-meeting-list.3.14.14.zip...
Unpacking the package...
Installing the plugin...
Plugin installed successfully.
Activating '12-step-meeting-list'...
Plugin '12-step-meeting-list' activated.
Success: Installed 1 of 1 plugins.

Where are meetings displayed?

Here is the link to the meeting page:

Wp-admin » Meetings » Import & Export » Where's My Info? » "right here"


  • By default, the meeting page is located at URL/link/slug /meetings
  • MeoModo's Meeting Finder uses the same slug, so you can't see them at the same time, unless you change the meeting page for one of these plugins.


Change the location of the meeting page by setting variable $tsml_slug in file functions.php [1]. E.g.:

$tsml_slug = 'my-non-meeting-meeting-page';

Data model

There are several tables:

  1. Groups
  2. Meetings ← This is the main object
  3. Locations.

                                     | Types =        |
                                     | $tsml_programs |
                                 +-∞-| Type code (PK) |
+----------------------------+   |   | Description    |
| Meetings: posts where      |   |   +----------------+
| post_type = "tsml_meeting" |   |
+----------------------------+   |    
| post_id (PK)               | ∞-+   +--------------------------+
| Group (post_meta?)         | ∞---1 | Groups: posts where      |
+----------------------------+       | post_type = "tsml_group" |
                                     | post_id (PK)             |
                                     | ...                      |

Field definitions

This chapter contains an overview of the fields that are available for meetings. The primary source for this is available within the plugin:

wp-admin » Meetings » Import & Export » Example CSV » Field definition

The alternative names in the titles below, following the '»' symbol (if available) are what I use internally. I do this for various reasons:

  • To avoid using reserved SQL keywords
  • To avoid spaces and uppercase letters
  • To make titles less ambiguous
  • To make titles more consistent.

However, this is only done when needed. E.g., fields that are manually entered in a spreadsheet, rather than filled through SQL, don't need this.

Time » start_time

  • Meeting start time according to the local time zone
  • Format: hh:mm:ss (maybe there is an alternative format for 12-hour (AM/PM) style notation).

It seems that this field has to be formatted including seconds, like 12:00:00. If seconds are omitted, the time is not recognised and the meeting time will appear as on appointment (which is the default behaviour when a time is not understood).

End Time » end_time

Meeting end time

Day » day_of_week

One element from the collection {Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sonday}.

This is how you can see that this object is about meetings, not groups: A multigroup would have a separate meeting object for each meeting.

Name » meeting_name

  • The name of the meeting
  • In case of a multigroup with meetings with different names, use here the name of the meeting on this specific day.

That may sound theoretical, but it isn't. E.g.:

Group: The Hague Multi-Group

Associated meetings:

* Tuesday 20:00-21:00: Candlelight Topic Meeting
* Saturday 20:00-21:00: Saturday Night Discussion Group

Note that the second meeting actually has the word "group" in its name. That's probably an indication that this is a merger of two previous-independent groups, and they didn't bother to change the name of the second meeting/group. Or this is a multi-group consisting of a meeting and a group - Whatever. It really doesn't matter, once the basic data model is sufficiently fine-grained.

Slug » meeting_slug

A slug should be unique.

Location » meeting_location

Location is the name of the location, and is optional. Generally it's the
group or building name. If it's missing, the address will be used. In the
event that there are multiple location names for the same address, the
first location name will be used.
  • Name of the location - Not the actual address
  • E.g., American Church, The Hague
  • I think this field comes handy for folks scanning a meeting list for familiar locations
  • Personally, I include the name of the city here
  • Optional.


  • Strongly encouraged
  • Corrected through Google, so it might look differently afterwards

City, State, Country

  • Optional
  • Not needed, if Address is complete.


  • Freeform notes.

Conference URL

Header in TSML at a meeting page, for the section Online Meeting Details:

If this meeting has videoconference information, please enter the ful
valid URL here. Currently supported providers: Bluejeans, Dialpad,
Discord, Free Conference, FreeConferenceCall, Google Hangouts, GoTo,
Jitsi, Skype, WebEx, Zoho, Zoom. If other details are required, such as
a password, they can be included in the Notes field above, but a ‘one
tap’ experience is ideal. Passwords can be appended to phone numbers
using this format +12125551212,,123456789#,,#,,444444#

Documentation TSML for this specific field:

Conference URL is an optional URL to a specific public
videoconference meeting. This should be a common videoconferencing
service such as Zoom or Google Hangouts. It should launch directly into
the meeting and not link to an intermediary page.
  • The field Conferenece URL is only relevant for online meetings. It should contain the direct link to the meeting.
  • Strangely, TSML only supports a limited number of services for online meetings
  • It doesn't collaborate with 'intermediate' links, like Bit.ly links?


Here, a group is a way to cluster meetings together, usually because they share contact details and group name. These meetings are clustered simply through the name of the group. So, if you have a group in Rotterdam, called Sunrise Serenity and

  • The name of the group
  • {Meetings} and {Groups} are related through this field


Include types (or tags) as a comma-separated list. E.g.:

Babysitting Available, Birthday, Open, Wheelchair Access

You can only use types that are defined, prior to being used - See next chapter for details.

Types - General

Go to wp-admin » Meetings » Create a new meeting or edit an existing meeting » View all to see all available tags

Types are a tag or label system. Rather than using the default system within WordPress for this, it is a standalone system, with the default list hardcoded as an array in file wp-content/plugins/12-step-meetinglist/includes/variables.php. For each fellowship, there is a separate list. That's why you would find the same type multiple times, when doing a search like through grep -rn . -e "Transgender".

Default list - 2023.05

wp-admin » Meetings » Create a new meeting or edit an existing meeting » View all
  • 11th Step Meditation
  • 12 Steps & 12 Traditions
  • As Bill Sees It
  • Babysitting Available
  • Big Book
  • Birthday
  • Breakfast
  • Candlelight
  • Child-Friendly
  • Closed
  • Concurrent with Al-Anon
  • Concurrent with Alateen
  • Cross Talk Permitted
  • Daily Reflections
  • Digital Basket
  • Discussion
  • Dual Diagnosis
  • English
  • Fragrance Free
  • French
  • Gay
  • Grapevine
  • Hebrew
  • Indigenous
  • Italian
  • Japanese
  • Korean
  • Lesbian
  • Literature
  • Living Sober
  • Location Temporarily Closed
  • Meditation
  • Men
  • Native American
  • Newcomer
  • Non-Binary
  • Online Meeting
  • Open
  • Outdoor Meeting
  • People of Color
  • Polish
  • Portuguese
  • Professionals
  • Punjabi
  • Russian
  • Secular
  • Seniors
  • Sign Language
  • Smoking Permitted
  • Spanish
  • Speaker
  • Step Meeting
  • Tradition Study
  • Transgender
  • Wheelchair Access
  • Wheelchair-Accessible Bathroom
  • Women
  • Young People

Hardcoded array - AA

This is the part of the array with types for AA meetings:

'types' => [
  '11'    => __('11th Step Meditation', '12-step-meeting-list'),
  '12x12' => __('12 Steps & 12 Traditions', '12-step-meeting-list'),
  'ABSI'  => __('As Bill Sees It', '12-step-meeting-list'),
  'BA'    => __('Babysitting Available', '12-step-meeting-list'),
  'B'     => __('Big Book', '12-step-meeting-list'),
  'H'     => __('Birthday', '12-step-meeting-list'),
  'BRK'   => __('Breakfast', '12-step-meeting-list'),
  'CAN'   => __('Candlelight', '12-step-meeting-list'),
  'CF'    => __('Child-Friendly', '12-step-meeting-list'),
  'C'     => __('Closed', '12-step-meeting-list'),
  'AL-AN' => __('Concurrent with Al-Anon', '12-step-meeting-list'),
  'AL'    => __('Concurrent with Alateen', '12-step-meeting-list'),
  'XT'    => __('Cross Talk Permitted', '12-step-meeting-list'),
  'DR'    => __('Daily Reflections', '12-step-meeting-list'),
  'DB'    => __('Digital Basket', '12-step-meeting-list'),
  'D'     => __('Discussion', '12-step-meeting-list'),
  'DD'    => __('Dual Diagnosis', '12-step-meeting-list'),
  'EN'    => __('English', '12-step-meeting-list'),
  'FF'    => __('Fragrance Free', '12-step-meeting-list'),
  'FR'    => __('French', '12-step-meeting-list'),
  'G'     => __('Gay', '12-step-meeting-list'),
  'GR'    => __('Grapevine', '12-step-meeting-list'),
  'HE'    => __('Hebrew', '12-step-meeting-list'),
  'NDG'   => __('Indigenous', '12-step-meeting-list'),
  'ITA'   => __('Italian', '12-step-meeting-list'),
  'JA'    => __('Japanese', '12-step-meeting-list'),
  'KOR'   => __('Korean', '12-step-meeting-list'),
  'L'     => __('Lesbian', '12-step-meeting-list'),
  'LIT'   => __('Literature', '12-step-meeting-list'),
  'LS'    => __('Living Sober', '12-step-meeting-list'),
  'LGBTQ' => __('LGBTQ', '12-step-meeting-list'),
  'TC'    => __('Location Temporarily Closed', '12-step-meeting-list'),
  'MED'   => __('Meditation', '12-step-meeting-list'),
  'M'     => __('Men', '12-step-meeting-list'),
  'N'     => __('Native American', '12-step-meeting-list'),
  'BE'    => __('Newcomer', '12-step-meeting-list'),
  'NB'    => __('Non-Binary', '12-step-meeting-list'),
  'ONL'   => __('Online Meeting', '12-step-meeting-list'),
  'O'     => __('Open', '12-step-meeting-list'),
  'OUT'   => __('Outdoor Meeting', '12-step-meeting-list'),
  'POC'   => __('People of Color', '12-step-meeting-list'),
  'POL'   => __('Polish', '12-step-meeting-list'),
  'POR'   => __('Portuguese', '12-step-meeting-list'),
  'P'     => __('Professionals', '12-step-meeting-list'),
  'PUN'   => __('Punjabi', '12-step-meeting-list'),
  'RUS'   => __('Russian', '12-step-meeting-list'),
  'A'     => __('Secular', '12-step-meeting-list'),
  'SEN'   => __('Seniors', '12-step-meeting-list'),
  'ASL'   => __('Sign Language', '12-step-meeting-list'),
  'SM'    => __('Smoking Permitted', '12-step-meeting-list'),
  'S'     => __('Spanish', '12-step-meeting-list'),
  'SP'    => __('Speaker', '12-step-meeting-list'),
  'ST'    => __('Step Meeting', '12-step-meeting-list'),
  'TR'    => __('Tradition Study', '12-step-meeting-list'),
  'T'     => __('Transgender', '12-step-meeting-list'),
  'X'     => __('Wheelchair Access', '12-step-meeting-list'),
  'XB'    => __('Wheelchair-Accessible Bathroom', '12-step-meeting-list'),
  'W'     => __('Women', '12-step-meeting-list'),
  'Y'     => __('Young People', '12-step-meeting-list'),

Change in functions.php

  • You can change these tags, but be careful: If you're getting too creative, or change an existing type, you might loose compatibility with the meeting app, and your types won't show up
  • Don't change the existing variable mentioned above, because you will loose changes upon updating. Rather, overwrite it in theme file functions.php

There are different ways to edit your function.php file:

  1. Files (on your webserver) » wp-content » themes » active theme or child theme » functions.php
  2. wp-admin » Appearance » Theme File Editor: Handy to verify if you have multiple themes and subthemes on your site, and you aren't sure which one to edit.

When you edit this list through functions.php, changes are immediate.

According to https://wordpress.org/plugins/12-step-meeting-list/#faq-header, ther are are least two functions for updating Types: tsml_custom_descriptions() and tsml_custom_types(). However: Only the latter seems to work for me, and only to delete descriptions (but keep the actual Type ID)


As mentioned, this only seems to delete the description, not the actual Type:

if (function_exists('tsml_custom_types')) {
        'BA' => '',
        'BRK' => '',
        'CAN' => '',


To add a type:

if (function_exists('tsml_custom_types')) {
        'XYZ' => 'My Custom Type',

Update description

I have no idea why you would want to update a description - but you can:

if (function_exists('tsml_custom_types')) {
        'KOR' => 'Juhu! Our first Korean meeting!',


The location for additions (specific case I've worked with):

/var/www/example.com » wp-content » themes » enfold-child » functions.php

Relevant parts of this file:

// Update tag descriptions
// LIT - Literature
// Make clear that this means "Literature for sale" and not "Literature being
// read"
if (function_exists('tsml_custom_types')) {
        'LIT' => 'Literature for sale'

// Add tags
// Add the tags found here that are not present by default
// https://wiki.devliegendebrigade.nl/MeoModo_Meeting_Finder_plugin_(WordPress)#Tags
// Notes
// * Tag "FP - Free parking" has been dropped - 2023.10
// * Tag "Bilingual Dutch/English" changed to "Bilingual": Otherwise, we would
//   need a whole bunch of such tags. Just include all languages that are
//   relevant, both in English and in the related languages(s). BTW: Correct
//   spelling is "bilingual"
// * Tag "Birthday": I'm not sure if this would mean the same as "Sobriety
//   chips", and I think that for some folks, this potential difference would
//   be very important
// Resources
// * https://wiki.devliegendebrigade.nl/MeoModo_Meeting_Finder_plugin_(WordPress)#Tags
// * https://wiki.devliegendebrigade.nl/12_Step_Meeting_List_plugin_(WordPress)
// * https://wordpress.org/plugins/12-step-meeting-list/#faq-header
if (function_exists('tsml_custom_types')) {
        'BI'  => 'Bilingual',
        'DUT' => 'Dutch'
        'F2F' => 'Face-2-face',
        'HY'  => 'Hybrid',
        'NL'  => 'Nederlands'
        'SC'  => 'Sobriety chips',
        'TEL' => 'Telegram',
        'TOP' => 'Topic',
        'UKR' => 'Ukrainian',
        'Z'   => 'Zoom',
        'RU2' => 'Русский',
        'UK2' => 'Українська'

// Tags from old site that don't exist on the new site:
// Bilingual Dutch/English
// English
// Face-2-face
// Free parking
// Gay men
// Hybrid
// OK - Online Meeting
// OK - Open
// OK - Russian
// Sobriety chips → Birthday
// Spanish - En Español - S
// Speaker meeting - SP
// Telegram
// Topic meeting
// Ukrainian
// Women - W
// Young people - Y
// Zoom
// Русский
// українська

Types - Specific

Some specific types are discussed here.

BI - Bilingual

  • Use this tag for meetings where folks are invited to share in more than one language
  • Usually, this means English plus another language, but not always so
  • When a meeting is in English plus another language, only indicate what the other language is
  • When a meeting is in two (or more) languages, none of which is English, indicate both languages
  • When indicating languages other then English, do so in English as well as in the native language(s) & scripts
  • Often, names can refer to both country or language, but here it always refers to language. E.g.: Ukrainian means that the language is Ukrainian, not that the meeting is somehow related to the country (which would imply that both Russian and Ukrainian is spoken).


Languages Types
English + Dutch
  • Bilingual
  • Dutch
  • Nederlands
Ukrainian + Russian
  • Bilingual
  • Russian
  • Ukrainian
  • Русский
  • українська
English, Russian, Ukrainian
  • Bilingual
  • English
  • Russian
  • Ukrainian
  • Русский
  • українська

Dutch - DUT & Nederlands - NL

Use these tags together, for meetings where Dutch is one of the expected languages for participants to share in.

In person

This is a weird one: As soon as an address is provided, a meeting gets the type In-person. So no need for an additional type Face-2-face.

LIT - Literature

I don't know if this stands for

  1. A meeting where literature is being read
  2. A meeting with literature for sale.

I contacted the developers through the plugin page at https://github.com/code4recovery/12-step-meeting-list/discussions/1229 and they didn't have a conclusive answer either. I assume that it rather means that literature is available for sale: It seems more relevant to me that folks can identify meetings where literature is for sale, than the information that some literature is being read.

Online meetings require physical addresses

Online meetings require approximate physical addresses. This doesn't make sense to me and is actually confusing. I guess this is a hint that this plugin was developed before the corona pandemic of 2020-2022. The reason behind this (as for some other appearantly suprising choices): To keep compatibility with the meeting app.

For meetings in the Netherlands, to be shared with other AA entities, what would be an appropriate value here?

Option Considerations
  • Even online meetings usually seem to have some kind of geographic basis or origin. E.g.: 'the Canary Islands online meeting', or an online meeting that was started in Amsterdam
  • The confusing part (actually, about requiring a phyical address for an online meeting): The map will show a seemingly arbitrary place somewhere in the middle of The Netherlands
Continental European Time Zone
  • Stating a time zone might make sense, since meetings effectively are associated with certain time zones, even if participants come from all over the world
  • Can map services handle this? - I think I gave it a try, and it didn't go well
  • Isn't this even more confusing to visitors?

→ Currently, I use Netherlands (2023.10).


Non-latin script

About using non-latin script (Cyrillic, to be precise):

  • Group name: No problem
  • Type: No problem
  • Import/export: No problem (use UTF-8)
  • Permalink: Problem → Use ASCII.

Specifications CSV import file

  • Comma-separated
  • Cells that contain commas, should be quoted using double quotes. Address fields are likely to contain commas, e.g.: Dam 10, Amsterdam
  • Character set: UTF-8 - Important when E.g., Cyrillic, Russian or Ukrainian script is included in the source file.
Example of creating an export file from LibreOffice Calc, for import in TSML

Add a conference provider

By default, TSML only supports a limited number of conference providers for online meetings. This is to maintain compatibility with the Meeting Guide App. Fortunately, you can add conference providers yourself, or even remove this validation completely.

Default supported conference providers

Message from TSML when you go to a meeting » Edit, for the section Online Meeting Details:

If this meeting has videoconference information, please enter the ful
valid URL here. Currently supported providers: Bluejeans, Dialpad,
Discord, Free Conference, FreeConferenceCall, Google Hangouts, GoTo,
Jitsi, Skype, WebEx, Zoho, Zoom. If other details are required, such as
a password, they can be included in the Notes field above, but a ‘one
tap’ experience is ideal. Passwords can be appended to phone numbers
using this format +12125551212,,123456789#,,#,,444444#

The list of valid conference providers is stored in an array called $tsml_conference_providers. This array is initialized in file plugins/12-step-meeting-list/includes/variables.php. It is used in file plugins/12-step-meeting-list/includes/functions.php, .../shortcodes.php and .../admin_meeting.php

In file plugins/12-step-meeting-list/includes/functions.php, the default conference providers are defined around lines 38-54 (2023.10):

//list of valid conference providers (matches Meeting Guide app). set this to null in your theme if you don't want to validate
$tsml_conference_providers = [
	'bluejeans.com' => 'Bluejeans',
	'discord.gg' => 'Discord',
	'freeconference.com' => 'Free Conference',
	'freeconferencecall.com' => 'FreeConferenceCall',
	'goto.com' => 'GoTo',
	'gotomeet.me' => 'GoTo',
	'gotomeeting.com' => 'GoTo',
	'meet.google.com' => 'Google Hangouts',
	'meet.jit.si' => 'Jitsi',
	'meetings.dialpad.com' => 'Dialpad',
	'skype.com' => 'Skype',
	'webex.com' => 'WebEx',
	'zoho.com' => 'Zoho',
	'zoom.us' => 'Zoom',

How to add

Adding a conference provider - like Telegram in our case, is quite doable, so you won't be depending on the plugin developers for this.

Add a conference provider in your own functions.php by extending array $tsml_conference_providers, typically located in your child theme directory. In the case on which this article is based, it is wp-content/themes/enfold-child/functions.php.

Added code:

// Add Telegram as Conference Provider
if (!empty($tsml_conference_providers))
    $tsml_conference_providers += [ 
        't.me' => 'Telegram',

Before this takes effect, you need to leave the current meeting object.


Mapbox & User Interface

As of 2023.11, TSML offers two user interfaces:

  • Legacy UI: Looks awful, but works with both Google Maps and Mapbox for displaying meetings on maps
  • TSML UI: Looks good, but only works with Mapbox.

And now for a problem: When you sign up for a Mapbox account, you need to provide credit card information.

Oh oh, you need a credit card to sign up for Mapbox, eventhough they assure you that you don't really need it
Always reassuring to see that I'm not the only one who sees a problem

Appendix: Function reference

All from https://github.com/code4recovery/12-step-meeting-list/blob/main/includes/functions.php


// Function: define custom type descriptions
// Used:     theme's functions.php
function tsml_custom_descriptions($descriptions)
	global $tsml_programs, $tsml_program;
	$tsml_programs[$tsml_program]['type_descriptions'] = $descriptions;


// Function: define custom flags (/men, /women) for your area
// Used:     theme's functions.php
function tsml_custom_flags($flags)
	global $tsml_programs, $tsml_program;
	$tsml_programs[$tsml_program]['flags'] = $flags;


// Function: define custom meeting types for your area
// Used:     theme's functions.php
function tsml_custom_types($types)
	global $tsml_programs, $tsml_program;
	foreach ($types as $key => $value) {
		$tsml_programs[$tsml_program]['types'][$key] = $value;

See also