Skip to content
N edited this page May 15, 2026 · 4 revisions

PlayLog API Documentation

Creating a log

To log something in the PlayLog window (the Play Log) you only need to call the PlayLog.log function.

PlayLog.log("This is a new {C:attention}log{}!")

If called with just a string, the function will log it to the Play Log, but we can get some extended functionality if we pass arguments to it. PlayLog.log accepts a table of arguments that varies based on the type.

PlayLog.log{ type = "message", text = "This is a new {C:attention}#1#{}!", vars = { "Log" } }

The "message" type acts the same way as calling the function with a string, but allows passing variables as well.

PlayLog includes many default types that are used for vanilla or generic effects.

PlayLog.log{ type = "destroys", card = card, destroyed = destroyed_cards }

This will show "[card name] destroyed [list of cards destroyed]" in the log. PlayLog automatically takes care of adding the proper localized name, colours and tooltips for the cards as well as formatting the list properly.

See the Log Types documentation for more information on the different default types.

Of course, if you call PlayLog.log in your own mod it will likely not go well if the player doesn't have the mod installed. This is also not the type of mod users would be happy to see as a dependency. To solve this problem, you can choose whichever of these two solutions you fancy the most.

-- Solution 1
-- Make a dummy function somewhere in your mod
PlayLog = PlayLog or { log = function() end }

-- Solution 2
-- Check for the mod every time you use it
if PlayLog then PlayLog.log{ ... } end

You can find examples of how to log in logging.lua

Making your own Log Types

Creating a new log type is similar to creating a new SMODS object.

PlayLog.LogType {
    key = "key", -- This will prepend the mod's prefix like regular SMODS objects
    group = "group_key", -- Group this type belongs to for the config menu. Default ones are `"generic"|"player_actions"|"effects"|"gamestate"|"scoring"`
    get_message = function(self, args) -- Returns the message for the log.
        return "message" -- It must return a string. If for any reason you don't have a log please return "ERROR" instead
    end
}

The localization of the type name for the settings will be fetched from G.localization.misc.playlog_types.modprefix_key and the group (if you make a custom one) will be fetched from G.localization.misc.playlog_groups.group_key (it is recommended to prepend your mod's prefix to avoid collisions).

There are also some helper functions to use with get_message.

PlayLog.localize(key, vars, loc_table)

This will return the localization found in G.localization.misc.playlog[key] by default or G.localization.misc[loc_table][key] if specified. vars will be used to replace any variables (more info in the formatting section below).

PlayLog.loc_list(values)

Given a table of strings (or values that can be cast to string) it returns a string with the list of values formatted in natural language. For example { "A", "B", "C" } will be formatted as "A, B and C"

PlayLog.format_object/PlayLog.format_objects

Given an object (for format_object) or an array of objects (for format_objects), it returns the formatted object names (in an array for format_objects). This will obtain the proper localized name and add the appropriate tooltip for each object. This function accepts a variety of objects of different types, although some custom ones might not be handle appropriately. In particular, it accepts:

  • A string (in which case it just returns the same value, unless it's a key to a prototype)
  • Game object instances of types such as Card, Blind or Tag
  • Prototypes of types such as SMODS.Center, SMODS.Blind, SMODS.Tag, SMODS.Seal, SMODS.Stake
  • Keys for the prototypes mentioned above

Here's an example of a basic type defined by PlayLog.

PlayLog.LogType {
    key = "skip_blind",
    group = "player_actions",
    get_message = function(self, args)
        if args.tag then
            return PlayLog.localize("skip_blind_for",
                { PlayLog.format_object(args.blind), PlayLog.format_object(args.tag) })
        end
        return PlayLog.localize("skip_blind", { PlayLog.format_object(args.blind) })
    end
}

You can find more examples in logtypes.lua

Formatting

PlayLog text formatting works almost the same as Balatro's text formatting but since the technology for the UI is custom not all the functionality has been ported.

In particular, the following is accepted:

Variables and colour indexes for V and B are obtained from vars and vars.colours respectively, similar to loc_vars in SMODS objects. If you pass vars to PlayLog.log it will fill in any variables not replaced by get_message. Unlike regular Balatro formatting however, vars can have formatting as well and it will be respected (inner formatting will override outer formatting, i.e. vars formatting will win over the localization's formatting)

The {T:} modifier works differently than in vanilla, it takes a key for an object (not necessarily in the localization table) and tries its best to make a tooltip for it. Check PlayLog.format_object above for more information about the types it accepts.

Additionally, PlayLog supports {F:function_name} to create custom tooltips. This function will be fetched from PlayLog.FUNCS.function_name (it is recommended to prepend your mod's prefix to avoid collisions). This functions takes two arguments payload, that stores values from that log that you want to show later (see below), and hovered, that has information about tooltip hovered. Here are some examples of these functions with all the different returns values possible:

PlayLog.FUNCS.pl_test_string = function(payload, hovered)
    return "Plain string return — title defaults to func name"
end
-- Example log usage
PlayLog.log("{F:pl_test_string}[FUNC string]{}")

PlayLog.FUNCS.pl_test_text_only = function(payload, hovered)
    return { text = "Table return with only .text set" }
end

PlayLog.FUNCS.pl_test_custom_title = function(payload, hovered)
    return { title = "Custom Title", text = "Body text here" }
end

PlayLog.FUNCS.pl_test_no_title = function(payload, hovered)
    return { title = false, text = "Title is hidden (title = false)" }
end

PlayLog.FUNCS.pl_test_title_colour = function(payload, hovered)
    return {
        title        = "Red Title",
        title_colour = G.C.RED,
        text         = "title_colour = G.C.RED",
    }
end

PlayLog.FUNCS.pl_test_title_scale = function(payload, hovered)
    return {
        title       = "Big Title",
        title_scale = 0.72,
        text        = "title_scale = 0.72",
    }
end

PlayLog.FUNCS.pl_test_body_colour = function(payload, hovered)
    return {
        text   = "Body text in green",
        colour = G.C.GREEN,
    }
end

PlayLog.FUNCS.pl_test_body_bg = function(payload, hovered)
    return {
        text      = "Body text with blue pill background",
        colour    = G.C.WHITE,
        bg_colour = G.C.BLUE,
    }
end

PlayLog.FUNCS.pl_test_body_scale = function(payload, hovered)
    return {
        text  = "Body at scale 0.62",
        scale = 0.62,
    }
end

PlayLog.FUNCS.pl_test_all_options = function(payload, hovered)
    return {
        title        = "All Options",
        title_colour = G.C.GOLD,
        title_scale  = 0.65,
        text         = "gold title, mult bg, white body, scale 0.55",
        colour       = G.C.WHITE,
        bg_colour    = G.C.MULT,
        scale        = 0.55,
    }
end

PlayLog.FUNCS.pl_test_rows = function(payload, hovered)
    return {
        title = "Rows Return",
        rows = {
            { { n = G.UIT.T, config = { text = "Row 1: chips", scale = 0.44, colour = G.C.CHIPS, shadow = true, align = 'cm' } } },
            { { n = G.UIT.T, config = { text = "Row 2: mult", scale = 0.10, colour = G.C.MULT, shadow = true, align = 'cm' } } },
            { { n = G.UIT.T, config = { text = "Row 3: money", scale = 0.80, colour = G.C.MONEY, shadow = true, align = 'cm' } } },
        }
    }
end

PlayLog.FUNCS.pl_test_uibox = function(payload, hovered)
    return { -- replaces the whole tooltip
        definition = {
            n = G.UIT.ROOT,
            config = {
                align = "cm",
                padding = 0.1,
                r = 0.12,
                emboss = 0.1,
                colour =lighten(G.C.JOKER_GREY, 0.5)
            },
            nodes = {
                {
                    n = G.UIT.R,
                    config = { align = "cm", minw = 1, colour = adjust_alpha(darken(G.C.BLACK, 0.1), 0.8), r = 0.1 },
                    nodes = {
                    }
                },
            }
        }
    }
end

PlayLog.FUNCS.pl_test_no_shadow = function()
    return {
        title = "No Shadow Test",
        title_shadow = false,
        shadow = false,
        text = "Both title and text have no shadow",
        colour = G.C.RED,
    }
end

PlayLog.FUNCS.pl_test_payload = function(payload, hovered)
    if not payload then return { title = false, text = "no payload" } end
    return {
        title  = "Payload Demo",
        text   = "chips: " .. tostring(payload.chips) .. "  mult: " .. tostring(payload.mult),
        colour = G.C.CHIPS,
    }
end
-- Example log usage
local payload_ref = PlayLog.store_func_payload('pl_test_payload', { chips = 150, mult = 8 })
if payload_ref then
    PlayLog.log("{F:" .. payload_ref .. "}[FUNC payload]{}")
end

Payloads can be stored by calling PlayLog.store_func_payload('function_name', table) and using the return as the function name, as seen above.

The combination {F:function_name,T:tooltip-key} calls PlayLog.FUNCS.function_name(tooltip_ui) where the argument is the tooltip for the card with key tooltip-key, which you can return with modifications.